paymentService = $paymentService;
}
public function getInvoices($accountId, $clientPublicId = false, $entityType = ENTITY_INVOICE, $filter = false)
{
$query = \DB::table('invoices')
->join('clients', 'clients.id', '=', 'invoices.client_id')
->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', $accountId)
->where('clients.deleted_at', '=', null)
->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
->where('contacts.is_primary', '=', true)
->select('clients.public_id as client_public_id', 'invoice_number', 'invoice_status_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'quote_id', 'quote_invoice_id', 'invoices.deleted_at', 'invoices.is_deleted', 'invoices.partial');
if (!\Session::get('show_trash:'.$entityType)) {
$query->where('invoices.deleted_at', '=', null);
}
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
}
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%')
->orWhere('invoice_statuses.name', 'like', '%'.$filter.'%')
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
->orWhere('contacts.email', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function getRecurringInvoices($accountId, $clientPublicId = false, $filter = false)
{
$query = \DB::table('invoices')
->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)
->where('invoices.is_quote', '=', false)
->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', true)
->where('contacts.is_primary', '=', true)
->where('clients.deleted_at', '=', null)
->select('clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'frequencies.name as frequency', 'start_date', 'end_date', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'invoices.deleted_at', 'invoices.is_deleted');
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
}
if (!\Session::get('show_trash:invoice')) {
$query->where('invoices.deleted_at', '=', null);
}
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function getClientDatatable($contactId, $entityType, $search)
{
$query = \DB::table('invitations')
->join('invoices', 'invoices.id', '=', 'invitations.invoice_id')
->join('clients', 'clients.id', '=', 'invoices.client_id')
->where('invitations.contact_id', '=', $contactId)
->where('invitations.deleted_at', '=', null)
->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE)
->where('invoices.is_deleted', '=', false)
->where('clients.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
->select('invitation_key', 'invoice_number', 'invoice_date', 'invoices.balance as balance', 'due_date', 'clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'start_date', 'end_date', 'clients.currency_id', 'invoices.partial');
$table = \Datatable::query($query)
->addColumn('invoice_number', function ($model) use ($entityType) { return link_to('/view/'.$model->invitation_key, $model->invoice_number); })
->addColumn('invoice_date', function ($model) { return Utils::fromSqlDate($model->invoice_date); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); });
if ($entityType == ENTITY_INVOICE) {
$table->addColumn('balance', function ($model) {
return $model->partial > 0 ?
trans('texts.partial_remaining', ['partial' => Utils::formatMoney($model->partial, $model->currency_id), 'balance' => Utils::formatMoney($model->balance, $model->currency_id)]) :
Utils::formatMoney($model->balance, $model->currency_id);
});
}
return $table->addColumn('due_date', function ($model) { return Utils::fromSqlDate($model->due_date); })
->make();
}
public function getDatatable($accountId, $clientPublicId = null, $entityType, $search)
{
$query = $this->getInvoices($accountId, $clientPublicId, $entityType, $search)
->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE ? true : false);
$table = \Datatable::query($query);
if (!$clientPublicId) {
$table->addColumn('checkbox', function ($model) { return ''; });
}
$table->addColumn("invoice_number", function ($model) use ($entityType) { return link_to("{$entityType}s/".$model->public_id.'/edit', $model->invoice_number, ['class' => Utils::getEntityRowClass($model)]); });
if (!$clientPublicId) {
$table->addColumn('client_name', function ($model) { return link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)); });
}
$table->addColumn("invoice_date", function ($model) { return Utils::fromSqlDate($model->invoice_date); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); });
if ($entityType == ENTITY_INVOICE) {
$table->addColumn('balance', function ($model) {
return $model->partial > 0 ?
trans('texts.partial_remaining', ['partial' => Utils::formatMoney($model->partial, $model->currency_id), 'balance' => Utils::formatMoney($model->balance, $model->currency_id)]) :
Utils::formatMoney($model->balance, $model->currency_id);
});
}
return $table->addColumn('due_date', function ($model) { return Utils::fromSqlDate($model->due_date); })
->addColumn('invoice_status_name', function ($model) { return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted')) : self::getStatusLabel($model->invoice_status_id, $model->invoice_status_name); })
->addColumn('dropdown', function ($model) use ($entityType) {
if ($model->is_deleted) {
return '
';
}
$str = '
';
})
->make();
}
private function getStatusLabel($statusId, $statusName) {
$label = trans("texts.status_" . strtolower($statusName));
$class = 'default';
switch ($statusId) {
case INVOICE_STATUS_SENT:
$class = 'info';
break;
case INVOICE_STATUS_VIEWED:
$class = 'warning';
break;
case INVOICE_STATUS_PARTIAL:
$class = 'primary';
break;
case INVOICE_STATUS_PAID:
$class = 'success';
break;
}
return "$label
";
}
public function getErrors($input)
{
$contact = (array) $input->client->contacts[0];
$rules = [
'email' => 'email|required_without:first_name',
'first_name' => 'required_without:email',
];
$validator = \Validator::make($contact, $rules);
if ($validator->fails()) {
return $validator;
}
$invoice = (array) $input;
$invoiceId = isset($invoice['public_id']) && $invoice['public_id'] ? Invoice::getPrivateId($invoice['public_id']) : null;
$rules = [
'invoice_number' => 'required|unique:invoices,invoice_number,'.$invoiceId.',id,account_id,'.\Auth::user()->account_id,
'discount' => 'positive',
];
if ($invoice['is_recurring'] && $invoice['start_date'] && $invoice['end_date']) {
$rules['end_date'] = 'after:'.$invoice['start_date'];
}
$validator = \Validator::make($invoice, $rules);
if ($validator->fails()) {
return $validator;
}
return false;
}
public function save($publicId, $data, $entityType)
{
if ($publicId) {
$invoice = Invoice::scope($publicId)->firstOrFail();
} else {
$invoice = Invoice::createNew();
if ($entityType == ENTITY_QUOTE) {
$invoice->is_quote = true;
}
}
$account = \Auth::user()->account;
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']) {
$account->invoice_terms = trim($data['terms']);
}
if (isset($data['set_default_footer']) && $data['set_default_footer']) {
$account->invoice_footer = trim($data['invoice_footer']);
}
$account->save();
}
if (isset($data['invoice_number'])) {
$invoice->invoice_number = trim($data['invoice_number']);
}
$invoice->discount = round(Utils::parseFloat($data['discount']), 2);
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
$invoice->partial = round(Utils::parseFloat($data['partial']), 2);
$invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']);
$invoice->has_tasks = isset($data['has_tasks']) ? $data['has_tasks'] : false;
if (!$publicId) {
$invoice->client_id = $data['client_id'];
$invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false;
}
if ($invoice->is_recurring) {
if ($invoice->start_date && $invoice->start_date != Utils::toSqlDate($data['start_date'])) {
$invoice->last_sent_date = null;
}
$invoice->frequency_id = $data['frequency_id'] ? $data['frequency_id'] : 0;
$invoice->start_date = Utils::toSqlDate($data['start_date']);
$invoice->end_date = Utils::toSqlDate($data['end_date']);
$invoice->due_date = null;
$invoice->auto_bill = isset($data['auto_bill']) && $data['auto_bill'] ? true : false;
} else {
$invoice->due_date = isset($data['due_date_sql']) ? $data['due_date_sql'] : Utils::toSqlDate($data['due_date']);
$invoice->frequency_id = 0;
$invoice->start_date = null;
$invoice->end_date = null;
}
$invoice->terms = trim($data['terms']) ? trim($data['terms']) : (!$publicId && $account->invoice_terms ? $account->invoice_terms : '');
$invoice->invoice_footer = trim($data['invoice_footer']) ? trim($data['invoice_footer']) : (!$publicId && $account->invoice_footer ? $account->invoice_footer : '');
$invoice->public_notes = trim($data['public_notes']);
// process date variables
$invoice->terms = Utils::processVariables($invoice->terms);
$invoice->invoice_footer = Utils::processVariables($invoice->invoice_footer);
$invoice->public_notes = Utils::processVariables($invoice->public_notes);
$invoice->po_number = trim($data['po_number']);
$invoice->invoice_design_id = $data['invoice_design_id'];
if (isset($data['tax_name']) && isset($data['tax_rate']) && $data['tax_name']) {
$invoice->tax_rate = Utils::parseFloat($data['tax_rate']);
$invoice->tax_name = trim($data['tax_name']);
} else {
$invoice->tax_rate = 0;
$invoice->tax_name = '';
}
$total = 0;
$itemTax = 0;
foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
if (!$item['cost'] && !$item['product_key'] && !$item['notes']) {
continue;
}
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
$lineTotal = $invoiceItemCost * $invoiceItemQty;
$total += round($lineTotal, 2);
}
foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
if (isset($item['tax_rate']) && Utils::parseFloat($item['tax_rate']) > 0) {
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate']);
$lineTotal = $invoiceItemCost * $invoiceItemQty;
if ($invoice->discount > 0) {
if ($invoice->is_amount_discount) {
$lineTotal -= round(($lineTotal/$total) * $invoice->discount, 2);
} else {
$lineTotal -= round($lineTotal * ($invoice->discount/100), 2);
}
}
$itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2);
}
}
if ($invoice->discount > 0) {
if ($invoice->is_amount_discount) {
$total -= $invoice->discount;
} else {
$total *= (100 - $invoice->discount) / 100;
}
}
$invoice->custom_value1 = round($data['custom_value1'], 2);
$invoice->custom_value2 = round($data['custom_value2'], 2);
$invoice->custom_taxes1 = $data['custom_taxes1'] ? true : false;
$invoice->custom_taxes2 = $data['custom_taxes2'] ? true : false;
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']);
}
// 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;
}
$total += $total * $invoice->tax_rate / 100;
$total = round($total, 2);
$total += $itemTax;
// custom fields not charged taxes
if ($invoice->custom_value1 && !$invoice->custom_taxes1) {
$total += $invoice->custom_value1;
}
if ($invoice->custom_value2 && !$invoice->custom_taxes2) {
$total += $invoice->custom_value2;
}
if ($publicId) {
$invoice->balance = $total - ($invoice->amount - $invoice->balance);
} else {
$invoice->balance = $total;
}
$invoice->amount = $total;
$invoice->save();
if ($publicId) {
$invoice->invoice_items()->forceDelete();
}
foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
if (!$item['cost'] && !$item['product_key'] && !$item['notes']) {
continue;
}
if (isset($item['task_public_id']) && $item['task_public_id']) {
$task = Task::scope($item['task_public_id'])->where('invoice_id', '=', null)->firstOrFail();
$task->invoice_id = $invoice->id;
$task->client_id = $invoice->client_id;
$task->save();
} else if ($item['product_key'] && !$invoice->has_tasks) {
$product = Product::findProductByKey(trim($item['product_key']));
if (\Auth::user()->account->update_products) {
if (!$product) {
$product = Product::createNew();
$product->product_key = trim($item['product_key']);
}
$product->notes = $item['notes'];
$product->cost = $item['cost'];
$product->save();
}
}
$invoiceItem = InvoiceItem::createNew();
$invoiceItem->product_id = isset($product) ? $product->id : null;
$invoiceItem->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']);
$invoiceItem->tax_rate = 0;
if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) {
$invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']);
$invoiceItem['tax_name'] = trim($item['tax_name']);
}
$invoice->invoice_items()->save($invoiceItem);
}
return $invoice;
}
public function cloneInvoice($invoice, $quotePublicId = null)
{
$invoice->load('invitations', 'invoice_items');
$account = $invoice->account;
$clone = Invoice::createNew($invoice);
$clone->balance = $invoice->amount;
// if the invoice prefix is diff than quote prefix, use the same number for the invoice
if (($account->invoice_number_prefix || $account->quote_number_prefix)
&& $account->invoice_number_prefix != $account->quote_number_prefix
&& $account->share_counter) {
$invoiceNumber = $invoice->invoice_number;
if ($account->quote_number_prefix && strpos($invoiceNumber, $account->quote_number_prefix) === 0) {
$invoiceNumber = substr($invoiceNumber, strlen($account->quote_number_prefix));
}
$clone->invoice_number = $account->invoice_number_prefix.$invoiceNumber;
} else {
$clone->invoice_number = $account->getNextInvoiceNumber();
}
foreach ([
'client_id',
'discount',
'is_amount_discount',
'invoice_date',
'po_number',
'due_date',
'is_recurring',
'frequency_id',
'start_date',
'end_date',
'terms',
'invoice_footer',
'public_notes',
'invoice_design_id',
'tax_name',
'tax_rate',
'amount',
'is_quote',
'custom_value1',
'custom_value2',
'custom_taxes1',
'custom_taxes2',
'partial',
'custom_text_value1',
'custom_text_value2'] as $field) {
$clone->$field = $invoice->$field;
}
if ($quotePublicId) {
$clone->is_quote = false;
$clone->quote_id = $quotePublicId;
}
$clone->save();
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',
'tax_name',
'tax_rate', ] as $field) {
$cloneItem->$field = $item->$field;
}
$clone->invoice_items()->save($cloneItem);
}
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;
}
public function bulk($ids, $action, $statusId = false)
{
if (!$ids) {
return 0;
}
$invoices = Invoice::withTrashed()->scope($ids)->get();
foreach ($invoices as $invoice) {
if ($action == 'mark') {
$invoice->invoice_status_id = $statusId;
$invoice->save();
} elseif ($action == 'restore') {
$invoice->restore();
} else {
if ($action == 'delete') {
$invoice->is_deleted = true;
$invoice->save();
}
$invoice->delete();
}
}
return count($invoices);
}
public function findOpenInvoices($clientId)
{
return Invoice::scope()
->whereClientId($clientId)
->whereIsQuote(false)
->whereIsRecurring(false)
->whereDeletedAt(null)
->whereHasTasks(true)
->where('invoice_status_id', '<', 5)
->select(['public_id', 'invoice_number'])
->get();
}
public function createRecurringInvoice($recurInvoice)
{
$recurInvoice->load('account.timezone', 'invoice_items', 'client', 'user');
if ($recurInvoice->client->deleted_at) {
return false;
}
if (!$recurInvoice->user->confirmed) {
return false;
}
if (!$recurInvoice->shouldSendToday()) {
return false;
}
$invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber(false, 'R');
$invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount;
$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);
$invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer);
$invoice->tax_name = $recurInvoice->tax_name;
$invoice->tax_rate = $recurInvoice->tax_rate;
$invoice->invoice_design_id = $recurInvoice->invoice_design_id;
$invoice->custom_value1 = $recurInvoice->custom_value1;
$invoice->custom_value2 = $recurInvoice->custom_value2;
$invoice->custom_taxes1 = $recurInvoice->custom_taxes1;
$invoice->custom_taxes2 = $recurInvoice->custom_taxes2;
$invoice->custom_text_value1 = $recurInvoice->custom_text_value1;
$invoice->custom_text_value2 = $recurInvoice->custom_text_value2;
$invoice->is_amount_discount = $recurInvoice->is_amount_discount;
if ($invoice->client->payment_terms != 0) {
$days = $invoice->client->payment_terms;
if ($days == -1) {
$days = 0;
}
$invoice->due_date = date_create()->modify($days.' day')->format('Y-m-d');
}
$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);
$item->tax_name = $recurItem->tax_name;
$item->tax_rate = $recurItem->tax_rate;
$invoice->invoice_items()->save($item);
}
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();
if ($recurInvoice->auto_bill) {
if ($this->paymentService->autoBillInvoice($invoice)) {
$invoice->invoice_status_id = INVOICE_STATUS_PAID;
}
}
if ($recurInvoice->account->pdf_email_attachment) {
$invoice->updateCachedPDF();
}
return $invoice;
}
public function findNeedingReminding($account)
{
$dates = [];
for ($i=1; $i<=3; $i++) {
$field = "enable_reminder{$i}";
if (!$account->$field) {
continue;
}
$field = "num_days_reminder{$i}";
$dates[] = "due_date = '" . date('Y-m-d', strtotime("- {$account->$field} days")) . "'";
}
$sql = implode(' OR ', $dates);
$invoices = Invoice::whereAccountId($account->id)
->where('balance', '>', 0)
->whereRaw('(' . $sql . ')')
->get();
return $invoices;
}
}