diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 735b080ee8d6..6bc4b700896c 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -262,7 +262,7 @@ class PaymentController extends BaseController $card = new CreditCard($data); return [ - 'amount' => ($invoice->partial ? $invoice->partial : $invoice->balance), + 'amount' => $invoice->getRequestedAmount(), 'card' => $card, 'currency' => $currencyCode, 'returnUrl' => URL::to('complete'), @@ -304,7 +304,7 @@ class PaymentController extends BaseController $data = [ 'showBreadcrumbs' => false, 'url' => 'payment/'.$invitationKey, - 'amount' => ($invoice->partial ? $invoice->partial : $invoice->balance), + 'amount' => $invoice->getRequestedAmount(), 'invoiceNumber' => $invoice->invoice_number, 'client' => $client, 'contact' => $invitation->contact, @@ -603,7 +603,7 @@ class PaymentController extends BaseController $payment->invitation_id = $invitation->id; $payment->account_gateway_id = $accountGateway->id; $payment->invoice_id = $invoice->id; - $payment->amount = $invoice->partial ? $invoice->partial : $invoice->balance; + $payment->amount = $invoice->getRequestedAmount(); $payment->client_id = $invoice->client_id; $payment->contact_id = $invitation->contact_id; $payment->transaction_reference = $ref; diff --git a/app/Models/Account.php b/app/Models/Account.php index 42c0ec54d63a..4dadd3d521c3 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -228,6 +228,7 @@ class Account extends Eloquent 'subtotal', 'paid_to_date', 'balance_due', + 'amount_due', 'terms', 'your_invoice', 'quote', diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 25c38f5d21cc..c8ea1c62b811 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -187,7 +187,7 @@ class Activity extends Eloquent $diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount')); $fieldChanged = false; - foreach (['invoice_number', 'po_number', 'invoice_date', 'due_date', 'terms', 'public_notes', 'invoice_footer'] as $field) { + foreach (['invoice_number', 'po_number', 'invoice_date', 'due_date', 'terms', 'public_notes', 'invoice_footer', 'partial'] as $field) { if ($invoice->$field != $invoice->getOriginal($field)) { $fieldChanged = true; break; diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php index 58aedfb5cd5b..d55a16d25a99 100644 --- a/app/Models/Invitation.php +++ b/app/Models/Invitation.php @@ -9,7 +9,7 @@ class Invitation extends EntityModel public function invoice() { - return $this->belongsTo('App\Models\Invoice'); + return $this->belongsTo('App\Models\Invoice')->withTrashed(); } public function contact() diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index d5a54b7cfb11..e457732dcfff 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -72,6 +72,11 @@ class Invoice extends EntityModel return $this->invoice_status_id >= INVOICE_STATUS_PAID; } + public function getRequestedAmount() + { + return $this->partial > 0 ? $this->partial : $this->balance; + } + public function hidePrivateFields() { $this->setVisible([ diff --git a/app/Ninja/Mailers/ContactMailer.php b/app/Ninja/Mailers/ContactMailer.php index 98acdc810ead..8ac55cb281e7 100644 --- a/app/Ninja/Mailers/ContactMailer.php +++ b/app/Ninja/Mailers/ContactMailer.php @@ -19,7 +19,7 @@ class ContactMailer extends Mailer $subject = trans("texts.{$entityType}_subject", ['invoice' => $invoice->invoice_number, 'account' => $invoice->account->getDisplayName()]); $accountName = $invoice->account->getDisplayName(); $emailTemplate = $invoice->account->getEmailTemplate($entityType); - $invoiceAmount = Utils::formatMoney($invoice->amount, $invoice->client->currency_id); + $invoiceAmount = Utils::formatMoney($invoice->getRequestedAmount(), $invoice->client->currency_id); foreach ($invoice->invitations as $invitation) { if (!$invitation->user || !$invitation->user->email) { diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 658109520f09..af0857989321 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -19,7 +19,7 @@ class InvoiceRepository ->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'); + ->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); @@ -86,7 +86,7 @@ class InvoiceRepository ->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'); + ->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); }) @@ -94,7 +94,11 @@ class InvoiceRepository ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); }); if ($entityType == ENTITY_INVOICE) { - $table->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); }); + $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); }) @@ -122,7 +126,11 @@ class InvoiceRepository ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); }); if ($entityType == ENTITY_INVOICE) { - $table->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); }); + $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); }) diff --git a/public/js/built.js b/public/js/built.js index 61231868d7ed..12adac8cfe27 100644 --- a/public/js/built.js +++ b/public/js/built.js @@ -31601,6 +31601,12 @@ function GetPdf(invoice, javascript){ //set default style for report doc.setFont('Helvetica',''); + // For partial payments show "Amount Due" rather than "Balance Due" + if (!invoiceLabels.balance_due_orig) { + invoiceLabels.balance_due_orig = invoiceLabels.balance_due; + } + invoiceLabels.balance_due = NINJA.parseFloat(invoice.partial) ? invoiceLabels.amount_due : invoiceLabels.balance_due_orig; + eval(javascript); // add footer @@ -32233,13 +32239,20 @@ function displayInvoice(doc, invoice, x, y, layout, rightAlignX) { } function getInvoiceDetails(invoice) { - return [ + var fields = [ {'invoice_number': invoice.invoice_number}, {'po_number': invoice.po_number}, {'invoice_date': invoice.invoice_date}, {'due_date': invoice.due_date}, - {'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)}, ]; + + if (NINJA.parseFloat(invoice.partial)) { + fields.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)}); + } + + fields.push({'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)}) + + return fields; } function getInvoiceDetailsHeight(invoice, layout) { @@ -32294,6 +32307,10 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX) data.push({'paid_to_date': formatMoney(paid, invoice.client.currency_id)}); } + if (NINJA.parseFloat(invoice.partial) && invoice.total_amount != invoice.subtotal_amount) { + data.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)}); + } + var options = { hasheader: true, rightAlignX: 550, @@ -32490,15 +32507,17 @@ function calculateAmounts(invoice) { total += roundToTwo(invoice.custom_value2); } - if (NINJA.parseFloat(invoice.partial)) { - invoice.balance_amount = roundToTwo(invoice.partial); - } else { - invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); - } + invoice.total_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); invoice.discount_amount = discount; invoice.tax_amount = tax; invoice.has_taxes = hasTaxes; + if (NINJA.parseFloat(invoice.partial)) { + invoice.balance_amount = roundToTwo(invoice.partial); + } else { + invoice.balance_amount = invoice.total_amount; + } + return invoice; } diff --git a/public/js/script.js b/public/js/script.js index b20175cdce0c..ee7ec9bc5012 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -78,6 +78,12 @@ function GetPdf(invoice, javascript){ //set default style for report doc.setFont('Helvetica',''); + // For partial payments show "Amount Due" rather than "Balance Due" + if (!invoiceLabels.balance_due_orig) { + invoiceLabels.balance_due_orig = invoiceLabels.balance_due; + } + invoiceLabels.balance_due = NINJA.parseFloat(invoice.partial) ? invoiceLabels.amount_due : invoiceLabels.balance_due_orig; + eval(javascript); // add footer @@ -710,13 +716,20 @@ function displayInvoice(doc, invoice, x, y, layout, rightAlignX) { } function getInvoiceDetails(invoice) { - return [ + var fields = [ {'invoice_number': invoice.invoice_number}, {'po_number': invoice.po_number}, {'invoice_date': invoice.invoice_date}, {'due_date': invoice.due_date}, - {'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)}, ]; + + if (NINJA.parseFloat(invoice.partial)) { + fields.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)}); + } + + fields.push({'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)}) + + return fields; } function getInvoiceDetailsHeight(invoice, layout) { @@ -771,6 +784,10 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX) data.push({'paid_to_date': formatMoney(paid, invoice.client.currency_id)}); } + if (NINJA.parseFloat(invoice.partial) && invoice.total_amount != invoice.subtotal_amount) { + data.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)}); + } + var options = { hasheader: true, rightAlignX: 550, @@ -967,15 +984,17 @@ function calculateAmounts(invoice) { total += roundToTwo(invoice.custom_value2); } - if (NINJA.parseFloat(invoice.partial)) { - invoice.balance_amount = roundToTwo(invoice.partial); - } else { - invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); - } + invoice.total_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); invoice.discount_amount = discount; invoice.tax_amount = tax; invoice.has_taxes = hasTaxes; + if (NINJA.parseFloat(invoice.partial)) { + invoice.balance_amount = roundToTwo(invoice.partial); + } else { + invoice.balance_amount = invoice.total_amount; + } + return invoice; } diff --git a/resources/lang/da/texts.php b/resources/lang/da/texts.php index 6907426d44ba..59c05e563509 100644 --- a/resources/lang/da/texts.php +++ b/resources/lang/da/texts.php @@ -591,5 +591,8 @@ return array( 'payment_type_credit_card' => 'Credit card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', ); diff --git a/resources/lang/de/texts.php b/resources/lang/de/texts.php index 9cb8632a7a8d..646ebf4af9e9 100644 --- a/resources/lang/de/texts.php +++ b/resources/lang/de/texts.php @@ -582,6 +582,9 @@ return array( 'payment_type_credit_card' => 'Credit card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', ); \ No newline at end of file diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 96bf0896684e..3ab0a35d27ee 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -591,5 +591,7 @@ return array( 'payment_type_bitcoin' => 'Bitcoin', 'knowledge_base' => 'Knowledge Base', 'partial' => 'Partial', - + 'partial_remaining' => ':partial of :balance', + + ); diff --git a/resources/lang/es/texts.php b/resources/lang/es/texts.php index da6b5c39de9c..e00057bcbb98 100644 --- a/resources/lang/es/texts.php +++ b/resources/lang/es/texts.php @@ -561,6 +561,9 @@ return array( 'payment_type_credit_card' => 'Credit card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', diff --git a/resources/lang/es_ES/texts.php b/resources/lang/es_ES/texts.php index 1b8467fee0be..0fbf4030239e 100644 --- a/resources/lang/es_ES/texts.php +++ b/resources/lang/es_ES/texts.php @@ -590,6 +590,9 @@ return array( 'payment_type_credit_card' => 'Credit card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', ); \ No newline at end of file diff --git a/resources/lang/fr/texts.php b/resources/lang/fr/texts.php index df5eeacb6f24..13c9ccb88234 100644 --- a/resources/lang/fr/texts.php +++ b/resources/lang/fr/texts.php @@ -582,6 +582,9 @@ return array( 'payment_type_credit_card' => 'Credit card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', ); \ No newline at end of file diff --git a/resources/lang/it/texts.php b/resources/lang/it/texts.php index d1dfb403009f..ad40d5014e68 100644 --- a/resources/lang/it/texts.php +++ b/resources/lang/it/texts.php @@ -584,6 +584,9 @@ return array( 'payment_type_credit_card' => 'Credit card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', ); diff --git a/resources/lang/lt/texts.php b/resources/lang/lt/texts.php index dcf32ef564d6..3bca31dd23ea 100644 --- a/resources/lang/lt/texts.php +++ b/resources/lang/lt/texts.php @@ -592,6 +592,9 @@ return array( 'payment_type_credit_card' => 'Credit card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', diff --git a/resources/lang/nb_NO/texts.php b/resources/lang/nb_NO/texts.php index e707c3833612..20cbada5e501 100644 --- a/resources/lang/nb_NO/texts.php +++ b/resources/lang/nb_NO/texts.php @@ -590,6 +590,9 @@ return array( 'payment_type_credit_card' => 'Credit card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', ); \ No newline at end of file diff --git a/resources/lang/nl/texts.php b/resources/lang/nl/texts.php index eb8dfde7acb7..0c9d11a851b2 100644 --- a/resources/lang/nl/texts.php +++ b/resources/lang/nl/texts.php @@ -585,6 +585,9 @@ return array( 'payment_type_credit_card' => 'Credit card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', diff --git a/resources/lang/pt_BR/texts.php b/resources/lang/pt_BR/texts.php index 355f2766e694..ab1709120fb9 100644 --- a/resources/lang/pt_BR/texts.php +++ b/resources/lang/pt_BR/texts.php @@ -585,6 +585,9 @@ return array( 'payment_type_credit_card' => 'Credit card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', ); diff --git a/resources/lang/sv/texts.php b/resources/lang/sv/texts.php index 617af813dda2..936fc232bfb9 100644 --- a/resources/lang/sv/texts.php +++ b/resources/lang/sv/texts.php @@ -588,6 +588,9 @@ return array( 'payment_type_credit_card' => 'Credit card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'knowledge_base' => 'Knowledge Base', + 'partial' => 'Partial', + 'partial_remaining' => ':partial of :balance', ); diff --git a/resources/views/clients/show.blade.php b/resources/views/clients/show.blade.php index 7a85b5434d62..ea69903283c0 100644 --- a/resources/views/clients/show.blade.php +++ b/resources/views/clients/show.blade.php @@ -11,7 +11,7 @@ @if ($gatewayLink) - {!! Button::link($gatewayLink, trans('texts.view_in_stripe'), ['target' => '_blank']) !!} + {!! Button::normal(trans('texts.view_in_stripe'))->asLinkTo($gatewayLink)->withAttributes(['target' => '_blank']) !!} @endif @if ($client->trashed()) diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 80d2486a23c5..6e68b18e9b69 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -78,7 +78,7 @@ ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('') !!} {!! Former::text('partial')->data_bind("value: partial, valueUpdate: 'afterkeydown', enable: is_partial") - ->addGroupClass('partial')->append(Former::checkbox('is_partial')->raw() + ->onchange('onPartialChange()')->addGroupClass('partial')->append(Former::checkbox('is_partial')->raw() ->data_bind('checked: is_partial')->onclick('onPartialEnabled()') . ' ' . (trans('texts.enable'))) !!} @if ($entityType == ENTITY_INVOICE) @@ -1606,15 +1606,23 @@ } } + function onPartialChange() + { + var val = NINJA.parseFloat($('#partial').val()); + val = Math.max(Math.min(val, model.invoice().totals.rawTotal()), 0); + $('#partial').val(val); + } + function onPartialEnabled() { - if ($('#is_partial').prop('checked')) { - model.invoice().partial(model.invoice().totals.rawTotal() || ''); - } else { - model.invoice().partial(''); - } - + model.invoice().partial(''); refreshPDF(); + + if ($('#is_partial').prop('checked')) { + setTimeout(function() { + $('#partial').focus(); + }, 1); + } } function onRecurringEnabled()