1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-21 08:51:34 +02:00
invoiceninja/app/Ninja/Repositories/InvoiceRepository.php

998 lines
40 KiB
PHP
Raw Normal View History

2017-01-30 20:40:43 +01:00
<?php
2013-12-25 22:34:42 +01:00
2017-01-30 20:40:43 +01:00
namespace App\Ninja\Repositories;
use App\Jobs\SendInvoiceEmail;
use App\Models\Account;
2017-01-30 13:35:04 +01:00
use App\Models\Client;
2017-01-30 20:40:43 +01:00
use App\Models\Document;
use App\Models\Expense;
use App\Models\Invitation;
2015-03-31 11:38:24 +02:00
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\Product;
2015-05-27 18:52:10 +02:00
use App\Models\Task;
use App\Services\PaymentService;
2017-01-30 20:40:43 +01:00
use Auth;
use DB;
use Utils;
2013-12-25 22:34:42 +01:00
2015-10-28 20:22:07 +01:00
class InvoiceRepository extends BaseRepository
2013-12-25 22:34:42 +01:00
{
protected $documentRepo;
2015-10-28 20:22:07 +01:00
public function getClassName()
{
return 'App\Models\Invoice';
}
2016-12-14 20:47:22 +01:00
public function __construct(PaymentService $paymentService, DocumentRepository $documentRepo, PaymentRepository $paymentRepo)
{
$this->documentRepo = $documentRepo;
$this->paymentService = $paymentService;
2016-12-14 20:47:22 +01:00
$this->paymentRepo = $paymentRepo;
}
2015-11-18 15:40:50 +01:00
public function all()
{
return Invoice::scope()
2016-05-26 16:56:54 +02:00
->invoiceType(INVOICE_TYPE_STANDARD)
2015-11-18 15:40:50 +01:00
->with('user', 'client.contacts', 'invoice_status')
->withTrashed()
->where('is_recurring', '=', false)
->get();
}
2015-01-11 12:56:58 +01:00
public function getInvoices($accountId, $clientPublicId = false, $entityType = ENTITY_INVOICE, $filter = false)
{
$query = DB::table('invoices')
->join('accounts', 'accounts.id', '=', 'invoices.account_id')
2015-01-11 12:56:58 +01:00
->join('clients', 'clients.id', '=', 'invoices.client_id')
->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
2014-05-20 23:40:09 +02:00
->where('invoices.account_id', '=', $accountId)
2014-06-02 18:21:47 +02:00
->where('contacts.deleted_at', '=', null)
2015-01-11 12:56:58 +01:00
->where('invoices.is_recurring', '=', false)
->where('contacts.is_primary', '=', true)
2016-10-20 17:14:54 +02:00
//->whereRaw('(clients.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
->select(
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
'clients.public_id as client_public_id',
'clients.user_id as client_user_id',
'invoice_number',
'invoice_number as quote_number',
'invoice_status_id',
DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"),
'invoices.public_id',
'invoices.amount',
'invoices.balance',
'invoices.invoice_date as date',
'invoices.due_date',
'invoices.due_date as valid_until',
'invoice_statuses.name as status',
'invoice_statuses.name as invoice_status_name',
'contacts.first_name',
'contacts.last_name',
'contacts.email',
'invoices.quote_id',
'invoices.quote_invoice_id',
'invoices.deleted_at',
'invoices.is_deleted',
2016-03-16 00:08:00 +01:00
'invoices.partial',
'invoices.user_id',
'invoices.is_public',
'invoices.is_recurring'
);
2015-01-11 12:56:58 +01:00
2016-11-18 14:31:43 +01:00
$this->applyFilters($query, $entityType, ENTITY_INVOICE);
2016-11-20 15:08:36 +01:00
if ($statuses = session('entity_status_filter:' . $entityType)) {
$statuses = explode(',', $statuses);
2016-11-18 14:31:43 +01:00
$query->where(function ($query) use ($statuses) {
foreach ($statuses as $status) {
if (in_array($status, \App\Models\EntityModel::$statuses)) {
continue;
}
$query->orWhere('invoice_status_id', '=', $status);
}
if (in_array(INVOICE_STATUS_OVERDUE, $statuses)) {
$query->orWhere(function ($query) use ($statuses) {
$query->where('invoices.balance', '>', 0)
->where('invoices.due_date', '<', date('Y-m-d'))
->where('invoices.is_public', '=', true);
2016-11-18 14:31:43 +01:00
});
}
});
2015-01-11 12:56:58 +01:00
}
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
2016-11-27 13:20:58 +01:00
} else {
$query->whereNull('clients.deleted_at');
2015-01-11 12:56:58 +01:00
}
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%')
2016-11-27 10:46:32 +01:00
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
->orWhere('contacts.email', 'like', '%'.$filter.'%');
2013-12-25 22:34:42 +01:00
});
2015-01-11 12:56:58 +01:00
}
return $query;
}
public function getRecurringInvoices($accountId, $clientPublicId = false, $filter = false)
{
$query = DB::table('invoices')
->join('accounts', 'accounts.id', '=', 'invoices.account_id')
2015-01-11 12:56:58 +01:00
->join('clients', 'clients.id', '=', 'invoices.client_id')
->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', $accountId)
2016-05-26 16:56:54 +02:00
->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD)
2015-01-11 12:56:58 +01:00
->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', true)
->where('contacts.is_primary', '=', true)
->select(
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
'clients.public_id as client_public_id',
DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"),
'invoices.public_id',
'invoices.amount',
'frequencies.name as frequency',
'invoices.start_date',
'invoices.end_date',
'invoices.last_sent_date',
2017-01-26 19:17:02 +01:00
'invoices.last_sent_date as last_sent',
'contacts.first_name',
'contacts.last_name',
'contacts.email',
'invoices.deleted_at',
2016-05-06 20:26:37 +02:00
'invoices.is_deleted',
'invoices.user_id'
);
2015-01-11 12:56:58 +01:00
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
2016-11-27 13:20:58 +01:00
} else {
$query->whereNull('clients.deleted_at');
2015-01-11 12:56:58 +01:00
}
2016-11-18 14:31:43 +01:00
$this->applyFilters($query, ENTITY_RECURRING_INVOICE, ENTITY_INVOICE);
2015-01-11 12:56:58 +01:00
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%')
2016-12-19 10:48:11 +01:00
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%')
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
->orWhere('contacts.email', 'like', '%'.$filter.'%');
2013-12-25 22:34:42 +01:00
});
2015-01-11 12:56:58 +01:00
}
2013-12-25 22:34:42 +01:00
2015-01-11 12:56:58 +01:00
return $query;
}
2013-12-25 22:34:42 +01:00
public function getClientRecurringDatatable($contactId)
2015-01-11 12:56:58 +01:00
{
$query = DB::table('invitations')
->join('accounts', 'accounts.id', '=', 'invitations.account_id')
2015-01-11 12:56:58 +01:00
->join('invoices', 'invoices.id', '=', 'invitations.invoice_id')
->join('clients', 'clients.id', '=', 'invoices.client_id')
->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id')
->where('invitations.contact_id', '=', $contactId)
->where('invitations.deleted_at', '=', null)
2016-05-26 16:56:54 +02:00
->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD)
->where('invoices.is_deleted', '=', false)
->where('clients.deleted_at', '=', null)
->where('invoices.is_recurring', '=', true)
->where('invoices.is_public', '=', true)
2016-05-09 22:29:02 +02:00
->whereIn('invoices.auto_bill', [AUTO_BILL_OPT_IN, AUTO_BILL_OPT_OUT])
//->where('invoices.start_date', '>=', date('Y-m-d H:i:s'))
->select(
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
'invitations.invitation_key',
'invoices.invoice_number',
'invoices.due_date',
'clients.public_id as client_public_id',
'clients.name as client_name',
'invoices.public_id',
'invoices.amount',
'invoices.start_date',
'invoices.end_date',
2016-05-09 22:29:02 +02:00
'invoices.client_enable_auto_bill',
'frequencies.name as frequency'
);
$table = \Datatable::query($query)
2017-01-30 17:05:31 +01:00
->addColumn('frequency', function ($model) {
return $model->frequency;
})
->addColumn('start_date', function ($model) {
return Utils::fromSqlDate($model->start_date);
})
->addColumn('end_date', function ($model) {
return Utils::fromSqlDate($model->end_date);
})
->addColumn('amount', function ($model) {
return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id);
})
2016-05-09 22:29:02 +02:00
->addColumn('client_enable_auto_bill', function ($model) {
if ($model->client_enable_auto_bill) {
2016-06-30 20:05:02 +02:00
return trans('texts.enabled') . ' - <a href="javascript:setAutoBill('.$model->public_id.',false)">'.trans('texts.disable').'</a>';
} else {
2016-06-30 20:05:02 +02:00
return trans('texts.disabled') . ' - <a href="javascript:setAutoBill('.$model->public_id.',true)">'.trans('texts.enable').'</a>';
}
});
return $table->make();
}
public function getClientDatatable($contactId, $entityType, $search)
{
$query = DB::table('invitations')
->join('accounts', 'accounts.id', '=', 'invitations.account_id')
2015-01-11 12:56:58 +01:00
->join('invoices', 'invoices.id', '=', 'invitations.invoice_id')
->join('clients', 'clients.id', '=', 'invoices.client_id')
2016-05-01 10:43:10 +02:00
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invitations.contact_id', '=', $contactId)
->where('invitations.deleted_at', '=', null)
2016-05-26 16:56:54 +02:00
->where('invoices.invoice_type_id', '=', $entityType == ENTITY_QUOTE ? INVOICE_TYPE_QUOTE : INVOICE_TYPE_STANDARD)
->where('invoices.is_deleted', '=', false)
->where('clients.deleted_at', '=', null)
2016-05-01 10:43:10 +02:00
->where('contacts.deleted_at', '=', null)
->where('contacts.is_primary', '=', true)
->where('invoices.is_recurring', '=', false)
->where('invoices.is_public', '=', true)
// Only show paid invoices for ninja accounts
->whereRaw(sprintf("((accounts.account_key != '%s' and accounts.account_key != '%s') or invoices.invoice_status_id = %d)", env('NINJA_LICENSE_ACCOUNT_KEY'), NINJA_ACCOUNT_KEY, INVOICE_STATUS_PAID))
->select(
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
'invitations.invitation_key',
'invoices.invoice_number',
'invoices.invoice_date',
'invoices.balance as balance',
'invoices.due_date',
'clients.public_id as client_public_id',
DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"),
'invoices.public_id',
'invoices.amount',
'invoices.start_date',
'invoices.end_date',
'invoices.partial'
);
2015-01-11 12:56:58 +01:00
$table = \Datatable::query($query)
2017-01-30 17:05:31 +01:00
->addColumn('invoice_number', function ($model) use ($entityType) {
return link_to('/view/'.$model->invitation_key, $model->invoice_number)->toHtml();
})
->addColumn('invoice_date', function ($model) {
return Utils::fromSqlDate($model->invoice_date);
})
->addColumn('amount', function ($model) {
return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id);
});
2015-01-11 12:56:58 +01:00
if ($entityType == ENTITY_INVOICE) {
2015-04-16 21:57:12 +02:00
$table->addColumn('balance', function ($model) {
return $model->partial > 0 ?
trans('texts.partial_remaining', [
2016-01-10 11:25:05 +01:00
'partial' => Utils::formatMoney($model->partial, $model->currency_id, $model->country_id),
2017-01-30 20:40:43 +01:00
'balance' => Utils::formatMoney($model->balance, $model->currency_id, $model->country_id),
]) :
Utils::formatMoney($model->balance, $model->currency_id, $model->country_id);
2015-04-16 21:57:12 +02:00
});
2015-01-11 12:56:58 +01:00
}
2017-01-30 17:05:31 +01:00
return $table->addColumn('due_date', function ($model) {
return Utils::fromSqlDate($model->due_date);
})
2015-01-11 12:56:58 +01:00
->make();
}
/**
2017-01-30 20:40:43 +01:00
* @param array $data
* @param Invoice|null $invoice
2017-01-30 20:40:43 +01:00
*
* @return Invoice|mixed
*/
public function save(array $data, Invoice $invoice = null)
2014-05-20 23:40:09 +02:00
{
/** @var Account $account */
2015-10-25 08:13:06 +01:00
$account = \Auth::user()->account;
2015-10-28 20:22:07 +01:00
$publicId = isset($data['public_id']) ? $data['public_id'] : false;
2017-01-30 20:40:43 +01:00
$isNew = ! $publicId || $publicId == '-1';
2015-10-25 08:13:06 +01:00
if ($invoice) {
// do nothing
2016-08-09 16:14:26 +02:00
$entityType = $invoice->getEntityType();
} elseif ($isNew) {
2015-10-28 20:22:07 +01:00
$entityType = ENTITY_INVOICE;
2015-11-29 11:58:40 +01:00
if (isset($data['is_recurring']) && filter_var($data['is_recurring'], FILTER_VALIDATE_BOOLEAN)) {
2015-10-25 08:13:06 +01:00
$entityType = ENTITY_RECURRING_INVOICE;
2015-11-29 11:58:40 +01:00
} elseif (isset($data['is_quote']) && filter_var($data['is_quote'], FILTER_VALIDATE_BOOLEAN)) {
2015-10-28 20:22:07 +01:00
$entityType = ENTITY_QUOTE;
2015-01-11 12:56:58 +01:00
}
2015-10-25 08:13:06 +01:00
$invoice = $account->createInvoice($entityType, $data['client_id']);
2017-01-30 13:40:07 +01:00
$invoice->invoice_date = date_create()->format('Y-m-d');
2015-11-29 11:58:40 +01:00
if (isset($data['has_tasks']) && filter_var($data['has_tasks'], FILTER_VALIDATE_BOOLEAN)) {
2015-11-02 23:05:28 +01:00
$invoice->has_tasks = true;
}
2016-01-10 11:25:05 +01:00
if (isset($data['has_expenses']) && filter_var($data['has_expenses'], FILTER_VALIDATE_BOOLEAN)) {
$invoice->has_expenses = true;
}
2017-01-30 13:35:04 +01:00
// set the default due date
if ($entityType == ENTITY_INVOICE) {
$client = Client::scope()->whereId($data['client_id'])->first();
$invoice->due_date = $account->defaultDueDate($client);
2017-01-16 12:59:46 +01:00
}
2015-10-28 20:22:07 +01:00
} else {
$invoice = Invoice::scope($publicId)->firstOrFail();
2016-10-10 10:40:04 +02:00
if (Utils::isNinjaDev()) {
\Log::warning('Entity not set in invoice repo save');
}
}
if ($invoice->is_deleted) {
return $invoice;
2015-01-11 12:56:58 +01:00
}
if (isset($data['is_public']) && filter_var($data['is_public'], FILTER_VALIDATE_BOOLEAN)) {
$invoice->is_public = true;
2017-01-30 17:05:31 +01:00
if (! $invoice->isSent()) {
$invoice->invoice_status_id = INVOICE_STATUS_SENT;
}
}
2016-03-31 11:29:01 +02:00
$invoice->fill($data);
if ((isset($data['set_default_terms']) && $data['set_default_terms'])
|| (isset($data['set_default_footer']) && $data['set_default_footer'])) {
if (isset($data['set_default_terms']) && $data['set_default_terms']) {
2015-11-11 13:17:58 +01:00
$account->{"{$invoice->getEntityType()}_terms"} = trim($data['terms']);
}
if (isset($data['set_default_footer']) && $data['set_default_footer']) {
$account->invoice_footer = trim($data['invoice_footer']);
}
$account->save();
2016-05-29 14:34:44 +02:00
}
2017-01-30 20:40:43 +01:00
if (! empty($data['invoice_number']) && ! $invoice->is_recurring) {
$invoice->invoice_number = trim($data['invoice_number']);
}
2015-11-18 15:40:50 +01:00
if (isset($data['discount'])) {
$invoice->discount = round(Utils::parseFloat($data['discount']), 2);
}
if (isset($data['is_amount_discount'])) {
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
}
if (isset($data['invoice_date_sql'])) {
$invoice->invoice_date = $data['invoice_date_sql'];
} elseif (isset($data['invoice_date'])) {
$invoice->invoice_date = Utils::toSqlDate($data['invoice_date']);
}
2015-11-02 23:05:28 +01:00
2017-01-30 17:05:31 +01:00
if (isset($data['invoice_status_id'])) {
if ($data['invoice_status_id'] == 0) {
2016-01-27 11:00:00 +01:00
$data['invoice_status_id'] = INVOICE_STATUS_DRAFT;
}
$invoice->invoice_status_id = $data['invoice_status_id'];
}
2015-01-11 12:56:58 +01:00
if ($invoice->is_recurring) {
2017-01-29 12:30:57 +01:00
if (isset($data['start_date']) && $invoice->start_date && $invoice->start_date != Utils::toSqlDate($data['start_date'])) {
2015-09-20 23:05:02 +02:00
$invoice->last_sent_date = null;
}
2017-01-29 12:30:57 +01:00
$invoice->frequency_id = array_get($data, 'frequency_id', 0);
$invoice->start_date = Utils::toSqlDate(array_get($data, 'start_date'));
$invoice->end_date = Utils::toSqlDate(array_get($data, 'end_date'));
2016-05-09 22:29:02 +02:00
$invoice->client_enable_auto_bill = isset($data['client_enable_auto_bill']) && $data['client_enable_auto_bill'] ? true : false;
2017-01-29 12:30:57 +01:00
$invoice->auto_bill = array_get($data, 'auto_bill_id') ?: array_get($data, 'auto_bill', AUTO_BILL_OFF);
2017-01-30 17:05:31 +01:00
if ($invoice->auto_bill < AUTO_BILL_OFF || $invoice->auto_bill > AUTO_BILL_ALWAYS) {
2016-05-09 22:29:02 +02:00
$invoice->auto_bill = AUTO_BILL_OFF;
}
2016-05-29 14:34:44 +02:00
if (isset($data['recurring_due_date'])) {
$invoice->due_date = $data['recurring_due_date'];
} elseif (isset($data['due_date'])) {
$invoice->due_date = $data['due_date'];
}
2015-01-11 12:56:58 +01:00
} else {
2017-01-30 20:40:43 +01:00
if (! empty($data['due_date']) || ! empty($data['due_date_sql'])) {
2015-11-18 15:40:50 +01:00
$invoice->due_date = isset($data['due_date_sql']) ? $data['due_date_sql'] : Utils::toSqlDate($data['due_date']);
}
2015-01-11 12:56:58 +01:00
$invoice->frequency_id = 0;
$invoice->start_date = null;
$invoice->end_date = null;
}
2016-04-20 10:04:54 +02:00
if (isset($data['terms']) && trim($data['terms'])) {
$invoice->terms = trim($data['terms']);
2016-12-29 21:40:24 +01:00
} elseif ($isNew && ! $invoice->is_recurring && $account->{"{$entityType}_terms"}) {
2016-04-20 10:04:54 +02:00
$invoice->terms = $account->{"{$entityType}_terms"};
} else {
$invoice->terms = '';
}
2016-05-29 14:34:44 +02:00
2017-01-30 20:40:43 +01:00
$invoice->invoice_footer = (isset($data['invoice_footer']) && trim($data['invoice_footer'])) ? trim($data['invoice_footer']) : (! $publicId && $account->invoice_footer ? $account->invoice_footer : '');
2017-01-29 16:32:59 +01:00
$invoice->public_notes = isset($data['public_notes']) ? trim($data['public_notes']) : '';
2015-06-03 19:55:48 +02:00
// process date variables if not recurring
2017-01-30 20:40:43 +01:00
if (! $invoice->is_recurring) {
$invoice->terms = Utils::processVariables($invoice->terms);
$invoice->invoice_footer = Utils::processVariables($invoice->invoice_footer);
$invoice->public_notes = Utils::processVariables($invoice->public_notes);
}
2015-06-03 19:55:48 +02:00
2015-12-08 11:10:20 +01:00
if (isset($data['po_number'])) {
$invoice->po_number = trim($data['po_number']);
}
2016-01-10 11:25:05 +01:00
2015-11-18 15:40:50 +01:00
$invoice->invoice_design_id = isset($data['invoice_design_id']) ? $data['invoice_design_id'] : $account->invoice_design_id;
2015-01-11 12:56:58 +01:00
// provide backwards compatibility
2016-03-31 11:29:01 +02:00
if (isset($data['tax_name']) && isset($data['tax_rate'])) {
2016-05-29 14:34:44 +02:00
$data['tax_name1'] = $data['tax_name'];
$data['tax_rate1'] = $data['tax_rate'];
2015-01-11 12:56:58 +01:00
}
$total = 0;
$itemTax = 0;
2015-01-11 12:56:58 +01:00
foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
2017-01-30 20:40:43 +01:00
if (! $item['cost'] && ! $item['product_key'] && ! $item['notes']) {
2015-01-11 12:56:58 +01:00
continue;
}
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
2015-01-11 12:56:58 +01:00
$lineTotal = $invoiceItemCost * $invoiceItemQty;
$total += round($lineTotal, 2);
}
foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
2016-03-31 11:29:01 +02:00
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
$lineTotal = $invoiceItemCost * $invoiceItemQty;
if ($invoice->discount > 0) {
if ($invoice->is_amount_discount) {
2017-01-30 20:40:43 +01:00
$lineTotal -= round(($lineTotal / $total) * $invoice->discount, 2);
2016-03-31 11:29:01 +02:00
} else {
2017-01-30 20:40:43 +01:00
$lineTotal -= round($lineTotal * ($invoice->discount / 100), 2);
}
2016-03-31 11:29:01 +02:00
}
2015-01-11 12:56:58 +01:00
2016-03-31 11:29:01 +02:00
if (isset($item['tax_rate1']) && Utils::parseFloat($item['tax_rate1']) > 0) {
2016-05-29 14:34:44 +02:00
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate1']);
2016-03-31 11:29:01 +02:00
$itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2);
}
if (isset($item['tax_rate2']) && Utils::parseFloat($item['tax_rate2']) > 0) {
2016-05-29 14:34:44 +02:00
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate2']);
$itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2);
}
2015-01-11 12:56:58 +01:00
}
if ($invoice->discount > 0) {
if ($invoice->is_amount_discount) {
$total -= $invoice->discount;
} else {
2017-01-30 20:40:43 +01:00
$discount = round($total * ($invoice->discount / 100), 2);
2017-01-25 12:03:26 +01:00
$total -= $discount;
2015-01-11 12:56:58 +01:00
}
}
2015-10-28 20:22:07 +01:00
if (isset($data['custom_value1'])) {
$invoice->custom_value1 = round($data['custom_value1'], 2);
if ($isNew) {
$invoice->custom_taxes1 = $account->custom_invoice_taxes1 ?: false;
}
}
if (isset($data['custom_value2'])) {
$invoice->custom_value2 = round($data['custom_value2'], 2);
if ($isNew) {
$invoice->custom_taxes2 = $account->custom_invoice_taxes2 ?: false;
}
}
2015-10-11 16:41:09 +02:00
if (isset($data['custom_text_value1'])) {
$invoice->custom_text_value1 = trim($data['custom_text_value1']);
}
if (isset($data['custom_text_value2'])) {
$invoice->custom_text_value2 = trim($data['custom_text_value2']);
}
2015-01-11 12:56:58 +01:00
// custom fields charged taxes
if ($invoice->custom_value1 && $invoice->custom_taxes1) {
$total += $invoice->custom_value1;
}
if ($invoice->custom_value2 && $invoice->custom_taxes2) {
$total += $invoice->custom_value2;
}
$taxAmount1 = round($total * ($invoice->tax_rate1 ? $invoice->tax_rate1 : 0) / 100, 2);
$taxAmount2 = round($total * ($invoice->tax_rate2 ? $invoice->tax_rate2 : 0) / 100, 2);
2016-05-29 14:34:44 +02:00
$total = round($total + $taxAmount1 + $taxAmount2, 2);
$total += $itemTax;
2015-01-11 12:56:58 +01:00
// custom fields not charged taxes
2017-01-30 20:40:43 +01:00
if ($invoice->custom_value1 && ! $invoice->custom_taxes1) {
2015-01-11 12:56:58 +01:00
$total += $invoice->custom_value1;
}
2017-01-30 20:40:43 +01:00
if ($invoice->custom_value2 && ! $invoice->custom_taxes2) {
2015-01-11 12:56:58 +01:00
$total += $invoice->custom_value2;
}
if ($publicId) {
$invoice->balance = $total - ($invoice->amount - $invoice->balance);
} else {
$invoice->balance = $total;
}
2016-09-21 16:07:01 +02:00
if (isset($data['partial'])) {
2017-01-30 17:05:31 +01:00
$invoice->partial = max(0, min(round(Utils::parseFloat($data['partial']), 2), $invoice->balance));
2016-09-21 16:07:01 +02:00
}
2015-01-11 12:56:58 +01:00
$invoice->amount = $total;
$invoice->save();
if ($publicId) {
$invoice->invoice_items()->forceDelete();
}
2016-05-29 14:34:44 +02:00
2017-01-30 17:05:31 +01:00
if (! empty($data['document_ids'])) {
2016-10-23 10:01:21 +02:00
$document_ids = array_map('intval', $data['document_ids']);
2017-01-30 17:05:31 +01:00
foreach ($document_ids as $document_id) {
2016-10-23 10:01:21 +02:00
$document = Document::scope($document_id)->first();
2017-01-30 17:05:31 +01:00
if ($document && Auth::user()->can('edit', $document)) {
if ($document->invoice_id && $document->invoice_id != $invoice->id) {
2016-10-23 10:01:21 +02:00
// From a clone
$document = $document->cloneDocument();
2017-01-30 20:40:43 +01:00
$document_ids[] = $document->public_id; // Don't remove this document
2016-10-23 10:01:21 +02:00
}
2016-05-29 14:34:44 +02:00
2016-10-23 10:01:21 +02:00
$document->invoice_id = $invoice->id;
$document->expense_id = null;
$document->save();
}
2016-03-23 03:23:45 +01:00
}
2016-05-29 14:34:44 +02:00
2017-01-30 17:05:31 +01:00
if (! $invoice->wasRecentlyCreated) {
foreach ($invoice->documents as $document) {
2017-01-30 20:40:43 +01:00
if (! in_array($document->public_id, $document_ids)) {
2016-10-23 10:01:21 +02:00
// Removed
// Not checking permissions; deleting a document is just editing the invoice
2017-01-30 17:05:31 +01:00
if ($document->invoice_id == $invoice->id) {
2016-10-23 10:01:21 +02:00
// Make sure the document isn't on a clone
$document->delete();
}
2016-06-02 21:03:59 +02:00
}
2016-03-23 03:23:45 +01:00
}
}
}
2015-01-11 12:56:58 +01:00
foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
2016-02-28 12:59:52 +01:00
if (empty($item['cost']) && empty($item['product_key']) && empty($item['notes']) && empty($item['custom_value1']) && empty($item['custom_value2'])) {
2015-01-11 12:56:58 +01:00
continue;
}
$task = false;
2015-05-27 18:52:10 +02:00
if (isset($item['task_public_id']) && $item['task_public_id']) {
$task = Task::scope($item['task_public_id'])->where('invoice_id', '=', null)->firstOrFail();
2017-01-30 17:05:31 +01:00
if (Auth::user()->can('edit', $task)) {
$task->invoice_id = $invoice->id;
$task->client_id = $invoice->client_id;
$task->save();
}
2015-12-13 21:12:54 +01:00
}
2015-08-13 07:45:48 +02:00
$expense = false;
2016-01-10 11:25:05 +01:00
if (isset($item['expense_public_id']) && $item['expense_public_id']) {
$expense = Expense::scope($item['expense_public_id'])->where('invoice_id', '=', null)->firstOrFail();
2017-01-30 17:05:31 +01:00
if (Auth::user()->can('edit', $expense)) {
$expense->invoice_id = $invoice->id;
$expense->client_id = $invoice->client_id;
$expense->save();
}
2016-01-10 11:25:05 +01:00
}
2016-02-19 09:56:48 +01:00
if ($productKey = trim($item['product_key'])) {
if (\Auth::user()->account->update_products && ! $invoice->has_tasks && ! $invoice->has_expenses) {
2015-12-27 12:08:58 +01:00
$product = Product::findProductByKey($productKey);
2017-01-30 20:40:43 +01:00
if (! $product) {
2016-04-27 22:56:14 +02:00
if (Auth::user()->can('create', ENTITY_PRODUCT)) {
$product = Product::createNew();
$product->product_key = trim($item['product_key']);
2017-01-30 17:05:31 +01:00
} else {
$product = null;
}
}
2016-04-27 22:56:14 +02:00
if ($product && (Auth::user()->can('edit', $product))) {
$product->notes = ($task || $expense) ? '' : $item['notes'];
$product->cost = $expense ? 0 : $item['cost'];
$product->save();
2015-12-27 12:08:58 +01:00
}
2015-01-11 12:56:58 +01:00
}
}
$invoiceItem = InvoiceItem::createNew();
$invoiceItem->product_id = isset($product) ? $product->id : null;
2015-11-18 15:40:50 +01:00
$invoiceItem->product_key = isset($item['product_key']) ? (trim($invoice->is_recurring ? $item['product_key'] : Utils::processVariables($item['product_key']))) : '';
$invoiceItem->notes = trim($invoice->is_recurring ? $item['notes'] : Utils::processVariables($item['notes']));
$invoiceItem->cost = Utils::parseFloat($item['cost']);
$invoiceItem->qty = Utils::parseFloat($item['qty']);
2015-01-11 12:56:58 +01:00
2016-02-28 12:59:52 +01:00
if (isset($item['custom_value1'])) {
$invoiceItem->custom_value1 = $item['custom_value1'];
}
if (isset($item['custom_value2'])) {
$invoiceItem->custom_value2 = $item['custom_value2'];
}
2016-03-31 11:29:01 +02:00
// provide backwards compatability
if (isset($item['tax_name']) && isset($item['tax_rate'])) {
2016-05-29 14:34:44 +02:00
$item['tax_name1'] = $item['tax_name'];
$item['tax_rate1'] = $item['tax_rate'];
2015-01-11 12:56:58 +01:00
}
2016-03-31 11:29:01 +02:00
$invoiceItem->fill($item);
2016-05-29 14:34:44 +02:00
2015-01-11 12:56:58 +01:00
$invoice->invoice_items()->save($invoiceItem);
}
return $invoice;
2014-05-20 23:40:09 +02:00
}
2015-01-11 12:56:58 +01:00
/**
* @param Invoice $invoice
2017-01-30 20:40:43 +01:00
* @param null $quotePublicId
*
* @return mixed
*/
public function cloneInvoice(Invoice $invoice, $quotePublicId = null)
2014-05-20 23:40:09 +02:00
{
2015-01-11 12:56:58 +01:00
$invoice->load('invitations', 'invoice_items');
$account = $invoice->account;
$clone = Invoice::createNew($invoice);
$clone->balance = $invoice->amount;
2014-05-20 23:40:09 +02:00
// if the invoice prefix is diff than quote prefix, use the same number for the invoice (if it's available)
$invoiceNumber = false;
if ($account->hasInvoicePrefix() && $account->share_counter) {
2015-01-11 12:56:58 +01:00
$invoiceNumber = $invoice->invoice_number;
2015-06-07 10:05:30 +02:00
if ($account->quote_number_prefix && strpos($invoiceNumber, $account->quote_number_prefix) === 0) {
2015-01-11 12:56:58 +01:00
$invoiceNumber = substr($invoiceNumber, strlen($account->quote_number_prefix));
}
$invoiceNumber = $account->invoice_number_prefix.$invoiceNumber;
2015-12-23 12:49:49 +01:00
if (Invoice::scope(false, $account->id)
->withTrashed()
->whereInvoiceNumber($invoiceNumber)
->first()) {
$invoiceNumber = false;
}
2015-01-11 12:56:58 +01:00
}
2017-01-04 09:11:32 +01:00
$clone->invoice_number = $invoiceNumber ?: $account->getNextNumber($clone);
2017-01-30 13:40:07 +01:00
$clone->invoice_date = date_create()->format('Y-m-d');
2017-01-30 13:35:04 +01:00
$clone->due_date = $account->defaultDueDate($invoice->client);
2014-05-20 23:40:09 +02:00
2015-01-11 12:56:58 +01:00
foreach ([
'client_id',
'discount',
'is_amount_discount',
'po_number',
'is_recurring',
'frequency_id',
'start_date',
'end_date',
'terms',
2015-02-28 22:42:47 +01:00
'invoice_footer',
2015-01-11 12:56:58 +01:00
'public_notes',
'invoice_design_id',
2016-03-31 11:29:01 +02:00
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
2015-01-11 12:56:58 +01:00
'amount',
2016-05-26 16:56:54 +02:00
'invoice_type_id',
2015-01-11 12:56:58 +01:00
'custom_value1',
'custom_value2',
'custom_taxes1',
2015-06-04 22:53:58 +02:00
'custom_taxes2',
2015-10-11 16:41:09 +02:00
'partial',
'custom_text_value1',
'custom_text_value2',
] as $field) {
2015-01-11 12:56:58 +01:00
$clone->$field = $invoice->$field;
}
2014-05-20 23:40:09 +02:00
2015-01-11 12:56:58 +01:00
if ($quotePublicId) {
2016-05-26 16:56:54 +02:00
$clone->invoice_type_id = INVOICE_TYPE_STANDARD;
2015-01-11 12:56:58 +01:00
$clone->quote_id = $quotePublicId;
if ($account->invoice_terms) {
$clone->terms = $account->invoice_terms;
}
if ($account->auto_convert_quote) {
$clone->is_public = true;
$clone->invoice_status_id = INVOICE_STATUS_SENT;
}
2015-01-11 12:56:58 +01:00
}
2014-01-12 19:55:33 +01:00
2015-01-11 12:56:58 +01:00
$clone->save();
2014-01-12 19:55:33 +01:00
2015-01-11 12:56:58 +01:00
if ($quotePublicId) {
$invoice->quote_invoice_id = $clone->public_id;
$invoice->save();
}
foreach ($invoice->invoice_items as $item) {
$cloneItem = InvoiceItem::createNew($invoice);
foreach ([
'product_id',
'product_key',
'notes',
'cost',
'qty',
2016-03-31 11:29:01 +02:00
'tax_name1',
2016-05-29 14:34:44 +02:00
'tax_rate1',
2016-03-31 11:29:01 +02:00
'tax_name2',
2016-05-29 14:34:44 +02:00
'tax_rate2',
'custom_value1',
'custom_value2',
2016-03-31 11:29:01 +02:00
] as $field) {
2015-01-11 12:56:58 +01:00
$cloneItem->$field = $item->$field;
}
$clone->invoice_items()->save($cloneItem);
}
foreach ($invoice->documents as $document) {
2016-05-29 14:34:44 +02:00
$cloneDocument = $document->cloneDocument();
$clone->documents()->save($cloneDocument);
}
2015-01-11 12:56:58 +01:00
foreach ($invoice->invitations as $invitation) {
$cloneInvitation = Invitation::createNew($invoice);
$cloneInvitation->contact_id = $invitation->contact_id;
$cloneInvitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$clone->invitations()->save($cloneInvitation);
}
return $clone;
}
2017-01-11 14:53:26 +01:00
/**
* @param Invoice $invoice
*/
public function emailInvoice(Invoice $invoice)
{
2017-02-02 13:02:50 +01:00
// TODO remove this with Laravel 5.3 (https://github.com/invoiceninja/invoiceninja/issues/1303)
if (config('queue.default') === 'sync') {
app('App\Ninja\Mailers\ContactMailer')->sendInvoice($invoice);
} else {
dispatch(new SendInvoiceEmail($invoice));
}
2017-01-11 14:53:26 +01:00
}
/**
* @param Invoice $invoice
*/
public function markSent(Invoice $invoice)
2015-01-11 12:56:58 +01:00
{
2017-01-13 08:02:22 +01:00
$invoice->markSent();
2016-12-14 20:47:22 +01:00
}
/**
* @param Invoice $invoice
*/
public function markPaid(Invoice $invoice)
{
2017-01-30 17:05:31 +01:00
if (! $invoice->canBePaid()) {
2016-12-14 20:47:22 +01:00
return;
}
$invoice->markSentIfUnsent();
2016-12-14 20:47:22 +01:00
$data = [
'client_id' => $invoice->client_id,
'invoice_id' => $invoice->id,
'amount' => $invoice->balance,
];
return $this->paymentRepo->save($data);
2015-01-11 12:56:58 +01:00
}
2015-07-12 21:43:45 +02:00
/**
* @param $invitationKey
2017-01-30 20:40:43 +01:00
*
* @return Invitation|bool
*/
2015-10-13 09:11:44 +02:00
public function findInvoiceByInvitation($invitationKey)
{
/** @var \App\Models\Invitation $invitation */
2015-10-13 09:11:44 +02:00
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
2015-10-20 19:12:34 +02:00
2017-01-30 20:40:43 +01:00
if (! $invitation) {
2015-10-20 19:12:34 +02:00
return false;
2015-10-13 09:11:44 +02:00
}
$invoice = $invitation->invoice;
2017-01-30 20:40:43 +01:00
if (! $invoice || $invoice->is_deleted) {
2015-10-20 19:12:34 +02:00
return false;
2015-10-13 09:11:44 +02:00
}
2016-03-23 23:40:42 +01:00
$invoice->load('user', 'invoice_items', 'documents', 'invoice_design', 'account.country', 'client.contacts', 'client.country');
2015-10-13 09:11:44 +02:00
$client = $invoice->client;
2017-01-30 20:40:43 +01:00
if (! $client || $client->is_deleted) {
2015-10-20 19:12:34 +02:00
return false;
2015-10-13 09:11:44 +02:00
}
return $invitation;
}
/**
* @param $clientId
2017-01-30 20:49:42 +01:00
* @param mixed $entityType
2017-01-30 20:40:43 +01:00
*
* @return mixed
*/
2016-08-21 17:25:35 +02:00
public function findOpenInvoices($clientId, $entityType = false)
2015-07-12 21:43:45 +02:00
{
2016-08-21 17:25:35 +02:00
$query = Invoice::scope()
2017-01-22 11:09:29 +01:00
->invoiceType(INVOICE_TYPE_STANDARD)
->whereClientId($clientId)
->whereIsRecurring(false)
->whereDeletedAt(null)
->where('balance', '>', 0);
2016-08-21 17:25:35 +02:00
if ($entityType == ENTITY_TASK) {
$query->whereHasTasks(true);
} elseif ($entityType == ENTITY_EXPENSE) {
2017-01-22 11:09:29 +01:00
$query->whereHasTasks(false);
2016-08-21 17:25:35 +02:00
}
return $query->where('invoice_status_id', '<', 5)
2015-07-12 21:43:45 +02:00
->select(['public_id', 'invoice_number'])
->get();
}
/**
* @param Invoice $recurInvoice
2017-01-30 20:40:43 +01:00
*
* @return mixed
*/
public function createRecurringInvoice(Invoice $recurInvoice)
{
$recurInvoice->load('account.timezone', 'invoice_items', 'client', 'user');
if ($recurInvoice->client->deleted_at) {
return false;
}
2017-01-30 20:40:43 +01:00
if (! $recurInvoice->user->confirmed) {
return false;
}
2017-01-30 20:40:43 +01:00
if (! $recurInvoice->shouldSendToday()) {
return false;
}
$invoice = Invoice::createNew($recurInvoice);
$invoice->is_public = true;
2016-06-20 16:14:43 +02:00
$invoice->invoice_type_id = INVOICE_TYPE_STANDARD;
$invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id;
2017-01-04 09:11:32 +01:00
$invoice->invoice_number = $recurInvoice->account->getNextNumber($invoice);
$invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount;
2017-01-30 13:40:07 +01:00
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->discount = $recurInvoice->discount;
$invoice->po_number = $recurInvoice->po_number;
$invoice->public_notes = Utils::processVariables($recurInvoice->public_notes);
$invoice->terms = Utils::processVariables($recurInvoice->terms ?: $recurInvoice->account->invoice_terms);
$invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer ?: $recurInvoice->account->invoice_footer);
2016-03-31 11:29:01 +02:00
$invoice->tax_name1 = $recurInvoice->tax_name1;
$invoice->tax_rate1 = $recurInvoice->tax_rate1;
$invoice->tax_name2 = $recurInvoice->tax_name2;
$invoice->tax_rate2 = $recurInvoice->tax_rate2;
$invoice->invoice_design_id = $recurInvoice->invoice_design_id;
2015-10-28 20:22:07 +01:00
$invoice->custom_value1 = $recurInvoice->custom_value1 ?: 0;
$invoice->custom_value2 = $recurInvoice->custom_value2 ?: 0;
$invoice->custom_taxes1 = $recurInvoice->custom_taxes1 ?: 0;
$invoice->custom_taxes2 = $recurInvoice->custom_taxes2 ?: 0;
$invoice->custom_text_value1 = Utils::processVariables($recurInvoice->custom_text_value1);
$invoice->custom_text_value2 = Utils::processVariables($recurInvoice->custom_text_value2);
$invoice->is_amount_discount = $recurInvoice->is_amount_discount;
$invoice->due_date = $recurInvoice->getDueDate();
$invoice->save();
foreach ($recurInvoice->invoice_items as $recurItem) {
$item = InvoiceItem::createNew($recurItem);
$item->product_id = $recurItem->product_id;
$item->qty = $recurItem->qty;
$item->cost = $recurItem->cost;
$item->notes = Utils::processVariables($recurItem->notes);
$item->product_key = Utils::processVariables($recurItem->product_key);
2016-03-31 11:29:01 +02:00
$item->tax_name1 = $recurItem->tax_name1;
$item->tax_rate1 = $recurItem->tax_rate1;
$item->tax_name2 = $recurItem->tax_name2;
$item->tax_rate2 = $recurItem->tax_rate2;
2016-05-07 15:29:27 +02:00
$item->custom_value1 = Utils::processVariables($recurItem->custom_value1);
$item->custom_value2 = Utils::processVariables($recurItem->custom_value2);
$invoice->invoice_items()->save($item);
}
foreach ($recurInvoice->documents as $recurDocument) {
$document = $recurDocument->cloneDocument();
$invoice->documents()->save($document);
}
foreach ($recurInvoice->invitations as $recurInvitation) {
$invitation = Invitation::createNew($recurInvitation);
$invitation->contact_id = $recurInvitation->contact_id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invoice->invitations()->save($invitation);
}
$recurInvoice->last_sent_date = date('Y-m-d');
$recurInvoice->save();
2017-01-30 20:40:43 +01:00
if ($recurInvoice->getAutoBillEnabled() && ! $recurInvoice->account->auto_bill_on_due_date) {
2016-05-26 21:22:09 +02:00
// autoBillInvoice will check for ACH, so we're not checking here
if ($this->paymentService->autoBillInvoice($invoice)) {
2015-12-16 12:49:26 +01:00
// update the invoice reference to match its actual state
// this is to ensure a 'payment received' email is sent
$invoice->invoice_status_id = INVOICE_STATUS_PAID;
}
}
return $invoice;
}
2015-09-17 21:01:06 +02:00
/**
* @param Account $account
2017-01-30 20:40:43 +01:00
*
* @return mixed
*/
public function findNeedingReminding(Account $account)
2015-09-17 21:01:06 +02:00
{
$dates = [];
2016-01-10 11:25:05 +01:00
2017-01-30 20:40:43 +01:00
for ($i = 1; $i <= 3; $i++) {
if ($date = $account->getReminderDate($i)) {
$field = $account->{"field_reminder{$i}"} == REMINDER_FIELD_DUE_DATE ? 'due_date' : 'invoice_date';
$dates[] = "$field = '$date'";
2015-09-17 21:01:06 +02:00
}
}
$sql = implode(' OR ', $dates);
2016-05-26 16:56:54 +02:00
$invoices = Invoice::invoiceType(INVOICE_TYPE_STANDARD)
->whereAccountId($account->id)
2015-09-17 21:01:06 +02:00
->where('balance', '>', 0)
->where('is_recurring', '=', false)
2017-02-02 07:16:53 +01:00
->whereIsPublic(true)
->whereRaw('('.$sql.')')
2015-09-17 21:01:06 +02:00
->get();
return $invoices;
}
2013-12-25 22:34:42 +01:00
}