diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index d0d0fcfad05f..7e900bb53b81 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -99,6 +99,7 @@ class InvoiceController extends BaseController if ($clone) { $invoice->id = $invoice->public_id = null; + $invoice->is_public = false; $invoice->invoice_number = $account->getNextInvoiceNumber($invoice); $invoice->balance = $invoice->amount; $invoice->invoice_status_id = 0; @@ -129,10 +130,6 @@ class InvoiceController extends BaseController DropdownButton::DIVIDER ]; - if ($invoice->invoice_status_id < INVOICE_STATUS_SENT && !$invoice->is_recurring) { - $actions[] = ['url' => 'javascript:onMarkClick()', 'label' => trans('texts.mark_sent')]; - } - if ($entityType == ENTITY_QUOTE) { if ($invoice->quote_invoice_id) { $actions[] = ['url' => URL::to("invoices/{$invoice->quote_invoice_id}/edit"), 'label' => trans('texts.view_invoice')]; @@ -194,7 +191,7 @@ class InvoiceController extends BaseController } // Set the invitation data on the client's contacts - if (!$clone) { + if ($invoice->is_public && ! $clone) { $clients = $data['clients']; foreach ($clients as $client) { if ($client->id != $invoice->client->id) { diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php index fd0fc7d720da..7e362b7ad14f 100644 --- a/app/Models/Invitation.php +++ b/app/Models/Invitation.php @@ -97,8 +97,8 @@ class Invitation extends EntityModel if ($this->$field && $this->field != '0000-00-00 00:00:00') { $date = Utils::dateToString($this->$field); $hasValue = true; - } - $parts[] = trans('texts.invitation_status_' . $status) . ': ' . $date; + $parts[] = trans('texts.invitation_status_' . $status) . ': ' . $date; + } } return $hasValue ? implode($parts, '
') : false; diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 41d7914e9a30..98e7cb6f44b8 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -153,7 +153,7 @@ class Invoice extends EntityModel implements BalanceAffecting */ public function affectsBalance() { - return $this->isType(INVOICE_TYPE_STANDARD) && !$this->is_recurring; + return $this->isType(INVOICE_TYPE_STANDARD) && !$this->is_recurring && $this->is_public; } /** @@ -161,7 +161,7 @@ class Invoice extends EntityModel implements BalanceAffecting */ public function getAdjustment() { - if (!$this->affectsBalance()) { + if ( ! $this->affectsBalance()) { return 0; } @@ -173,6 +173,11 @@ class Invoice extends EntityModel implements BalanceAffecting */ private function getRawAdjustment() { + // if we've just made the invoice public then apply the full amount + if ($this->is_public && ! $this->getOriginal('is_public')) { + return $this->amount; + } + return floatval($this->amount) - floatval($this->getOriginal('amount')); } @@ -410,6 +415,8 @@ class Invoice extends EntityModel implements BalanceAffecting */ public function markInvitationsSent($notify = false) { + $this->load('invitations'); + foreach ($this->invitations as $invitation) { $this->markInvitationSent($invitation, false, $notify); } diff --git a/app/Ninja/Repositories/DocumentRepository.php b/app/Ninja/Repositories/DocumentRepository.php index e0308a5ef9d6..2a950a7726e8 100644 --- a/app/Ninja/Repositories/DocumentRepository.php +++ b/app/Ninja/Repositories/DocumentRepository.php @@ -190,6 +190,7 @@ class DocumentRepository extends BaseRepository ->where('invoices.is_deleted', '=', false) ->where('clients.deleted_at', '=', null) ->where('invoices.is_recurring', '=', false) + ->where('invoices.is_public', '=', true) // TODO: This needs to be a setting to also hide the activity on the dashboard page //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) ->select( diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index cfe54304e89d..68a1ac9bbbad 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -75,7 +75,8 @@ class InvoiceRepository extends BaseRepository 'invoices.deleted_at', 'invoices.is_deleted', 'invoices.partial', - 'invoices.user_id' + 'invoices.user_id', + 'invoices.is_public' ); $this->applyFilters($query, $entityType, ENTITY_INVOICE); @@ -227,6 +228,7 @@ class InvoiceRepository extends BaseRepository ->where('contacts.deleted_at', '=', null) ->where('contacts.is_primary', '=', true) ->where('invoices.is_recurring', '=', false) + ->where('invoices.is_public', '=', true) // This needs to be a setting to also hide the activity on the dashboard page //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) ->select( @@ -308,6 +310,14 @@ class InvoiceRepository extends BaseRepository return $invoice; } + // set default to true for backwards compatability + if ( ! isset($data['is_public']) || filter_var($data['is_public'], FILTER_VALIDATE_BOOLEAN)) { + $invoice->is_public = true; + if ( ! $invoice->isSent()) { + $invoice->invoice_status_id = INVOICE_STATUS_SENT; + } + } + $invoice->fill($data); if ((isset($data['set_default_terms']) && $data['set_default_terms']) @@ -672,7 +682,8 @@ class InvoiceRepository extends BaseRepository 'custom_taxes2', 'partial', 'custom_text_value1', - 'custom_text_value2', ] as $field) { + 'custom_text_value2', + ] as $field) { $clone->$field = $invoice->$field; } @@ -731,7 +742,12 @@ class InvoiceRepository extends BaseRepository */ public function markSent(Invoice $invoice) { - $invoice->markInvitationsSent(); + if ( ! $invoice->isSent()) { + $invoice->invoice_status_id = INVOICE_STATUS_SENT; + } + + $invoice->is_public = true; + $invoice->save(); } /** @@ -748,7 +764,7 @@ class InvoiceRepository extends BaseRepository } $invoice = $invitation->invoice; - if (!$invoice || $invoice->is_deleted) { + if (!$invoice || $invoice->is_deleted || ! $invoice->is_public) { return false; } diff --git a/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php index 0372b0207063..fbc582868f94 100644 --- a/app/Ninja/Repositories/PaymentRepository.php +++ b/app/Ninja/Repositories/PaymentRepository.php @@ -103,6 +103,7 @@ class PaymentRepository extends BaseRepository ->where('payments.is_deleted', '=', false) ->where('invitations.deleted_at', '=', null) ->where('invoices.is_deleted', '=', false) + ->where('invoices.is_public', '=', true) ->where('invitations.contact_id', '=', $contactId) ->select( DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index a1402777ae9e..44d16160f952 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -79,6 +79,7 @@ class InvoiceService extends BaseService } } + $wasPublic = $invoice ? $invoice->is_public : false; $invoice = $this->invoiceRepo->save($data, $invoice); $client = $invoice->client; @@ -110,6 +111,10 @@ class InvoiceService extends BaseService } } + if ($invoice->is_public && ! $wasPublic) { + $invoice->markInvitationsSent(); + } + return $invoice; } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index d60bf3466a3f..8e5b24d4def2 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1011,9 +1011,9 @@ $LANG = array( 'pro_plan_remove_logo' => ':link to remove the Invoice Ninja logo by joining the Pro Plan', 'pro_plan_remove_logo_link' => 'Click here', - 'invitation_status_sent' => 'Email Sent', - 'invitation_status_opened' => 'Email Openend', - 'invitation_status_viewed' => 'Invoice Viewed', + 'invitation_status_sent' => 'Sent', + 'invitation_status_opened' => 'Openend', + 'invitation_status_viewed' => 'Viewed', 'email_error_inactive_client' => 'Emails can not be sent to inactive clients', 'email_error_inactive_contact' => 'Emails can not be sent to inactive contacts', 'email_error_inactive_invoice' => 'Emails can not be sent to inactive invoices', @@ -2257,6 +2257,7 @@ $LANG = array( 'force_pdfjs' => 'PDF Viewer', 'redirect_url' => 'Redirect URL', 'redirect_url_help' => 'Optionally specify a URL to redirect to after a payment is made entered.', + 'save_draft' => 'Save Draft', ); diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 5ec127594ac7..d38c9c79fb36 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -527,7 +527,8 @@ {!! Former::text('entityType') !!} {!! Former::text('action') !!} - {!! Former::text('public_id')->data_bind('value: public_id') !!} + {!! Former::text('public_id')->data_bind('value: public_id') !!} + {!! Former::text('is_public')->data_bind('value: is_public') !!} {!! Former::text('is_recurring')->data_bind('value: is_recurring') !!} {!! Former::text('is_quote')->data_bind('value: is_quote') !!} {!! Former::text('has_tasks')->data_bind('value: has_tasks') !!} @@ -553,7 +554,12 @@ @else @if (!$invoice->is_deleted) - {!! Button::success(trans("texts.save_{$entityType}"))->withAttributes(array('id' => 'saveButton', 'onclick' => 'onSaveClick()'))->appendIcon(Icon::create('floppy-disk')) !!} + @if ($invoice->is_public) + {!! Button::success(trans("texts.save_{$entityType}"))->withAttributes(array('id' => 'saveButton', 'onclick' => 'onSaveClick()'))->appendIcon(Icon::create('floppy-disk')) !!} + @else + {!! Button::normal(trans("texts.save_draft"))->withAttributes(array('id' => 'saveButton', 'onclick' => 'onSaveClick()'))->appendIcon(Icon::create('floppy-disk')) !!} + {!! Button::success(trans("texts.mark_sent"))->withAttributes(array('id' => 'saveButton', 'onclick' => 'onMarkSentClick()'))->appendIcon(Icon::create('globe')) !!} + @endif {!! Button::info(trans("texts.email_{$entityType}"))->withAttributes(array('id' => 'emailButton', 'onclick' => 'onEmailClick()'))->appendIcon(Icon::create('send')) !!} @if (!$invoice->trashed()) @if ($invoice->id) @@ -1284,6 +1290,11 @@ }, getSendToEmails()); } + function onMarkSentClick() { + model.invoice().is_public(true); + onSaveClick(); + } + function onSaveClick() { if (model.invoice().is_recurring()) { // warn invoice will be emailed when saving new recurring invoice @@ -1449,10 +1460,6 @@ return isValid; } - function onMarkClick() { - submitBulkAction('markSent'); - } - function onCloneClick() { submitAction('clone'); } diff --git a/resources/views/invoices/knockout.blade.php b/resources/views/invoices/knockout.blade.php index a3ce21249355..f0da49228aac 100644 --- a/resources/views/invoices/knockout.blade.php +++ b/resources/views/invoices/knockout.blade.php @@ -160,6 +160,7 @@ function ViewModel(data) { function InvoiceModel(data) { var self = this; this.client = ko.observable(data ? false : new ClientModel()); + this.is_public = ko.observable(0); self.account = {!! $account !!}; self.id = ko.observable(''); self.discount = ko.observable('');