diff --git a/app/Constants.php b/app/Constants.php
index 7a80973e78b6..638d10c2e177 100644
--- a/app/Constants.php
+++ b/app/Constants.php
@@ -46,6 +46,7 @@ if (! defined('APP_NAME')) {
define('ENTITY_PROPOSAL_TEMPLATE', 'proposal_template');
define('ENTITY_PROPOSAL_SNIPPET', 'proposal_snippet');
define('ENTITY_PROPOSAL_CATEGORY', 'proposal_category');
+ define('ENTITY_PROPOSAL_INVITATION', 'proposal_invitation');
define('INVOICE_TYPE_STANDARD', 1);
define('INVOICE_TYPE_QUOTE', 2);
diff --git a/app/Http/Requests/CreateProposalRequest.php b/app/Http/Requests/CreateProposalRequest.php
index cac756a49204..1c1c0fd59888 100644
--- a/app/Http/Requests/CreateProposalRequest.php
+++ b/app/Http/Requests/CreateProposalRequest.php
@@ -22,7 +22,7 @@ class CreateProposalRequest extends ProposalRequest
public function rules()
{
return [
- 'quote_id' => 'required',
+ 'invoice_id' => 'required',
];
}
}
diff --git a/app/Http/Requests/UpdateProposalRequest.php b/app/Http/Requests/UpdateProposalRequest.php
index d525f11e8dc2..8e106e045232 100644
--- a/app/Http/Requests/UpdateProposalRequest.php
+++ b/app/Http/Requests/UpdateProposalRequest.php
@@ -26,7 +26,7 @@ class UpdateProposalRequest extends ProposalRequest
}
return [
- 'quote_id' => 'required',
+ 'invoice_id' => 'required',
];
}
}
diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php
index 76d395efd744..d9fc6164b5c1 100644
--- a/app/Models/Invitation.php
+++ b/app/Models/Invitation.php
@@ -2,10 +2,9 @@
namespace App\Models;
-use Carbon;
use Illuminate\Database\Eloquent\SoftDeletes;
-use Utils;
use App\Models\LookupInvitation;
+use App\Models\Traits\Inviteable;
/**
* Class Invitation.
@@ -13,6 +12,8 @@ use App\Models\LookupInvitation;
class Invitation extends EntityModel
{
use SoftDeletes;
+ use Inviteable;
+
/**
* @var array
*/
@@ -57,111 +58,6 @@ class Invitation extends EntityModel
{
return $this->belongsTo('App\Models\Account');
}
-
- // If we're getting the link for PhantomJS to generate the PDF
- // we need to make sure it's served from our site
-
- /**
- * @param string $type
- * @param bool $forceOnsite
- *
- * @return string
- */
- public function getLink($type = 'view', $forceOnsite = false, $forcePlain = false)
- {
- if (! $this->account) {
- $this->load('account');
- }
-
- $account = $this->account;
- $iframe_url = $account->iframe_url;
- $url = trim(SITE_URL, '/');
-
- if (env('REQUIRE_HTTPS')) {
- $url = str_replace('http://', 'https://', $url);
- }
-
- if ($account->hasFeature(FEATURE_CUSTOM_URL)) {
- if (Utils::isNinjaProd() && ! Utils::isReseller()) {
- $url = $account->present()->clientPortalLink();
- }
-
- if ($iframe_url && ! $forceOnsite) {
- return "{$iframe_url}?{$this->invitation_key}";
- } elseif ($this->account->subdomain && ! $forcePlain) {
- $url = Utils::replaceSubdomain($url, $account->subdomain);
- }
- }
-
- return "{$url}/{$type}/{$this->invitation_key}";
- }
-
- /**
- * @return bool|string
- */
- public function getStatus()
- {
- $hasValue = false;
- $parts = [];
- $statuses = $this->message_id ? ['sent', 'opened', 'viewed'] : ['sent', 'viewed'];
-
- foreach ($statuses as $status) {
- $field = "{$status}_date";
- $date = '';
- 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;
- }
- }
-
- return $hasValue ? implode($parts, '
') : false;
- }
-
- /**
- * @return mixed
- */
- public function getName()
- {
- return $this->invitation_key;
- }
-
- /**
- * @param null $messageId
- */
- public function markSent($messageId = null)
- {
- $this->message_id = $messageId;
- $this->email_error = null;
- $this->sent_date = Carbon::now()->toDateTimeString();
- $this->save();
- }
-
- public function isSent()
- {
- return $this->sent_date && $this->sent_date != '0000-00-00 00:00:00';
- }
-
- public function markViewed()
- {
- $invoice = $this->invoice;
- $client = $invoice->client;
-
- $this->viewed_date = Carbon::now()->toDateTimeString();
- $this->save();
-
- $invoice->markViewed();
- $client->markLoggedIn();
- }
-
- public function signatureDiv()
- {
- if (! $this->signature_base64) {
- return false;
- }
-
- return sprintf('
%s: %s', $this->signature_base64, trans('texts.signed'), Utils::fromSqlDateTime($this->signature_date));
- }
}
Invitation::creating(function ($invitation)
diff --git a/app/Models/Proposal.php b/app/Models/Proposal.php
index 497830e012e4..2b2b0f892843 100644
--- a/app/Models/Proposal.php
+++ b/app/Models/Proposal.php
@@ -64,6 +64,14 @@ class Proposal extends EntityModel
return $this->belongsTo('App\Models\Invoice')->withTrashed();
}
+ /**
+ * @return mixed
+ */
+ public function proposal_invitations()
+ {
+ return $this->hasMany('App\Models\ProposalInvitation')->orderBy('proposal_invitations.contact_id');
+ }
+
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
diff --git a/app/Models/ProposalInvitation.php b/app/Models/ProposalInvitation.php
new file mode 100644
index 000000000000..c2895ffcbb27
--- /dev/null
+++ b/app/Models/ProposalInvitation.php
@@ -0,0 +1,86 @@
+belongsTo('App\Models\Proposal')->withTrashed();
+ }
+
+ /**
+ * @return mixed
+ */
+ public function contact()
+ {
+ return $this->belongsTo('App\Models\Contact')->withTrashed();
+ }
+
+ /**
+ * @return mixed
+ */
+ public function user()
+ {
+ return $this->belongsTo('App\Models\User')->withTrashed();
+ }
+
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function account()
+ {
+ return $this->belongsTo('App\Models\Account');
+ }
+}
+
+/*
+ProposalInvitation::creating(function ($invitation)
+{
+ LookupProposalInvitation::createNew($invitation->account->account_key, [
+ 'invitation_key' => $invitation->invitation_key,
+ ]);
+});
+
+ProposalInvitation::updating(function ($invitation) {
+ $dirty = $invitation->getDirty();
+ if (array_key_exists('message_id', $dirty)) {
+ LookupProposalInvitation::updateInvitation($invitation->account->account_key, $invitation);
+ }
+});
+
+ProposalInvitation::deleted(function ($invitation)
+{
+ if ($invitation->forceDeleting) {
+ LookupProposalInvitation::deleteWhere([
+ 'invitation_key' => $invitation->invitation_key,
+ ]);
+ }
+});
+*/
diff --git a/app/Models/Traits/Inviteable.php b/app/Models/Traits/Inviteable.php
new file mode 100644
index 000000000000..825755a4936d
--- /dev/null
+++ b/app/Models/Traits/Inviteable.php
@@ -0,0 +1,118 @@
+account) {
+ $this->load('account');
+ }
+
+ $account = $this->account;
+ $iframe_url = $account->iframe_url;
+ $url = trim(SITE_URL, '/');
+
+ if (env('REQUIRE_HTTPS')) {
+ $url = str_replace('http://', 'https://', $url);
+ }
+
+ if ($account->hasFeature(FEATURE_CUSTOM_URL)) {
+ if (Utils::isNinjaProd() && ! Utils::isReseller()) {
+ $url = $account->present()->clientPortalLink();
+ }
+
+ if ($iframe_url && ! $forceOnsite) {
+ return "{$iframe_url}?{$this->invitation_key}";
+ } elseif ($this->account->subdomain && ! $forcePlain) {
+ $url = Utils::replaceSubdomain($url, $account->subdomain);
+ }
+ }
+
+ return "{$url}/{$type}/{$this->invitation_key}";
+ }
+
+ /**
+ * @return bool|string
+ */
+ public function getStatus()
+ {
+ $hasValue = false;
+ $parts = [];
+ $statuses = $this->message_id ? ['sent', 'opened', 'viewed'] : ['sent', 'viewed'];
+
+ foreach ($statuses as $status) {
+ $field = "{$status}_date";
+ $date = '';
+ 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;
+ }
+ }
+
+ return $hasValue ? implode($parts, '
') : false;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getName()
+ {
+ return $this->invitation_key;
+ }
+
+ /**
+ * @param null $messageId
+ */
+ public function markSent($messageId = null)
+ {
+ $this->message_id = $messageId;
+ $this->email_error = null;
+ $this->sent_date = Carbon::now()->toDateTimeString();
+ $this->save();
+ }
+
+ public function isSent()
+ {
+ return $this->sent_date && $this->sent_date != '0000-00-00 00:00:00';
+ }
+
+ public function markViewed()
+ {
+ $this->viewed_date = Carbon::now()->toDateTimeString();
+ $this->save();
+
+ if ($this->invoice) {
+ $invoice = $this->invoice;
+ $client = $invoice->client;
+
+ $invoice->markViewed();
+ $client->markLoggedIn();
+ }
+ }
+
+ public function signatureDiv()
+ {
+ if (! $this->signature_base64) {
+ return false;
+ }
+
+ return sprintf('
%s: %s', $this->signature_base64, trans('texts.signed'), Utils::fromSqlDateTime($this->signature_date));
+ }
+}
diff --git a/app/Ninja/Repositories/ProposalRepository.php b/app/Ninja/Repositories/ProposalRepository.php
index 83714b810226..416a2f271c3a 100644
--- a/app/Ninja/Repositories/ProposalRepository.php
+++ b/app/Ninja/Repositories/ProposalRepository.php
@@ -5,6 +5,7 @@ namespace App\Ninja\Repositories;
use App\Models\Proposal;
use App\Models\Invoice;
use App\Models\ProposalTemplate;
+use App\Models\ProposalInvitation;
use Auth;
use DB;
use Utils;
@@ -89,6 +90,34 @@ class ProposalRepository extends BaseRepository
$proposal->save();
+ // create invitations
+ $contactIds = [];
+
+ foreach ($proposal->invoice->invitations as $invitation) {
+ $conactIds[] = $invitation->contact_id;
+ $found = false;
+ foreach ($proposal->proposal_invitations as $proposalInvitation) {
+ if ($invitation->contact_id == $proposalInvitation->contact_id) {
+ $found = true;
+ break;
+ }
+ }
+ if (! $found) {
+ $proposalInvitation = ProposalInvitation::createNew();
+ $proposalInvitation->proposal_id = $proposal->id;
+ $proposalInvitation->contact_id = $invitation->contact_id;
+ $proposalInvitation->invitation_key = strtolower(str_random(RANDOM_KEY_LENGTH));
+ $proposalInvitation->save();
+ }
+ }
+
+ // delete invitations
+ foreach ($proposal->proposal_invitations as $proposalInvitation) {
+ if (! in_array($proposalInvitation->contact_id, $conactIds)) {
+ $proposalInvitation->delete();
+ }
+ }
+
return $proposal;
}
}
diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php
index a4d33075daac..ba0f368b08ca 100644
--- a/resources/views/invoices/edit.blade.php
+++ b/resources/views/invoices/edit.blade.php
@@ -878,7 +878,6 @@
ko.mapping.fromJS(invoice, model.invoice().mapping, model.invoice);
model.invoice().is_recurring({{ $invoice->is_recurring ? '1' : '0' }});
model.invoice().start_date_orig(model.invoice().start_date());
-
@if ($invoice->id)
var invitationContactIds = {!! json_encode($invitationContactIds) !!};
var client = clientMap[invoice.client.public_id];