diff --git a/.env.example b/.env.example
index 4ca1bb2d3..c955b9de3 100644
--- a/.env.example
+++ b/.env.example
@@ -21,5 +21,8 @@ MAIL_FROM_NAME
MAIL_PASSWORD
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
+LOG=single
-LOG=single
\ No newline at end of file
+GOOGLE_CLIENT_ID
+GOOGLE_CLIENT_SECRET
+GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google
\ No newline at end of file
diff --git a/app/Commands/Command.php b/app/Commands/Command.php
index 018bc2192..5bc485011 100644
--- a/app/Commands/Command.php
+++ b/app/Commands/Command.php
@@ -1,7 +1,6 @@
-where('client_id', '=', $client->id)
->orderBy('activities.id')
- ->get(['activities.id', 'activities.created_at', 'activities.activity_type_id', 'activities.message', 'activities.adjustment', 'activities.balance', 'activities.invoice_id']);
+ ->get(['activities.id', 'activities.created_at', 'activities.activity_type_id', 'activities.adjustment', 'activities.balance', 'activities.invoice_id']);
//$this->info(var_dump($activities));
foreach ($activities as $activity) {
@@ -235,7 +235,6 @@ class CheckData extends Command {
'updated_at' => new Carbon,
'account_id' => $client->account_id,
'client_id' => $client->id,
- 'message' => 'Corrected client balance',
'adjustment' => $client->actual_balance - $activity->balance,
'balance' => $client->actual_balance,
]);
diff --git a/app/Console/Commands/CreateRandomData.php b/app/Console/Commands/CreateRandomData.php
deleted file mode 100644
index de4da39cb..000000000
--- a/app/Console/Commands/CreateRandomData.php
+++ /dev/null
@@ -1,88 +0,0 @@
-info(date('Y-m-d') . ' Running CreateRandomData...');
-
- $user = User::first();
-
- if (!$user) {
- $this->error("Error: please create user account by logging in");
- return;
- }
-
- $productNames = ['Arkansas', 'New York', 'Arizona', 'California', 'Colorado', 'Alabama', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'Alaska', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'];
- $clientNames = ['IBM', 'Nestle', 'Mitsubishi UFJ Financial', 'Vodafone', 'Eni', 'Procter & Gamble', 'Johnson & Johnson', 'American International Group', 'Banco Santander', 'BHP Billiton', 'Pfizer', 'Itaú Unibanco Holding', 'Ford Motor', 'BMW Group', 'Commonwealth Bank', 'EDF', 'Statoil', 'Google', 'Siemens', 'Novartis', 'Royal Bank of Canada', 'Sumitomo Mitsui Financial', 'Comcast', 'Sberbank', 'Goldman Sachs Group', 'Westpac Banking Group', 'Nippon Telegraph & Tel', 'Ping An Insurance Group', 'Banco Bradesco', 'Anheuser-Busch InBev', 'Bank of Communications', 'China Life Insurance', 'General Motors', 'Telefónica', 'MetLife', 'Honda Motor', 'Enel', 'BASF', 'Softbank', 'National Australia Bank', 'ANZ', 'ConocoPhillips', 'TD Bank Group', 'Intel', 'UBS', 'Hewlett-Packard', 'Coca-Cola', 'Cisco Systems', 'UnitedHealth Group', 'Boeing', 'Zurich Insurance Group', 'Hyundai Motor', 'Sanofi', 'Credit Agricole', 'United Technologies', 'Roche Holding', 'Munich Re', 'PepsiCo', 'Oracle', 'Bank of Nova Scotia'];
-
- foreach ($productNames as $i => $value) {
- $product = Product::createNew($user);
- $product->id = $i+1;
- $product->product_key = $value;
- $product->save();
- }
-
- foreach ($clientNames as $i => $value) {
- $client = Client::createNew($user);
- $client->name = $value;
- $client->save();
-
- $contact = Contact::createNew($user);
- $contact->email = "client@aol.com";
- $contact->is_primary = 1;
- $client->contacts()->save($contact);
-
- $numInvoices = rand(1, 25);
- if ($numInvoices == 4 || $numInvoices == 10 || $numInvoices == 25) {
- // leave these
- } else if ($numInvoices % 3 == 0) {
- $numInvoices = 1;
- } else if ($numInvoices > 10) {
- $numInvoices = $numInvoices % 2;
- }
-
- $paidUp = rand(0, 1) == 1;
-
- for ($j=1; $j<=$numInvoices; $j++) {
-
- $price = rand(10, 1000);
- if ($price < 900) {
- $price = rand(10, 150);
- }
-
- $invoice = Invoice::createNew($user);
- $invoice->invoice_number = $user->account->getNextInvoiceNumber($invoice);
- $invoice->amount = $invoice->balance = $price;
- $invoice->created_at = date('Y-m-d', strtotime(date("Y-m-d") . ' - ' . rand(1, 100) . ' days'));
- $client->invoices()->save($invoice);
-
- $productId = rand(0, 40);
- if ($productId > 20) {
- $productId = ($productId % 2) + rand(0, 2);
- }
-
- $invoiceItem = InvoiceItem::createNew($user);
- $invoiceItem->product_id = $productId+1;
- $invoiceItem->product_key = $productNames[$invoiceItem->product_id];
- $invoiceItem->cost = $invoice->amount;
- $invoiceItem->qty = 1;
- $invoice->invoice_items()->save($invoiceItem);
-
- if ($paidUp || rand(0,2) > 1) {
- $payment = Payment::createNew($user);
- $payment->invoice_id = $invoice->id;
- $payment->amount = $invoice->amount;
- $client->payments()->save($payment);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index fd97865be..03b6ce776 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -13,7 +13,6 @@ class Kernel extends ConsoleKernel
*/
protected $commands = [
'App\Console\Commands\SendRecurringInvoices',
- 'App\Console\Commands\CreateRandomData',
'App\Console\Commands\ResetData',
'App\Console\Commands\CheckData',
'App\Console\Commands\SendRenewalInvoices',
diff --git a/app/Events/ClientWasArchived.php b/app/Events/ClientWasArchived.php
new file mode 100644
index 000000000..03ebdc09c
--- /dev/null
+++ b/app/Events/ClientWasArchived.php
@@ -0,0 +1,21 @@
+client = $client;
+ }
+}
diff --git a/app/Events/ClientWasCreated.php b/app/Events/ClientWasCreated.php
new file mode 100644
index 000000000..5c2d37001
--- /dev/null
+++ b/app/Events/ClientWasCreated.php
@@ -0,0 +1,21 @@
+client = $client;
+ }
+}
diff --git a/app/Events/ClientWasDeleted.php b/app/Events/ClientWasDeleted.php
new file mode 100644
index 000000000..b87063c49
--- /dev/null
+++ b/app/Events/ClientWasDeleted.php
@@ -0,0 +1,21 @@
+client = $client;
+ }
+}
diff --git a/app/Events/ClientWasRestored.php b/app/Events/ClientWasRestored.php
new file mode 100644
index 000000000..385a0472a
--- /dev/null
+++ b/app/Events/ClientWasRestored.php
@@ -0,0 +1,21 @@
+client = $client;
+ }
+}
diff --git a/app/Events/ClientWasUpdated.php b/app/Events/ClientWasUpdated.php
new file mode 100644
index 000000000..7e4790da6
--- /dev/null
+++ b/app/Events/ClientWasUpdated.php
@@ -0,0 +1,21 @@
+client = $client;
+ }
+}
diff --git a/app/Events/CreditWasArchived.php b/app/Events/CreditWasArchived.php
new file mode 100644
index 000000000..2c680905b
--- /dev/null
+++ b/app/Events/CreditWasArchived.php
@@ -0,0 +1,23 @@
+credit = $credit;
+ }
+
+}
diff --git a/app/Events/CreditWasCreated.php b/app/Events/CreditWasCreated.php
new file mode 100644
index 000000000..bc20b312d
--- /dev/null
+++ b/app/Events/CreditWasCreated.php
@@ -0,0 +1,23 @@
+credit = $credit;
+ }
+
+}
diff --git a/app/Events/CreditWasDeleted.php b/app/Events/CreditWasDeleted.php
new file mode 100644
index 000000000..e26a5d3ab
--- /dev/null
+++ b/app/Events/CreditWasDeleted.php
@@ -0,0 +1,23 @@
+credit = $credit;
+ }
+
+}
diff --git a/app/Events/CreditWasRestored.php b/app/Events/CreditWasRestored.php
new file mode 100644
index 000000000..8d17d961e
--- /dev/null
+++ b/app/Events/CreditWasRestored.php
@@ -0,0 +1,23 @@
+credit = $credit;
+ }
+
+}
diff --git a/app/Events/InvoiceInvitationWasEmailed.php b/app/Events/InvoiceInvitationWasEmailed.php
new file mode 100644
index 000000000..da0031249
--- /dev/null
+++ b/app/Events/InvoiceInvitationWasEmailed.php
@@ -0,0 +1,23 @@
+invitation = $invitation;
+ }
+
+}
diff --git a/app/Events/InvoiceInvitationWasViewed.php b/app/Events/InvoiceInvitationWasViewed.php
new file mode 100644
index 000000000..bbf7e23c3
--- /dev/null
+++ b/app/Events/InvoiceInvitationWasViewed.php
@@ -0,0 +1,25 @@
+invoice = $invoice;
+ $this->invitation = $invitation;
+ }
+
+}
diff --git a/app/Events/InvoiceWasArchived.php b/app/Events/InvoiceWasArchived.php
new file mode 100644
index 000000000..7587c071a
--- /dev/null
+++ b/app/Events/InvoiceWasArchived.php
@@ -0,0 +1,22 @@
+invoice = $invoice;
+ }
+
+}
diff --git a/app/Events/InvoiceSent.php b/app/Events/InvoiceWasCreated.php
similarity index 88%
rename from app/Events/InvoiceSent.php
rename to app/Events/InvoiceWasCreated.php
index cbe08d052..cfd943bcf 100644
--- a/app/Events/InvoiceSent.php
+++ b/app/Events/InvoiceWasCreated.php
@@ -4,10 +4,9 @@ use App\Events\Event;
use Illuminate\Queue\SerializesModels;
-class InvoiceSent extends Event {
+class InvoiceWasCreated extends Event {
use SerializesModels;
-
public $invoice;
/**
diff --git a/app/Events/InvoiceViewed.php b/app/Events/InvoiceWasDeleted.php
similarity index 61%
rename from app/Events/InvoiceViewed.php
rename to app/Events/InvoiceWasDeleted.php
index 8d9f129e7..316b1b5c5 100644
--- a/app/Events/InvoiceViewed.php
+++ b/app/Events/InvoiceWasDeleted.php
@@ -4,10 +4,9 @@ use App\Events\Event;
use Illuminate\Queue\SerializesModels;
-class InvoiceViewed extends Event {
+class InvoiceWasDeleted extends Event {
use SerializesModels;
-
public $invoice;
/**
@@ -15,9 +14,9 @@ class InvoiceViewed extends Event {
*
* @return void
*/
- public function __construct($invoice)
- {
- $this->invoice = $invoice;
- }
+ public function __construct($invoice)
+ {
+ $this->invoice = $invoice;
+ }
}
diff --git a/app/Events/InvoiceWasEmailed.php b/app/Events/InvoiceWasEmailed.php
new file mode 100644
index 000000000..dc30f6a55
--- /dev/null
+++ b/app/Events/InvoiceWasEmailed.php
@@ -0,0 +1,22 @@
+invoice = $invoice;
+ }
+
+}
diff --git a/app/Events/InvoiceWasRestored.php b/app/Events/InvoiceWasRestored.php
new file mode 100644
index 000000000..5d75b4b24
--- /dev/null
+++ b/app/Events/InvoiceWasRestored.php
@@ -0,0 +1,25 @@
+invoice = $invoice;
+ $this->fromDeleted = $fromDeleted;
+ }
+
+}
diff --git a/app/Events/QuoteApproved.php b/app/Events/InvoiceWasUpdated.php
similarity index 87%
rename from app/Events/QuoteApproved.php
rename to app/Events/InvoiceWasUpdated.php
index 12b5384b3..87a0f8f20 100644
--- a/app/Events/QuoteApproved.php
+++ b/app/Events/InvoiceWasUpdated.php
@@ -4,10 +4,9 @@ use App\Events\Event;
use Illuminate\Queue\SerializesModels;
-class QuoteApproved extends Event {
+class InvoiceWasUpdated extends Event {
use SerializesModels;
-
public $invoice;
/**
diff --git a/app/Events/InvoicePaid.php b/app/Events/PaymentWasArchived.php
similarity index 87%
rename from app/Events/InvoicePaid.php
rename to app/Events/PaymentWasArchived.php
index 4dced7347..b8bb693df 100644
--- a/app/Events/InvoicePaid.php
+++ b/app/Events/PaymentWasArchived.php
@@ -4,10 +4,9 @@ use App\Events\Event;
use Illuminate\Queue\SerializesModels;
-class InvoicePaid extends Event {
+class PaymentWasArchived extends Event {
use SerializesModels;
-
public $payment;
/**
diff --git a/app/Events/PaymentWasCreated.php b/app/Events/PaymentWasCreated.php
new file mode 100644
index 000000000..619d33e95
--- /dev/null
+++ b/app/Events/PaymentWasCreated.php
@@ -0,0 +1,22 @@
+payment = $payment;
+ }
+
+}
diff --git a/app/Events/PaymentWasDeleted.php b/app/Events/PaymentWasDeleted.php
new file mode 100644
index 000000000..e12647c86
--- /dev/null
+++ b/app/Events/PaymentWasDeleted.php
@@ -0,0 +1,22 @@
+payment = $payment;
+ }
+
+}
diff --git a/app/Events/PaymentWasRestored.php b/app/Events/PaymentWasRestored.php
new file mode 100644
index 000000000..711bdbb67
--- /dev/null
+++ b/app/Events/PaymentWasRestored.php
@@ -0,0 +1,25 @@
+payment = $payment;
+ $this->fromDeleted = $fromDeleted;
+ }
+
+}
diff --git a/app/Events/QuoteInvitationWasApproved.php b/app/Events/QuoteInvitationWasApproved.php
new file mode 100644
index 000000000..954aa83b4
--- /dev/null
+++ b/app/Events/QuoteInvitationWasApproved.php
@@ -0,0 +1,25 @@
+quote = $quote;
+ $this->invitation = $invitation;
+ }
+
+}
diff --git a/app/Events/QuoteInvitationWasEmailed.php b/app/Events/QuoteInvitationWasEmailed.php
new file mode 100644
index 000000000..5ce1c6860
--- /dev/null
+++ b/app/Events/QuoteInvitationWasEmailed.php
@@ -0,0 +1,23 @@
+invitation = $invitation;
+ }
+
+}
diff --git a/app/Events/QuoteInvitationWasViewed.php b/app/Events/QuoteInvitationWasViewed.php
new file mode 100644
index 000000000..3cd84b0e1
--- /dev/null
+++ b/app/Events/QuoteInvitationWasViewed.php
@@ -0,0 +1,25 @@
+quote = $quote;
+ $this->invitation = $invitation;
+ }
+
+}
diff --git a/app/Events/QuoteWasArchived.php b/app/Events/QuoteWasArchived.php
new file mode 100644
index 000000000..285a61250
--- /dev/null
+++ b/app/Events/QuoteWasArchived.php
@@ -0,0 +1,22 @@
+quote = $quote;
+ }
+
+}
diff --git a/app/Events/QuoteWasCreated.php b/app/Events/QuoteWasCreated.php
new file mode 100644
index 000000000..d17ef9c13
--- /dev/null
+++ b/app/Events/QuoteWasCreated.php
@@ -0,0 +1,22 @@
+quote = $quote;
+ }
+
+}
diff --git a/app/Events/QuoteWasDeleted.php b/app/Events/QuoteWasDeleted.php
new file mode 100644
index 000000000..ce3685d7a
--- /dev/null
+++ b/app/Events/QuoteWasDeleted.php
@@ -0,0 +1,22 @@
+quote = $quote;
+ }
+
+}
diff --git a/app/Events/QuoteWasEmailed.php b/app/Events/QuoteWasEmailed.php
new file mode 100644
index 000000000..19b1ec12d
--- /dev/null
+++ b/app/Events/QuoteWasEmailed.php
@@ -0,0 +1,22 @@
+quote = $quote;
+ }
+
+}
diff --git a/app/Events/QuoteWasRestored.php b/app/Events/QuoteWasRestored.php
new file mode 100644
index 000000000..0f13a65b4
--- /dev/null
+++ b/app/Events/QuoteWasRestored.php
@@ -0,0 +1,22 @@
+quote = $quote;
+ }
+
+}
diff --git a/app/Events/QuoteWasUpdated.php b/app/Events/QuoteWasUpdated.php
new file mode 100644
index 000000000..f01b98226
--- /dev/null
+++ b/app/Events/QuoteWasUpdated.php
@@ -0,0 +1,22 @@
+quote = $quote;
+ }
+
+}
diff --git a/app/Http/Controllers/AccountApiController.php b/app/Http/Controllers/AccountApiController.php
new file mode 100644
index 000000000..a2154ac53
--- /dev/null
+++ b/app/Http/Controllers/AccountApiController.php
@@ -0,0 +1,40 @@
+accountRepo = $accountRepo;
+ }
+
+ public function index()
+ {
+ $manager = new Manager();
+ $manager->setSerializer(new ArraySerializer());
+
+ $account = Auth::user()->account->load('users');
+ $resource = new Item($account, new AccountTransformer, 'account');
+
+ $response = $manager->createData($resource)->toArray();
+ $response = json_encode($response, JSON_PRETTY_PRINT);
+ $headers = Utils::getApiHeaders();
+
+ return Response::make($response, 200, $headers);
+ }
+}
diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php
index d4a358ebc..2b6fc3321 100644
--- a/app/Http/Controllers/AccountController.php
+++ b/app/Http/Controllers/AccountController.php
@@ -38,6 +38,7 @@ use App\Models\Industry;
use App\Models\InvoiceDesign;
use App\Models\TaxRate;
use App\Ninja\Repositories\AccountRepository;
+use App\Ninja\Repositories\ReferralRepository;
use App\Ninja\Mailers\UserMailer;
use App\Ninja\Mailers\ContactMailer;
use App\Events\UserSignedUp;
@@ -45,19 +46,23 @@ use App\Events\UserLoggedIn;
use App\Events\UserSettingsChanged;
use App\Services\AuthService;
+use App\Commands\CreateClient;
+
class AccountController extends BaseController
{
protected $accountRepo;
protected $userMailer;
protected $contactMailer;
+ protected $referralRepository;
- public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer)
+ public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository)
{
parent::__construct();
$this->accountRepo = $accountRepo;
$this->userMailer = $userMailer;
$this->contactMailer = $contactMailer;
+ $this->referralRepository = $referralRepository;
}
public function demo()
@@ -221,13 +226,14 @@ class AccountController extends BaseController
foreach (AuthService::$providers as $provider) {
$oauthLoginUrls[] = ['label' => $provider, 'url' => '/auth/' . strtolower($provider)];
}
-
+
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'title' => trans('texts.user_details'),
'user' => Auth::user(),
'oauthProviderName' => AuthService::getProviderName(Auth::user()->oauth_provider_id),
'oauthLoginUrls' => $oauthLoginUrls,
+ 'referralCounts' => $this->referralRepository->getCounts(Auth::user()->id),
];
return View::make('accounts.user_details', $data);
@@ -519,8 +525,10 @@ class AccountController extends BaseController
$account->invoice_number_counter = Input::get('invoice_number_counter');
$account->quote_number_prefix = Input::get('quote_number_prefix');
$account->share_counter = Input::get('share_counter') ? true : false;
-
$account->pdf_email_attachment = Input::get('pdf_email_attachment') ? true : false;
+ $account->invoice_terms = Input::get('invoice_terms');
+ $account->invoice_footer = Input::get('invoice_footer');
+ $account->quote_terms = Input::get('quote_terms');
if (Input::has('recurring_hour')) {
$account->recurring_hour = Input::get('recurring_hour');
@@ -637,49 +645,56 @@ class AccountController extends BaseController
continue;
}
- $client = Client::createNew();
- $contact = Contact::createNew();
- $contact->is_primary = true;
- $contact->send_invoice = true;
- $count++;
+ $data = [
+ 'contacts' => [[]]
+ ];
foreach ($row as $index => $value) {
$field = $map[$index];
- $value = trim($value);
+ if ( ! $value = trim($value)) {
+ continue;
+ }
- if ($field == Client::$fieldName && !$client->name) {
- $client->name = $value;
- } elseif ($field == Client::$fieldPhone && !$client->work_phone) {
- $client->work_phone = $value;
- } elseif ($field == Client::$fieldAddress1 && !$client->address1) {
- $client->address1 = $value;
- } elseif ($field == Client::$fieldAddress2 && !$client->address2) {
- $client->address2 = $value;
- } elseif ($field == Client::$fieldCity && !$client->city) {
- $client->city = $value;
- } elseif ($field == Client::$fieldState && !$client->state) {
- $client->state = $value;
- } elseif ($field == Client::$fieldPostalCode && !$client->postal_code) {
- $client->postal_code = $value;
- } elseif ($field == Client::$fieldCountry && !$client->country_id) {
+ if ($field == Client::$fieldName) {
+ $data['name'] = $value;
+ } elseif ($field == Client::$fieldPhone) {
+ $data['work_phone'] = $value;
+ } elseif ($field == Client::$fieldAddress1) {
+ $data['address1'] = $value;
+ } elseif ($field == Client::$fieldAddress2) {
+ $data['address2'] = $value;
+ } elseif ($field == Client::$fieldCity) {
+ $data['city'] = $value;
+ } elseif ($field == Client::$fieldState) {
+ $data['state'] = $value;
+ } elseif ($field == Client::$fieldPostalCode) {
+ $data['postal_code'] = $value;
+ } elseif ($field == Client::$fieldCountry) {
$value = strtolower($value);
- $client->country_id = isset($countryMap[$value]) ? $countryMap[$value] : null;
- } elseif ($field == Client::$fieldNotes && !$client->private_notes) {
- $client->private_notes = $value;
- } elseif ($field == Contact::$fieldFirstName && !$contact->first_name) {
- $contact->first_name = $value;
- } elseif ($field == Contact::$fieldLastName && !$contact->last_name) {
- $contact->last_name = $value;
- } elseif ($field == Contact::$fieldPhone && !$contact->phone) {
- $contact->phone = $value;
- } elseif ($field == Contact::$fieldEmail && !$contact->email) {
- $contact->email = strtolower($value);
+ $data['country_id'] = isset($countryMap[$value]) ? $countryMap[$value] : null;
+ } elseif ($field == Client::$fieldNotes) {
+ $data['private_notes'] = $value;
+ } elseif ($field == Contact::$fieldFirstName) {
+ $data['contacts'][0]['first_name'] = $value;
+ } elseif ($field == Contact::$fieldLastName) {
+ $data['contacts'][0]['last_name'] = $value;
+ } elseif ($field == Contact::$fieldPhone) {
+ $data['contacts'][0]['phone'] = $value;
+ } elseif ($field == Contact::$fieldEmail) {
+ $data['contacts'][0]['email'] = strtolower($value);
}
}
- $client->save();
- $client->contacts()->save($contact);
- Activity::createClient($client, false);
+ $rules = [
+ 'contacts' => 'valid_contacts',
+ ];
+ $validator = Validator::make($data, $rules);
+ if ($validator->fails()) {
+ continue;
+ }
+
+ $this->dispatch(new CreateClient($data));
+ $count++;
}
$message = Utils::pluralize('created_client', $count);
@@ -792,12 +807,6 @@ class AccountController extends BaseController
private function saveNotifications()
{
- $account = Auth::user()->account;
- $account->invoice_terms = Input::get('invoice_terms');
- $account->invoice_footer = Input::get('invoice_footer');
- $account->email_footer = Input::get('email_footer');
- $account->save();
-
$user = Auth::user();
$user->notify_sent = Input::get('notify_sent');
$user->notify_viewed = Input::get('notify_viewed');
@@ -838,6 +847,7 @@ class AccountController extends BaseController
$account->country_id = Input::get('country_id') ? Input::get('country_id') : null;
$account->size_id = Input::get('size_id') ? Input::get('size_id') : null;
$account->industry_id = Input::get('industry_id') ? Input::get('industry_id') : null;
+ $account->email_footer = Input::get('email_footer');
$account->save();
/* Logo image file */
diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php
index e448f5bdb..9f190bbf2 100644
--- a/app/Http/Controllers/AccountGatewayController.php
+++ b/app/Http/Controllers/AccountGatewayController.php
@@ -60,20 +60,17 @@ class AccountGatewayController extends BaseController
public function edit($publicId)
{
$accountGateway = AccountGateway::scope($publicId)->firstOrFail();
- $config = $accountGateway->config;
- $selectedCards = $accountGateway->accepted_credit_cards;
-
- $configFields = json_decode($config);
-
- foreach ($configFields as $configField => $value) {
- $configFields->$configField = str_repeat('*', strlen($value));
+ $config = $accountGateway->getConfig();
+
+ foreach ($config as $field => $value) {
+ $config->$field = str_repeat('*', strlen($value));
}
$data = self::getViewModel($accountGateway);
$data['url'] = 'gateways/'.$publicId;
$data['method'] = 'PUT';
$data['title'] = trans('texts.edit_gateway') . ' - ' . $accountGateway->gateway->name;
- $data['config'] = $configFields;
+ $data['config'] = $config;
$data['hiddenFields'] = Gateway::$hiddenFields;
$data['paymentTypeId'] = $accountGateway->getPaymentType();
$data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get();
@@ -237,7 +234,7 @@ class AccountGatewayController extends BaseController
if ($accountGatewayPublicId) {
$accountGateway = AccountGateway::scope($accountGatewayPublicId)->firstOrFail();
- $oldConfig = json_decode($accountGateway->config);
+ $oldConfig = $accountGateway->getConfig();
} else {
$accountGateway = AccountGateway::createNew();
$accountGateway->gateway_id = $gatewayId;
@@ -267,7 +264,7 @@ class AccountGatewayController extends BaseController
$accountGateway->accepted_credit_cards = $cardCount;
$accountGateway->show_address = Input::get('show_address') ? true : false;
$accountGateway->update_address = Input::get('update_address') ? true : false;
- $accountGateway->config = json_encode($config);
+ $accountGateway->setConfig($config);
if ($accountGatewayPublicId) {
$accountGateway->save();
diff --git a/app/Http/Controllers/ActivityController.php b/app/Http/Controllers/ActivityController.php
index 39419f227..02700e233 100644
--- a/app/Http/Controllers/ActivityController.php
+++ b/app/Http/Controllers/ActivityController.php
@@ -5,20 +5,46 @@ use DB;
use Datatable;
use Utils;
use View;
+use App\Models\Client;
+use App\Models\Activity;
+use App\Ninja\Repositories\ActivityRepository;
class ActivityController extends BaseController
{
+ protected $activityRepo;
+
+ public function __construct(ActivityRepository $activityRepo)
+ {
+ parent::__construct();
+
+ $this->activityRepo = $activityRepo;
+ }
+
public function getDatatable($clientPublicId)
{
- $query = DB::table('activities')
- ->join('clients', 'clients.id', '=', 'activities.client_id')
- ->where('clients.public_id', '=', $clientPublicId)
- ->where('activities.account_id', '=', Auth::user()->account_id)
- ->select('activities.id', 'activities.message', 'activities.created_at', 'clients.currency_id', 'activities.balance', 'activities.adjustment');
+ $clientId = Client::getPrivateId($clientPublicId);
+
+ if ( ! $clientId) {
+ app()->abort(404);
+ }
+
+ $query = $this->activityRepo->findByClientId($clientId);
return Datatable::query($query)
->addColumn('activities.id', function ($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); })
- ->addColumn('message', function ($model) { return Utils::decodeActivity($model->message); })
+ ->addColumn('activity_type_id', function ($model) {
+ $data = [
+ 'client' => link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model)),
+ 'user' => $model->is_system ? '' . trans('texts.system') . '' : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email),
+ 'invoice' => $model->invoice ? link_to('/invoices/' . $model->invoice_public_id, $model->is_recurring ? trans('texts.recurring_invoice') : $model->invoice) : null,
+ 'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice) : null,
+ 'contact' => $model->contact_id ? link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model)) : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email),
+ 'payment' => $model->payment ?: '',
+ 'credit' => Utils::formatMoney($model->credit, $model->currency_id)
+ ];
+
+ return trans("texts.activity_{$model->activity_type_id}", $data);
+ })
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('adjustment', function ($model) { return $model->adjustment != 0 ? Utils::wrapAdjustment($model->adjustment, $model->currency_id) : ''; })
->make();
diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php
index ae5a7a125..8c3b9b5fe 100644
--- a/app/Http/Controllers/AppController.php
+++ b/app/Http/Controllers/AppController.php
@@ -189,6 +189,7 @@ class AppController extends BaseController
Artisan::call('db:seed', array('--force' => true, '--class' => 'PaymentLibrariesSeeder'));
Artisan::call('optimize', array('--force' => true));
Cache::flush();
+ Session::flush();
Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.processed_updates'));
} catch (Exception $e) {
diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php
index 6c732cf15..14ef9cf8b 100644
--- a/app/Http/Controllers/Auth/AuthController.php
+++ b/app/Http/Controllers/Auth/AuthController.php
@@ -97,8 +97,14 @@ class AuthController extends Controller {
} else {
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
}
-
Session::put(SESSION_USER_ACCOUNTS, $users);
+
+ if ($request->create_token) {
+ if ( ! env(API_SECRET) || $request->api_secret !== env(API_SECRET)) {
+ return 'Invalid secret';
+ }
+ return $this->accountRepo->createToken($request->token_name);
+ }
} elseif ($user) {
$user->failed_logins = $user->failed_logins + 1;
$user->save();
diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php
index 0cc63c7c5..1a2f6c8dc 100644
--- a/app/Http/Controllers/BaseController.php
+++ b/app/Http/Controllers/BaseController.php
@@ -1,7 +1,11 @@
clientRepo->getErrors($data);
+ $client = $this->clientRepo->save($request->input());
- if ($error) {
- $headers = Utils::getApiHeaders();
+ $client = Client::scope($client->public_id)->with('country', 'contacts', 'industry', 'size', 'currency')->first();
+ $client = Utils::remapPublicIds([$client]);
+ $response = json_encode($client, JSON_PRETTY_PRINT);
+ $headers = Utils::getApiHeaders();
- return Response::make($error, 500, $headers);
- } else {
- $client = $this->clientRepo->save(isset($data['id']) ? $data['id'] : false, $data, false);
- $client = Client::scope($client->public_id)->with('country', 'contacts', 'industry', 'size', 'currency')->first();
- $client = Utils::remapPublicIds([$client]);
- $response = json_encode($client, JSON_PRETTY_PRINT);
- $headers = Utils::getApiHeaders();
-
- return Response::make($response, 200, $headers);
- }
+ return Response::make($response, 200, $headers);
}
}
diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php
index 6b168751c..c5ed7e5b4 100644
--- a/app/Http/Controllers/ClientController.php
+++ b/app/Http/Controllers/ClientController.php
@@ -21,18 +21,23 @@ use App\Models\Industry;
use App\Models\Currency;
use App\Models\Country;
use App\Models\Task;
-
use App\Ninja\Repositories\ClientRepository;
+use App\Services\ClientService;
+
+use App\Http\Requests\CreateClientRequest;
+use App\Http\Requests\UpdateClientRequest;
class ClientController extends BaseController
{
+ protected $clientService;
protected $clientRepo;
- public function __construct(ClientRepository $clientRepo)
+ public function __construct(ClientRepository $clientRepo, ClientService $clientService)
{
parent::__construct();
$this->clientRepo = $clientRepo;
+ $this->clientService = $clientService;
}
/**
@@ -63,9 +68,6 @@ class ClientController extends BaseController
->addColumn('last_login', function ($model) { return Utils::timestampToDateString(strtotime($model->last_login)); })
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('dropdown', function ($model) {
- if ($model->is_deleted) {
- return '
';
- }
$str = '';
+ }
+
+ return $str.''.trans('texts.delete_client').'
';
})
->make();
@@ -103,9 +109,13 @@ class ClientController extends BaseController
*
* @return Response
*/
- public function store()
+ public function store(CreateClientRequest $request)
{
- return $this->save();
+ $client = $this->clientService->save($request->input());
+
+ Session::flash('message', trans('texts.created_client'));
+
+ return redirect()->to($client->getRoute());
}
/**
@@ -194,6 +204,7 @@ class ClientController extends BaseController
private static function getViewModel()
{
return [
+ 'data' => Input::old('data'),
'account' => Auth::user()->account,
'sizes' => Cache::get('sizes'),
'paymentTerms' => Cache::get('paymentTerms'),
@@ -212,105 +223,20 @@ class ClientController extends BaseController
* @param int $id
* @return Response
*/
- public function update($publicId)
+ public function update(UpdateClientRequest $request)
{
- return $this->save($publicId);
- }
-
- private function save($publicId = null)
- {
- $rules = array(
- 'email' => 'email|required_without:first_name',
- 'first_name' => 'required_without:email',
- );
- $validator = Validator::make(Input::all(), $rules);
-
- if ($validator->fails()) {
- $url = $publicId ? 'clients/'.$publicId.'/edit' : 'clients/create';
-
- return Redirect::to($url)
- ->withErrors($validator)
- ->withInput(Input::except('password'));
- } else {
- if ($publicId) {
- $client = Client::scope($publicId)->firstOrFail();
- } else {
- $client = Client::createNew();
- }
-
- $client->name = trim(Input::get('name'));
- $client->id_number = trim(Input::get('id_number'));
- $client->vat_number = trim(Input::get('vat_number'));
- $client->work_phone = trim(Input::get('work_phone'));
- $client->custom_value1 = trim(Input::get('custom_value1'));
- $client->custom_value2 = trim(Input::get('custom_value2'));
- $client->address1 = trim(Input::get('address1'));
- $client->address2 = trim(Input::get('address2'));
- $client->city = trim(Input::get('city'));
- $client->state = trim(Input::get('state'));
- $client->postal_code = trim(Input::get('postal_code'));
- $client->country_id = Input::get('country_id') ?: null;
- $client->private_notes = trim(Input::get('private_notes'));
- $client->size_id = Input::get('size_id') ?: null;
- $client->industry_id = Input::get('industry_id') ?: null;
- $client->currency_id = Input::get('currency_id') ?: null;
- $client->language_id = Input::get('language_id') ?: null;
- $client->payment_terms = Input::get('payment_terms') ?: 0;
- $client->website = trim(Input::get('website'));
-
- if (Input::has('invoice_number_counter')) {
- $client->invoice_number_counter = (int) Input::get('invoice_number_counter');
- }
- if (Input::has('quote_number_counter')) {
- $client->invoice_number_counter = (int) Input::get('quote_number_counter');
- }
-
- $client->save();
-
- $data = json_decode(Input::get('data'));
- $contactIds = [];
- $isPrimary = true;
-
- foreach ($data->contacts as $contact) {
- if (isset($contact->public_id) && $contact->public_id) {
- $record = Contact::scope($contact->public_id)->firstOrFail();
- } else {
- $record = Contact::createNew();
- }
-
- $record->email = trim($contact->email);
- $record->first_name = trim($contact->first_name);
- $record->last_name = trim($contact->last_name);
- $record->phone = trim($contact->phone);
- $record->is_primary = $isPrimary;
- $isPrimary = false;
-
- $client->contacts()->save($record);
- $contactIds[] = $record->public_id;
- }
-
- foreach ($client->contacts as $contact) {
- if (!in_array($contact->public_id, $contactIds)) {
- $contact->delete();
- }
- }
-
- if ($publicId) {
- Session::flash('message', trans('texts.updated_client'));
- } else {
- Activity::createClient($client);
- Session::flash('message', trans('texts.created_client'));
- }
-
- return Redirect::to('clients/'.$client->public_id);
- }
+ $client = $this->clientService->save($request->input());
+
+ Session::flash('message', trans('texts.updated_client'));
+
+ return redirect()->to($client->getRoute());
}
public function bulk()
{
$action = Input::get('action');
- $ids = Input::get('id') ? Input::get('id') : Input::get('ids');
- $count = $this->clientRepo->bulk($ids, $action);
+ $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
+ $count = $this->clientService->bulk($ids, $action);
$message = Utils::pluralize($action.'d_client', $count);
Session::flash('message', $message);
diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php
index 505400718..ad04651ac 100644
--- a/app/Http/Controllers/CreditController.php
+++ b/app/Http/Controllers/CreditController.php
@@ -8,18 +8,21 @@ use Utils;
use View;
use Validator;
use App\Models\Client;
-
+use App\Services\CreditService;
use App\Ninja\Repositories\CreditRepository;
+use App\Http\Requests\CreateCreditRequest;
class CreditController extends BaseController
{
protected $creditRepo;
+ protected $CreditService;
- public function __construct(CreditRepository $creditRepo)
+ public function __construct(CreditRepository $creditRepo, CreditService $creditService)
{
parent::__construct();
$this->creditRepo = $creditRepo;
+ $this->creditService = $creditService;
}
/**
@@ -106,46 +109,20 @@ class CreditController extends BaseController
return View::make('credit.edit', $data);
}
- public function store()
+ public function store(CreateCreditRequest $request)
{
- return $this->save();
- }
-
- public function update($publicId)
- {
- return $this->save($publicId);
- }
-
- private function save($publicId = null)
- {
- $rules = array(
- 'client' => 'required',
- 'amount' => 'required|positive',
- );
-
- $validator = Validator::make(Input::all(), $rules);
-
- if ($validator->fails()) {
- $url = $publicId ? 'credits/'.$publicId.'/edit' : 'credits/create';
-
- return Redirect::to($url)
- ->withErrors($validator)
- ->withInput();
- } else {
- $this->creditRepo->save($publicId, Input::all());
-
- $message = trans('texts.created_credit');
- Session::flash('message', $message);
-
- return Redirect::to('clients/'.Input::get('client'));
- }
+ $credit = $this->creditRepo->save($request->input());
+
+ Session::flash('message', trans('texts.created_credit'));
+
+ return redirect()->to($credit->client->getRoute());
}
public function bulk()
{
$action = Input::get('action');
- $ids = Input::get('id') ? Input::get('id') : Input::get('ids');
- $count = $this->creditRepo->bulk($ids, $action);
+ $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
+ $count = $this->creditService->bulk($ids, $action);
if ($count > 0) {
$message = Utils::pluralize($action.'d_credit', $count);
diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php
index 0b72d3669..a8b31fdb8 100644
--- a/app/Http/Controllers/DashboardController.php
+++ b/app/Http/Controllers/DashboardController.php
@@ -62,6 +62,7 @@ class DashboardController extends BaseController
->get();
$activities = Activity::where('activities.account_id', '=', Auth::user()->account_id)
+ ->with('client.contacts', 'user', 'invoice', 'payment', 'credit')
->where('activity_type_id', '>', 0)
->orderBy('created_at', 'desc')
->take(50)
diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php
index d3bb9b44b..2a9bae80e 100644
--- a/app/Http/Controllers/InvoiceApiController.php
+++ b/app/Http/Controllers/InvoiceApiController.php
@@ -58,7 +58,11 @@ class InvoiceApiController extends Controller
{
$data = Input::all();
$error = null;
-
+
+ if (isset($data['id']) || isset($data['public_id'])) {
+ die("We don't yet support updating invoices");
+ }
+
if (isset($data['email'])) {
$client = Client::scope()->whereHas('contacts', function($query) use ($data) {
$query->where('email', '=', $data['email']);
@@ -78,7 +82,7 @@ class InvoiceApiController extends Controller
}
$error = $this->clientRepo->getErrors($clientData);
if (!$error) {
- $client = $this->clientRepo->save(false, $clientData, false);
+ $client = $this->clientRepo->save($clientData);
}
}
} else if (isset($data['client_id'])) {
@@ -108,7 +112,7 @@ class InvoiceApiController extends Controller
} else {
$data = self::prepareData($data, $client);
$data['client_id'] = $client->id;
- $invoice = $this->invoiceRepo->save(false, $data, false);
+ $invoice = $this->invoiceRepo->save($data);
if (!isset($data['id'])) {
$invitation = Invitation::createNew();
diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php
index 54d74258c..4f6aefdd4 100644
--- a/app/Http/Controllers/InvoiceController.php
+++ b/app/Http/Controllers/InvoiceController.php
@@ -14,7 +14,6 @@ use Datatable;
use Request;
use DropdownButton;
use App\Models\Invoice;
-use App\Models\Invitation;
use App\Models\Client;
use App\Models\Account;
use App\Models\Product;
@@ -31,21 +30,27 @@ use App\Models\Gateway;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository;
-use App\Events\InvoiceViewed;
+use App\Events\InvoiceInvitationWasViewed;
+use App\Events\QuoteInvitationWasViewed;
+
+use App\Services\InvoiceService;
+use App\Http\Requests\SaveInvoiceRequest;
class InvoiceController extends BaseController
{
protected $mailer;
protected $invoiceRepo;
protected $clientRepo;
+ protected $invoiceService;
- public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo)
+ public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService)
{
parent::__construct();
$this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
$this->clientRepo = $clientRepo;
+ $this->invoiceService = $invoiceService;
}
public function index()
@@ -53,24 +58,20 @@ class InvoiceController extends BaseController
$data = [
'title' => trans('texts.invoices'),
'entityType' => ENTITY_INVOICE,
- 'columns' => Utils::trans(['checkbox', 'invoice_number', 'client', 'invoice_date', 'invoice_total', 'balance_due', 'due_date', 'status', 'action']),
+ 'columns' => Utils::trans([
+ 'checkbox',
+ 'invoice_number',
+ 'client',
+ 'invoice_date',
+ 'invoice_total',
+ 'balance_due',
+ 'due_date',
+ 'status',
+ 'action'
+ ]),
];
- $recurringInvoices = Invoice::scope()->where('is_recurring', '=', true);
-
- if (Session::get('show_trash:invoice')) {
- $recurringInvoices->withTrashed();
- } else {
- $recurringInvoices->join('clients', 'clients.id', '=', 'invoices.client_id')
- ->where('clients.deleted_at', '=', null);
- }
-
- if ($recurringInvoices->count() > 0) {
- $data['secEntityType'] = ENTITY_RECURRING_INVOICE;
- $data['secColumns'] = Utils::trans(['checkbox', 'frequency', 'client', 'start_date', 'end_date', 'invoice_total', 'action']);
- }
-
- return View::make('list', $data);
+ return response()->view('list', $data);
}
public function getDatatable($clientPublicId = null)
@@ -100,9 +101,6 @@ class InvoiceController extends BaseController
->addColumn('end_date', function ($model) { return Utils::fromSqlDate($model->end_date); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('dropdown', function ($model) {
- if ($model->is_deleted) {
- return '';
- }
$str = '
';
})
@@ -175,7 +182,9 @@ class PaymentController extends BaseController
// Handle offsite payments
if ($useToken || $paymentType != PAYMENT_TYPE_CREDIT_CARD
- || $gateway->id == GATEWAY_EWAY || $gateway->id == GATEWAY_TWO_CHECKOUT) {
+ || $gateway->id == GATEWAY_EWAY
+ || $gateway->id == GATEWAY_TWO_CHECKOUT
+ || $gateway->id == GATEWAY_PAYFAST) {
if (Session::has('error')) {
Session::reflash();
return Redirect::to('view/'.$invitationKey);
@@ -442,6 +451,8 @@ class PaymentController extends BaseController
$ref = $response->getData()['AccessCode'];
} elseif ($accountGateway->gateway_id == GATEWAY_TWO_CHECKOUT) {
$ref = $response->getData()['cart_order_id'];
+ } elseif ($accountGateway->gateway_id == GATEWAY_PAYFAST) {
+ $ref = $response->getData()['m_payment_id'];
} else {
$ref = $response->getTransactionReference();
}
@@ -540,49 +551,36 @@ class PaymentController extends BaseController
}
}
- public function store()
+ public function store(CreatePaymentRequest $request)
{
- return $this->save();
- }
+ $input = $request->input();
+ $payment = $this->paymentRepo->save($input);
- public function update($publicId)
- {
- return $this->save($publicId);
- }
-
- private function save($publicId = null)
- {
- if (!$publicId && $errors = $this->paymentRepo->getErrors(Input::all())) {
- $url = $publicId ? 'payments/'.$publicId.'/edit' : 'payments/create';
-
- return Redirect::to($url)
- ->withErrors($errors)
- ->withInput();
+ if (Input::get('email_receipt')) {
+ $this->contactMailer->sendPaymentConfirmation($payment);
+ Session::flash('message', trans('texts.created_payment_emailed_client'));
} else {
- $payment = $this->paymentRepo->save($publicId, Input::all());
-
- if ($publicId) {
- Session::flash('message', trans('texts.updated_payment'));
-
- return Redirect::to('payments/');
- } else {
- if (Input::get('email_receipt')) {
- $this->contactMailer->sendPaymentConfirmation($payment);
- Session::flash('message', trans('texts.created_payment_emailed_client'));
- } else {
- Session::flash('message', trans('texts.created_payment'));
- }
-
- return Redirect::to('clients/'.Input::get('client'));
- }
+ Session::flash('message', trans('texts.created_payment'));
}
+
+ return redirect()->to($payment->client->getRoute());
+ }
+
+ public function update(UpdatePaymentRequest $request)
+ {
+ $input = $request->input();
+ $payment = $this->paymentRepo->save($input);
+
+ Session::flash('message', trans('texts.updated_payment'));
+
+ return redirect()->to($payment->getRoute());
}
public function bulk()
{
$action = Input::get('action');
- $ids = Input::get('id') ? Input::get('id') : Input::get('ids');
- $count = $this->paymentRepo->bulk($ids, $action);
+ $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
+ $count = $this->paymentService->bulk($ids, $action);
if ($count > 0) {
$message = Utils::pluralize($action.'d_payment', $count);
diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php
index fdf9fa36e..a62b2f2d8 100644
--- a/app/Http/Controllers/PublicClientController.php
+++ b/app/Http/Controllers/PublicClientController.php
@@ -8,16 +8,18 @@ use Datatable;
use App\Models\Invitation;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository;
+use App\Ninja\Repositories\ActivityRepository;
class PublicClientController extends BaseController
{
private $invoiceRepo;
private $paymentRepo;
- public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo)
+ public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo)
{
$this->invoiceRepo = $invoiceRepo;
$this->paymentRepo = $paymentRepo;
+ $this->activityRepo = $activityRepo;
}
public function dashboard()
@@ -47,15 +49,22 @@ class PublicClientController extends BaseController
}
$invoice = $invitation->invoice;
- $query = DB::table('activities')
- ->join('clients', 'clients.id', '=', 'activities.client_id')
- ->where('activities.client_id', '=', $invoice->client_id)
- ->where('activities.adjustment', '!=', 0)
- ->select('activities.id', 'activities.message', 'activities.created_at', 'clients.currency_id', 'activities.balance', 'activities.adjustment');
+ $query = $this->activityRepo->findByClientId($invoice->client_id);
+ $query->where('activities.adjustment', '!=', 0);
return Datatable::query($query)
->addColumn('activities.id', function ($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); })
- ->addColumn('message', function ($model) { return strip_tags(Utils::decodeActivity($model->message)); })
+ ->addColumn('message', function ($model) {
+ $data = [
+ 'client' => Utils::getClientDisplayName($model),
+ 'user' => $model->is_system ? ('' . trans('texts.system') . '') : ($model->user_first_name . ' ' . $model->user_last_name),
+ 'invoice' => trans('texts.invoice') . ' ' . $model->invoice,
+ 'contact' => Utils::getClientDisplayName($model),
+ 'payment' => trans('texts.payment') . ($model->payment ? ' ' . $model->payment : ''),
+ ];
+
+ return trans("texts.activity_{$model->activity_type_id}", $data);
+ })
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('adjustment', function ($model) { return $model->adjustment != 0 ? Utils::wrapAdjustment($model->adjustment, $model->currency_id) : ''; })
->make();
diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php
index f78a693d6..d2b9db189 100644
--- a/app/Http/Controllers/QuoteController.php
+++ b/app/Http/Controllers/QuoteController.php
@@ -24,24 +24,24 @@ use App\Models\Invoice;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository;
-use App\Ninja\Repositories\TaxRateRepository;
-use App\Events\QuoteApproved;
+use App\Events\QuoteInvitationWasApproved;
+use App\Services\InvoiceService;
class QuoteController extends BaseController
{
protected $mailer;
protected $invoiceRepo;
protected $clientRepo;
- protected $taxRateRepo;
+ protected $invoiceService;
- public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, TaxRateRepository $taxRateRepo)
+ public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService)
{
parent::__construct();
$this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
$this->clientRepo = $clientRepo;
- $this->taxRateRepo = $taxRateRepo;
+ $this->invoiceService = $invoiceService;
}
public function index()
@@ -53,18 +53,19 @@ class QuoteController extends BaseController
$data = [
'title' => trans('texts.quotes'),
'entityType' => ENTITY_QUOTE,
- 'columns' => Utils::trans(['checkbox', 'quote_number', 'client', 'quote_date', 'quote_total', 'valid_until', 'status', 'action']),
+ 'columns' => Utils::trans([
+ 'checkbox',
+ 'quote_number',
+ 'client',
+ 'quote_date',
+ 'quote_total',
+ 'valid_until',
+ 'status',
+ 'action'
+ ]),
];
- /*
- if (Invoice::scope()->where('is_recurring', '=', true)->count() > 0)
- {
- $data['secEntityType'] = ENTITY_RECURRING_INVOICE;
- $data['secColumns'] = Utils::trans(['checkbox', 'frequency', 'client', 'start_date', 'end_date', 'quote_total', 'action']);
- }
- */
-
- return View::make('list', $data);
+ return response()->view('list', $data);
}
public function getDatatable($clientPublicId = null)
@@ -87,7 +88,8 @@ class QuoteController extends BaseController
$clientId = Client::getPrivateId($clientPublicId);
}
$invoice = $account->createInvoice(ENTITY_QUOTE, $clientId);
-
+ $invoice->public_id = 0;
+
$data = [
'entityType' => $invoice->getEntityType(),
'invoice' => $invoice,
@@ -123,22 +125,21 @@ class QuoteController extends BaseController
public function bulk()
{
- $action = Input::get('action');
+ $action = Input::get('bulk_action') ?: Input::get('action');;
if ($action == 'convert') {
$invoice = Invoice::with('invoice_items')->scope(Input::get('id'))->firstOrFail();
- $clone = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
+ $clone = $this->invoiceService->approveQuote($invoice);
Session::flash('message', trans('texts.converted_to_invoice'));
return Redirect::to('invoices/'.$clone->public_id);
}
-
- $statusId = Input::get('statusId');
- $ids = Input::get('id') ? Input::get('id') : Input::get('ids');
- $count = $this->invoiceRepo->bulk($ids, $action, $statusId);
+
+ $ids = Input::get('bulk_public_id') ?: (Input::get('public_id') ?: Input::get('ids'));
+ $count = $this->invoiceService->bulk($ids, $action);
if ($count > 0) {
- $key = $action == 'mark' ? "updated_quote" : "{$action}d_quote";
+ $key = $action == 'markSent' ? "updated_quote" : "{$action}d_quote";
$message = Utils::pluralize($key, $count);
Session::flash('message', $message);
}
@@ -155,19 +156,8 @@ class QuoteController extends BaseController
$invitation = Invitation::with('invoice.invoice_items', 'invoice.invitations')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
- if ($invoice->is_quote && !$invoice->quote_invoice_id) {
- Event::fire(new QuoteApproved($invoice));
- Activity::approveQuote($invitation);
-
- $invoice = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
- Session::flash('message', trans('texts.converted_to_invoice'));
-
- foreach ($invoice->invitations as $invitationClone) {
- if ($invitation->contact_id == $invitationClone->contact_id) {
- $invitationKey = $invitationClone->invitation_key;
- }
- }
- }
+ $invitationKey = $this->invoiceService->approveQuote($invoice, $invitation);
+ Session::flash('message', trans('texts.converted_to_invoice'));
return Redirect::to("view/{$invitationKey}");
}
diff --git a/app/Http/Controllers/RecurringInvoiceController.php b/app/Http/Controllers/RecurringInvoiceController.php
new file mode 100644
index 000000000..c59370647
--- /dev/null
+++ b/app/Http/Controllers/RecurringInvoiceController.php
@@ -0,0 +1,36 @@
+invoiceRepo = $invoiceRepo;
+ }
+
+ public function index()
+ {
+ $data = [
+ 'title' => trans('texts.recurring_invoices'),
+ 'entityType' => ENTITY_RECURRING_INVOICE,
+ 'columns' => Utils::trans([
+ 'checkbox',
+ 'frequency',
+ 'client',
+ 'start_date',
+ 'end_date',
+ 'invoice_total',
+ 'action'
+ ])
+ ];
+
+ return response()->view('list', $data);
+ }
+
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php
index 0fec382e7..b357f8d82 100644
--- a/app/Http/Controllers/TaskController.php
+++ b/app/Http/Controllers/TaskController.php
@@ -156,7 +156,7 @@ class TaskController extends BaseController
*/
public function edit($publicId)
{
- $task = Task::scope($publicId)->with('client', 'invoice')->firstOrFail();
+ $task = Task::scope($publicId)->with('client', 'invoice')->withTrashed()->firstOrFail();
$actions = [];
if ($task->invoice) {
@@ -240,7 +240,7 @@ class TaskController extends BaseController
public function bulk()
{
$action = Input::get('action');
- $ids = Input::get('id') ? Input::get('id') : Input::get('ids');
+ $ids = Input::get('public_id') ?: (Input::get('id') ?: Input::get('ids'));
if ($action == 'stop') {
$this->taskRepo->save($ids, ['action' => $action]);
diff --git a/app/Http/Controllers/TokenController.php b/app/Http/Controllers/TokenController.php
index 9feca2c1a..47885fe84 100644
--- a/app/Http/Controllers/TokenController.php
+++ b/app/Http/Controllers/TokenController.php
@@ -145,13 +145,9 @@ class TokenController extends BaseController
if ($tokenPublicId) {
$token->name = trim(Input::get('name'));
} else {
- $lastToken = AccountToken::withTrashed()->where('account_id', '=', Auth::user()->account_id)
- ->orderBy('public_id', 'DESC')->first();
-
$token = AccountToken::createNew();
$token->name = trim(Input::get('name'));
$token->token = str_random(RANDOM_KEY_LENGTH);
- $token->public_id = $lastToken ? $lastToken->public_id + 1 : 1;
}
$token->save();
diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php
index 903d6f028..6af49e9b2 100644
--- a/app/Http/Middleware/VerifyCsrfToken.php
+++ b/app/Http/Middleware/VerifyCsrfToken.php
@@ -34,6 +34,12 @@ class VerifyCsrfToken extends BaseVerifier {
}
}
+ if ($request->is('login')) {
+ if (env(API_SECRET) && $request->api_secret === env(API_SECRET)) {
+ return $next($request);
+ }
+ }
+
return parent::handle($request, $next);
}
diff --git a/app/Http/Requests/CreateClientRequest.php b/app/Http/Requests/CreateClientRequest.php
new file mode 100644
index 000000000..2d1716cc1
--- /dev/null
+++ b/app/Http/Requests/CreateClientRequest.php
@@ -0,0 +1,44 @@
+ 'valid_contacts',
+ ];
+ }
+
+ public function validator($factory)
+ {
+ // support submiting the form with a single client record
+ $input = $this->input();
+ if (isset($input['contact'])) {
+ $input['contacts'] = [$input['contact']];
+ unset($input['contact']);
+ $this->replace($input);
+ }
+
+ return $factory->make(
+ $this->input(), $this->container->call([$this, 'rules']), $this->messages()
+ );
+ }
+}
diff --git a/app/Http/Requests/CreateCreditRequest.php b/app/Http/Requests/CreateCreditRequest.php
new file mode 100644
index 000000000..f2dc44d31
--- /dev/null
+++ b/app/Http/Requests/CreateCreditRequest.php
@@ -0,0 +1,30 @@
+ 'required',
+ 'amount' => 'required|positive',
+ ];
+ }
+}
diff --git a/app/Http/Requests/CreatePaymentRequest.php b/app/Http/Requests/CreatePaymentRequest.php
new file mode 100644
index 000000000..d0c814687
--- /dev/null
+++ b/app/Http/Requests/CreatePaymentRequest.php
@@ -0,0 +1,44 @@
+input();
+ $rules = array(
+ 'client' => 'required',
+ 'invoice' => 'required',
+ 'amount' => 'required',
+ );
+
+ if ($input['payment_type_id'] == PAYMENT_TYPE_CREDIT) {
+ $rules['payment_type_id'] = 'has_credit:'.$input['client'].','.$input['amount'];
+ }
+
+ if (isset($input['invoice']) && $input['invoice']) {
+ $invoice = Invoice::scope($input['invoice'])->firstOrFail();
+ $rules['amount'] .= "|less_than:{$invoice->balance}";
+ }
+
+ return $rules;
+ }
+}
diff --git a/app/Http/Requests/SaveInvoiceRequest.php b/app/Http/Requests/SaveInvoiceRequest.php
new file mode 100644
index 000000000..1e18c6b5f
--- /dev/null
+++ b/app/Http/Requests/SaveInvoiceRequest.php
@@ -0,0 +1,44 @@
+ 'valid_contacts',
+ 'invoice_number' => 'required|unique:invoices,invoice_number,'.$invoiceId.',id,account_id,'.Auth::user()->account_id,
+ 'discount' => 'positive',
+ ];
+
+ /* There's a problem parsing the dates
+ if (Request::get('is_recurring') && Request::get('start_date') && Request::get('end_date')) {
+ $rules['end_date'] = 'after' . Request::get('start_date');
+ }
+ */
+
+ return $rules;
+ }
+}
diff --git a/app/Http/Requests/UpdateClientRequest.php b/app/Http/Requests/UpdateClientRequest.php
new file mode 100644
index 000000000..b73e019c4
--- /dev/null
+++ b/app/Http/Requests/UpdateClientRequest.php
@@ -0,0 +1,29 @@
+ 'valid_contacts',
+ ];
+ }
+}
diff --git a/app/Http/Requests/UpdatePaymentRequest.php b/app/Http/Requests/UpdatePaymentRequest.php
new file mode 100644
index 000000000..83b192280
--- /dev/null
+++ b/app/Http/Requests/UpdatePaymentRequest.php
@@ -0,0 +1,28 @@
+ 'auth'], function() {
Route::get('api/invoices/{client_id?}', array('as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable'));
Route::get('invoices/create/{client_id?}', 'InvoiceController@create');
Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring');
+ Route::get('recurring_invoices', 'RecurringInvoiceController@index');
Route::get('invoices/{public_id}/clone', 'InvoiceController@cloneInvoice');
Route::post('invoices/bulk', 'InvoiceController@bulk');
+ Route::post('recurring_invoices/bulk', 'InvoiceController@bulk');
Route::get('quotes/create/{client_id?}', 'QuoteController@create');
Route::get('quotes/{public_id}/clone', 'InvoiceController@cloneInvoice');
@@ -188,6 +190,7 @@ Route::group(['middleware' => 'auth'], function() {
Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
{
Route::resource('ping', 'ClientApiController@ping');
+ Route::get('accounts', 'AccountApiController@index');
Route::resource('clients', 'ClientApiController');
Route::get('quotes/{client_id?}', 'QuoteApiController@index');
Route::resource('quotes', 'QuoteApiController');
@@ -274,6 +277,9 @@ if (!defined('CONTACT_EMAIL')) {
define('ACCOUNT_API_TOKENS', 'api_tokens');
define('ACCOUNT_CUSTOMIZE_DESIGN', 'customize_design');
+ define('ACTION_RESTORE', 'restore');
+ define('ACTION_ARCHIVE', 'archive');
+ define('ACTION_DELETE', 'delete');
define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);
@@ -287,12 +293,12 @@ if (!defined('CONTACT_EMAIL')) {
define('ACTIVITY_TYPE_DELETE_INVOICE', 9);
define('ACTIVITY_TYPE_CREATE_PAYMENT', 10);
- define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
+ //define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
- define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
+ //define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16);
define('ACTIVITY_TYPE_DELETE_CREDIT', 17);
@@ -376,6 +382,7 @@ if (!defined('CONTACT_EMAIL')) {
define('GATEWAY_AUTHORIZE_NET', 1);
define('GATEWAY_EWAY', 4);
define('GATEWAY_AUTHORIZE_NET_SIM', 2);
+ define('GATEWAY_PAYFAST', 13);
define('GATEWAY_PAYPAL_EXPRESS', 17);
define('GATEWAY_PAYPAL_PRO', 18);
define('GATEWAY_STRIPE', 23);
@@ -399,7 +406,7 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG');
define('NINJA_WEB_URL', 'https://www.invoiceninja.com');
define('NINJA_APP_URL', 'https://app.invoiceninja.com');
- define('NINJA_VERSION', '2.4.3');
+ define('NINJA_VERSION', '2.4.5');
define('NINJA_DATE', '2000-01-01');
define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com');
@@ -409,7 +416,8 @@ if (!defined('CONTACT_EMAIL')) {
define('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html');
define('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/single/browser/v1/');
define('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php');
- define('REFERRAL_PROGRAM_URL', false);
+ define('GITTER_ROOM', 'hillelcoren/invoice-ninja');
+ define('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/affiliates/');
define('COUNT_FREE_DESIGNS', 4);
define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design
@@ -431,6 +439,7 @@ if (!defined('CONTACT_EMAIL')) {
define('TEST_USERNAME', 'user@example.com');
define('TEST_PASSWORD', 'password');
+ define('API_SECRET', 'API_SECRET');
define('TOKEN_BILLING_DISABLED', 1);
define('TOKEN_BILLING_OPT_IN', 2);
diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php
index adbd5bcff..8b0a148c5 100644
--- a/app/Libraries/Utils.php
+++ b/app/Libraries/Utils.php
@@ -65,6 +65,25 @@ class Utils
return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'] == 'true';
}
+ public static function isOAuthEnabled()
+ {
+ $providers = [
+ SOCIAL_GOOGLE,
+ SOCIAL_FACEBOOK,
+ SOCIAL_GITHUB,
+ SOCIAL_LINKEDIN
+ ];
+
+ foreach ($providers as $provider) {
+ $key = strtoupper($provider) . '_CLIENT_ID';
+ if (isset($_ENV[$key]) && $_ENV[$key]) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public static function allowNewAccounts()
{
return Utils::isNinja() || Auth::check();
@@ -496,32 +515,15 @@ class Utils
}
}
- public static function encodeActivity($person = null, $action, $entity = null, $otherPerson = null)
+ public static function getPersonDisplayName($firstName, $lastName, $email)
{
- $person = $person ? $person->getDisplayName() : 'System';
- $entity = $entity ? $entity->getActivityKey() : '';
- $otherPerson = $otherPerson ? 'to '.$otherPerson->getDisplayName() : '';
- $token = Session::get('token_id') ? ' ('.trans('texts.token').')' : '';
-
- return trim("$person $token $action $entity $otherPerson");
- }
-
- public static function decodeActivity($message)
- {
- $pattern = '/\[([\w]*):([\d]*):(.*)\]/i';
- preg_match($pattern, $message, $matches);
-
- if (count($matches) > 0) {
- $match = $matches[0];
- $type = $matches[1];
- $publicId = $matches[2];
- $name = $matches[3];
-
- $link = link_to($type.'s/'.$publicId, $name);
- $message = str_replace($match, "$type $link", $message);
+ if ($firstName || $lastName) {
+ return $firstName.' '.$lastName;
+ } elseif ($email) {
+ return $email;
+ } else {
+ return trans('texts.guest');
}
-
- return $message;
}
public static function generateLicense()
@@ -598,7 +600,7 @@ class Utils
foreach ($data as $key => $val) {
if (is_array($val)) {
if ($key == 'account' || isset($mapped[$key])) {
- unset($data[$key]);
+ // do nothing
} else {
$mapped[$key] = true;
$data[$key] = Utils::hideIds($val, $mapped);
@@ -795,4 +797,43 @@ class Utils
$adjustment = Utils::formatMoney($adjustment, $currencyId);
return "$adjustment
";
}
+
+ public static function copyContext($entity1, $entity2)
+ {
+ if (!$entity2) {
+ return $entity1;
+ }
+
+ $fields = [
+ 'contact_id',
+ 'payment_id',
+ 'invoice_id',
+ 'credit_id',
+ 'invitation_id'
+ ];
+
+ $fields1 = $entity1->getAttributes();
+ $fields2 = $entity2->getAttributes();
+
+ foreach ($fields as $field) {
+ if (isset($fields2[$field]) && $fields2[$field]) {
+ $entity1->$field = $entity2->$field;
+ }
+ }
+
+ return $entity1;
+ }
+
+ public static function withinPastYear($date)
+ {
+ if (!$date || $date == '0000-00-00') {
+ return false;
+ }
+
+ $today = new DateTime('now');
+ $datePaid = DateTime::createFromFormat('Y-m-d', $date);
+ $interval = $today->diff($date);
+
+ return $interval->y == 0;
+ }
}
diff --git a/app/Listeners/ActivityListener.php b/app/Listeners/ActivityListener.php
new file mode 100644
index 000000000..52c2e26f9
--- /dev/null
+++ b/app/Listeners/ActivityListener.php
@@ -0,0 +1,335 @@
+activityRepo = $activityRepo;
+ }
+
+ // Clients
+ public function createdClient(ClientWasCreated $event)
+ {
+ $this->activityRepo->create(
+ $event->client,
+ ACTIVITY_TYPE_CREATE_CLIENT
+ );
+ }
+
+ public function deletedClient(ClientWasDeleted $event)
+ {
+ $this->activityRepo->create(
+ $event->client,
+ ACTIVITY_TYPE_DELETE_CLIENT
+ );
+ }
+
+ public function archivedClient(ClientWasArchived $event)
+ {
+ if ($event->client->is_deleted) {
+ return;
+ }
+
+ $this->activityRepo->create(
+ $event->client,
+ ACTIVITY_TYPE_ARCHIVE_CLIENT
+ );
+ }
+
+ public function restoredClient(ClientWasRestored $event)
+ {
+ $this->activityRepo->create(
+ $event->client,
+ ACTIVITY_TYPE_RESTORE_CLIENT
+ );
+ }
+
+ // Invoices
+ public function createdInvoice(InvoiceWasCreated $event)
+ {
+ $this->activityRepo->create(
+ $event->invoice,
+ ACTIVITY_TYPE_CREATE_INVOICE,
+ $event->invoice->getAdjustment()
+ );
+ }
+
+ public function updatedInvoice(InvoiceWasUpdated $event)
+ {
+ if (! $event->invoice->isChanged()) {
+ return;
+ }
+
+ $backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($event->invoice->id);
+
+ $activity = $this->activityRepo->create(
+ $event->invoice,
+ ACTIVITY_TYPE_UPDATE_INVOICE,
+ $event->invoice->getAdjustment()
+ );
+
+ $activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
+ $activity->save();
+ }
+
+ public function deletedInvoice(InvoiceWasDeleted $event)
+ {
+ $invoice = $event->invoice;
+
+ $this->activityRepo->create(
+ $invoice,
+ ACTIVITY_TYPE_DELETE_INVOICE,
+ $invoice->affectsBalance() ? $invoice->balance * -1 : 0,
+ $invoice->affectsBalance() ? $invoice->getAmountPaid() * -1 : 0
+ );
+ }
+
+ public function archivedInvoice(InvoiceWasArchived $event)
+ {
+ if ($event->invoice->is_deleted) {
+ return;
+ }
+
+ $this->activityRepo->create(
+ $event->invoice,
+ ACTIVITY_TYPE_ARCHIVE_INVOICE
+ );
+ }
+
+ public function restoredInvoice(InvoiceWasRestored $event)
+ {
+ $invoice = $event->invoice;
+
+ $this->activityRepo->create(
+ $invoice,
+ ACTIVITY_TYPE_RESTORE_INVOICE,
+ $invoice->affectsBalance() && $event->fromDeleted ? $invoice->balance : 0,
+ $invoice->affectsBalance() && $event->fromDeleted ? $invoice->getAmountPaid() : 0
+ );
+ }
+
+ public function emailedInvoice(InvoiceInvitationWasEmailed $event)
+ {
+ $this->activityRepo->create(
+ $event->invitation->invoice,
+ ACTIVITY_TYPE_EMAIL_INVOICE,
+ false,
+ false,
+ $event->invitation
+ );
+ }
+
+ public function viewedInvoice(InvoiceInvitationWasViewed $event)
+ {
+ $this->activityRepo->create(
+ $event->invoice,
+ ACTIVITY_TYPE_VIEW_INVOICE,
+ false,
+ false,
+ $event->invitation
+ );
+ }
+
+ // Quotes
+ public function createdQuote(QuoteWasCreated $event)
+ {
+ $this->activityRepo->create(
+ $event->quote,
+ ACTIVITY_TYPE_CREATE_QUOTE
+ );
+ }
+
+ public function updatedQuote(QuoteWasUpdated $event)
+ {
+ if (! $event->quote->isChanged()) {
+ return;
+ }
+
+ $backupQuote = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($event->quote->id);
+
+ $activity = $this->activityRepo->create(
+ $event->quote,
+ ACTIVITY_TYPE_UPDATE_QUOTE
+ );
+
+ $activity->json_backup = $backupQuote->hidePrivateFields()->toJSON();
+ $activity->save();
+ }
+
+ public function deletedQuote(QuoteWasDeleted $event)
+ {
+ $this->activityRepo->create(
+ $event->quote,
+ ACTIVITY_TYPE_DELETE_QUOTE
+ );
+ }
+
+ public function archivedQuote(QuoteWasArchived $event)
+ {
+ if ($event->quote->is_deleted) {
+ return;
+ }
+
+ $this->activityRepo->create(
+ $event->quote,
+ ACTIVITY_TYPE_ARCHIVE_QUOTE
+ );
+ }
+
+ public function restoredQuote(QuoteWasRestored $event)
+ {
+ $this->activityRepo->create(
+ $event->quote,
+ ACTIVITY_TYPE_RESTORE_QUOTE
+ );
+ }
+
+ public function emailedQuote(QuoteInvitationWasEmailed $event)
+ {
+ $this->activityRepo->create(
+ $event->invitation->invoice,
+ ACTIVITY_TYPE_EMAIL_QUOTE,
+ false,
+ false,
+ $event->invitation
+ );
+ }
+
+ public function viewedQuote(QuoteInvitationWasViewed $event)
+ {
+ $this->activityRepo->create(
+ $event->quote,
+ ACTIVITY_TYPE_VIEW_QUOTE,
+ false,
+ false,
+ $event->invitation
+ );
+ }
+
+ public function approvedQuote(QuoteInvitationWasApproved $event)
+ {
+ $this->activityRepo->create(
+ $event->quote,
+ ACTIVITY_TYPE_APPROVE_QUOTE,
+ false,
+ false,
+ $event->invitation
+ );
+ }
+
+ // Credits
+ public function createdCredit(CreditWasCreated $event)
+ {
+ $this->activityRepo->create(
+ $event->credit,
+ ACTIVITY_TYPE_CREATE_CREDIT
+ );
+ }
+
+ public function deletedCredit(CreditWasDeleted $event)
+ {
+ $this->activityRepo->create(
+ $event->credit,
+ ACTIVITY_TYPE_DELETE_CREDIT
+ );
+ }
+
+ public function archivedCredit(CreditWasArchived $event)
+ {
+ if ($event->credit->is_deleted) {
+ return;
+ }
+
+ $this->activityRepo->create(
+ $event->credit,
+ ACTIVITY_TYPE_ARCHIVE_CREDIT
+ );
+ }
+
+ public function restoredCredit(CreditWasRestored $event)
+ {
+ $this->activityRepo->create(
+ $event->credit,
+ ACTIVITY_TYPE_RESTORE_CREDIT
+ );
+ }
+
+ // Payments
+ public function createdPayment(PaymentWasCreated $event)
+ {
+ $this->activityRepo->create(
+ $event->payment,
+ ACTIVITY_TYPE_CREATE_PAYMENT,
+ $event->payment->amount * -1,
+ $event->payment->amount
+ );
+ }
+
+ public function deletedPayment(PaymentWasDeleted $event)
+ {
+ $payment = $event->payment;
+
+ $this->activityRepo->create(
+ $payment,
+ ACTIVITY_TYPE_DELETE_PAYMENT,
+ $payment->amount,
+ $payment->amount * -1
+ );
+ }
+
+ public function archivedPayment(PaymentWasArchived $event)
+ {
+ if ($event->payment->is_deleted) {
+ return;
+ }
+
+ $this->activityRepo->create(
+ $event->payment,
+ ACTIVITY_TYPE_ARCHIVE_PAYMENT
+ );
+ }
+
+ public function restoredPayment(PaymentWasRestored $event)
+ {
+ $payment = $event->payment;
+
+ $this->activityRepo->create(
+ $payment,
+ ACTIVITY_TYPE_RESTORE_PAYMENT,
+ $event->fromDeleted ? $payment->amount * -1 : 0,
+ $event->fromDeleted ? $payment->amount : 0
+ );
+ }
+}
diff --git a/app/Listeners/CreditListener.php b/app/Listeners/CreditListener.php
new file mode 100644
index 000000000..bed71a47f
--- /dev/null
+++ b/app/Listeners/CreditListener.php
@@ -0,0 +1,33 @@
+creditRepo = $creditRepo;
+ }
+
+ public function deletedPayment(PaymentWasDeleted $event)
+ {
+ $payment = $event->payment;
+
+ // if the payment was from a credit we need to refund the credit
+ if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) {
+ return;
+ }
+
+ $credit = Credit::createNew();
+ $credit->client_id = $payment->client_id;
+ $credit->credit_date = Carbon::now()->toDateTimeString();
+ $credit->balance = $credit->amount = $payment->amount;
+ $credit->private_notes = $payment->transaction_reference;
+ $credit->save();
+ }
+}
diff --git a/app/Listeners/HandleInvoicePaid.php b/app/Listeners/HandleInvoicePaid.php
deleted file mode 100644
index d072abd00..000000000
--- a/app/Listeners/HandleInvoicePaid.php
+++ /dev/null
@@ -1,48 +0,0 @@
-userMailer = $userMailer;
- $this->contactMailer = $contactMailer;
- }
-
- /**
- * Handle the event.
- *
- * @param InvoicePaid $event
- * @return void
- */
- public function handle(InvoicePaid $event)
- {
- $payment = $event->payment;
- $invoice = $payment->invoice;
-
- $this->contactMailer->sendPaymentConfirmation($payment);
-
- foreach ($invoice->account->users as $user)
- {
- if ($user->{'notify_paid'})
- {
- $this->userMailer->sendNotification($user, $invoice, 'paid', $payment);
- }
- }
- }
-
-}
diff --git a/app/Listeners/HandleInvoiceSent.php b/app/Listeners/HandleInvoiceSent.php
deleted file mode 100644
index 119936e95..000000000
--- a/app/Listeners/HandleInvoiceSent.php
+++ /dev/null
@@ -1,42 +0,0 @@
-userMailer = $userMailer;
- }
-
- /**
- * Handle the event.
- *
- * @param InvoiceSent $event
- * @return void
- */
- public function handle(InvoiceSent $event)
- {
- $invoice = $event->invoice;
-
- foreach ($invoice->account->users as $user)
- {
- if ($user->{'notify_sent'})
- {
- $this->userMailer->sendNotification($user, $invoice, 'sent');
- }
- }
- }
-
-}
diff --git a/app/Listeners/HandleInvoiceViewed.php b/app/Listeners/HandleInvoiceViewed.php
deleted file mode 100644
index 47ee62a85..000000000
--- a/app/Listeners/HandleInvoiceViewed.php
+++ /dev/null
@@ -1,42 +0,0 @@
-userMailer = $userMailer;
- }
-
- /**
- * Handle the event.
- *
- * @param InvoiceViewed $event
- * @return void
- */
- public function handle(InvoiceViewed $event)
- {
- $invoice = $event->invoice;
-
- foreach ($invoice->account->users as $user)
- {
- if ($user->{'notify_viewed'})
- {
- $this->userMailer->sendNotification($user, $invoice, 'viewed');
- }
- }
- }
-
-}
diff --git a/app/Listeners/HandleQuoteApproved.php b/app/Listeners/HandleQuoteApproved.php
deleted file mode 100644
index 3a49aa9b5..000000000
--- a/app/Listeners/HandleQuoteApproved.php
+++ /dev/null
@@ -1,42 +0,0 @@
-userMailer = $userMailer;
- }
-
- /**
- * Handle the event.
- *
- * @param QuoteApproved $event
- * @return void
- */
- public function handle(QuoteApproved $event)
- {
- $invoice = $event->invoice;
-
- foreach ($invoice->account->users as $user)
- {
- if ($user->{'notify_approved'})
- {
- $this->userMailer->sendNotification($user, $invoice, 'approved');
- }
- }
- }
-
-}
diff --git a/app/Listeners/HandleUserSignedUp.php b/app/Listeners/HandleUserSignedUp.php
index 5bc4eab28..08961e161 100644
--- a/app/Listeners/HandleUserSignedUp.php
+++ b/app/Listeners/HandleUserSignedUp.php
@@ -41,12 +41,6 @@ class HandleUserSignedUp
$this->accountRepo->registerNinjaUser($user);
}
- $activities = Activity::scope()->get();
- foreach ($activities as $activity) {
- $activity->message = str_replace('Guest', $user->getFullName(), $activity->message);
- $activity->save();
- }
-
session([SESSION_COUNTER => -1]);
}
}
diff --git a/app/Listeners/InvoiceListener.php b/app/Listeners/InvoiceListener.php
new file mode 100644
index 000000000..0a5a19c5b
--- /dev/null
+++ b/app/Listeners/InvoiceListener.php
@@ -0,0 +1,58 @@
+payment;
+ $invoice = $payment->invoice;
+ $adjustment = $payment->amount * -1;
+ $partial = max(0, $invoice->partial - $payment->amount);
+
+ $invoice->updateBalances($adjustment, $partial);
+ $invoice->updatePaidStatus();
+ }
+
+ public function updatedInvoice(InvoiceWasUpdated $event)
+ {
+ $invoice = $event->invoice;
+ $invoice->updatePaidStatus();
+ }
+
+ public function viewedInvoice(InvoiceInvitationWasViewed $event)
+ {
+ $invitation = $event->invitation;
+ $invitation->markViewed();
+ }
+
+ public function deletedPayment(PaymentWasDeleted $event)
+ {
+ $payment = $event->payment;
+ $invoice = $payment->invoice;
+ $adjustment = $payment->amount;
+
+ $invoice->updateBalances($adjustment);
+ $invoice->updatePaidStatus();
+ }
+
+ public function restoredPayment(PaymentWasRestored $event)
+ {
+ if ( ! $event->fromDeleted) {
+ return;
+ }
+
+ $payment = $event->payment;
+ $invoice = $payment->invoice;
+ $adjustment = $payment->amount * -1;
+
+ $invoice->updateBalances($adjustment);
+ $invoice->updatePaidStatus();
+ }
+}
diff --git a/app/Listeners/NotificationListener.php b/app/Listeners/NotificationListener.php
new file mode 100644
index 000000000..aba304457
--- /dev/null
+++ b/app/Listeners/NotificationListener.php
@@ -0,0 +1,71 @@
+userMailer = $userMailer;
+ $this->contactMailer = $contactMailer;
+ }
+
+ private function sendEmails($invoice, $type, $payment = null)
+ {
+ foreach ($invoice->account->users as $user)
+ {
+ if ($user->{"notify_{$type}"})
+ {
+ $this->userMailer->sendNotification($user, $invoice, $type, $payment);
+ }
+ }
+ }
+
+ public function emailedInvoice(InvoiceWasEmailed $event)
+ {
+ $this->sendEmails($event->invoice, 'sent');
+ }
+
+ public function emailedQuote(QuoteWasEmailed $event)
+ {
+ $this->sendEmails($event->quote, 'sent');
+ }
+
+ public function viewedInvoice(InvoiceInvitationWasViewed $event)
+ {
+ $this->sendEmails($event->invoice, 'viewed');
+ }
+
+ public function viewedQuote(QuoteInvitationWasViewed $event)
+ {
+ $this->sendEmails($event->quote, 'viewed');
+ }
+
+ public function approvedQuote(QuoteInvitationWasApproved $event)
+ {
+ $this->sendEmails($event->quote, 'approved');
+ }
+
+ public function createdPayment(PaymentWasCreated $event)
+ {
+ // only send emails for online payments
+ if ( ! $event->payment->account_gateway_id) {
+ return;
+ }
+
+ $this->contactMailer->sendPaymentConfirmation($event->payment);
+ $this->sendEmails($event->payment->invoice, 'paid', $event->payment);
+ }
+
+}
\ No newline at end of file
diff --git a/app/Listeners/QuoteListener.php b/app/Listeners/QuoteListener.php
new file mode 100644
index 000000000..5dfa0e45a
--- /dev/null
+++ b/app/Listeners/QuoteListener.php
@@ -0,0 +1,15 @@
+invitation;
+ $invitation->markViewed();
+ }
+}
diff --git a/app/Listeners/SubscriptionListener.php b/app/Listeners/SubscriptionListener.php
new file mode 100644
index 000000000..7ef7a1116
--- /dev/null
+++ b/app/Listeners/SubscriptionListener.php
@@ -0,0 +1,47 @@
+checkSubscriptions(ACTIVITY_TYPE_CREATE_CLIENT, $event->client);
+ }
+
+ public function createdQuote(QuoteWasCreated $event)
+ {
+ $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_QUOTE, $event->quote);
+ }
+
+ public function createdPayment(PaymentWasCreated $event)
+ {
+ $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_PAYMENT, $event->payment);
+ }
+
+ public function createdCredit(CreditWasCreated $event)
+ {
+ $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CREDIT, $event->credit);
+ }
+
+ public function createdInvoice(InvoiceWasCreated $event)
+ {
+ $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_INVOICE, $event->invoice);
+ }
+
+ private function checkSubscriptions($activityTypeId, $entity)
+ {
+ $subscription = $entity->account->getSubscription($activityTypeId);
+
+ if ($subscription) {
+ Utils::notifyZapier($subscription, $entity);
+ }
+ }
+}
diff --git a/app/Listeners/TaskListener.php b/app/Listeners/TaskListener.php
new file mode 100644
index 000000000..b52c2fd5f
--- /dev/null
+++ b/app/Listeners/TaskListener.php
@@ -0,0 +1,14 @@
+invoice->id)
+ ->update(['invoice_id' => null]);
+ }
+}
diff --git a/app/Models/Account.php b/app/Models/Account.php
index c768f0bc6..fe16924f6 100644
--- a/app/Models/Account.php
+++ b/app/Models/Account.php
@@ -251,7 +251,7 @@ class Account extends Eloquent
$invoice->start_date = Utils::today();
$invoice->invoice_design_id = $this->invoice_design_id;
$invoice->client_id = $clientId;
-
+
if ($entityType === ENTITY_RECURRING_INVOICE) {
$invoice->invoice_number = microtime(true);
$invoice->is_recurring = true;
@@ -316,7 +316,7 @@ class Account extends Eloquent
$pattern = str_replace($search, $replace, $pattern);
- if ($invoice->client->id) {
+ if ($invoice->client_id) {
$pattern = $this->getClientInvoiceNumber($pattern, $invoice);
}
@@ -330,13 +330,11 @@ class Account extends Eloquent
}
$search = [
- //'{$clientId}',
'{$custom1}',
'{$custom2}',
];
$replace = [
- //str_pad($client->public_id, 3, '0', STR_PAD_LEFT),
$invoice->client->custom_value1,
$invoice->client->custom_value2,
];
@@ -344,17 +342,6 @@ class Account extends Eloquent
return str_replace($search, $replace, $pattern);
}
- // if we're using a pattern we don't know the next number until a client
- // is selected, to support this the default value is blank
- public function getDefaultInvoiceNumber($invoice = false)
- {
- if ($this->hasClientNumberPattern($invoice)) {
- return false;
- }
-
- return $this->getNextInvoiceNumber($invoice);
- }
-
public function getCounter($isQuote)
{
return $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter;
@@ -372,7 +359,7 @@ class Account extends Eloquent
// confirm the invoice number isn't already taken
do {
- $number = $prefix.str_pad($counter, 4, "0", STR_PAD_LEFT);
+ $number = $prefix.str_pad($counter, 4, '0', STR_PAD_LEFT);
$check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first();
$counter++;
$counterOffset++;
@@ -503,17 +490,11 @@ class Account extends Eloquent
$datePaid = $this->pro_plan_paid;
- if (!$datePaid || $datePaid == '0000-00-00') {
- return false;
- } elseif ($datePaid == NINJA_DATE) {
+ if ($datePaid == NINJA_DATE) {
return true;
}
- $today = new DateTime('now');
- $datePaid = DateTime::createFromFormat('Y-m-d', $datePaid);
- $interval = $today->diff($datePaid);
-
- return $interval->y == 0;
+ return Utils::withinPastYear($datePaid);
}
public function isWhiteLabel()
diff --git a/app/Models/AccountGateway.php b/app/Models/AccountGateway.php
index 16d72076c..fe1afbc69 100644
--- a/app/Models/AccountGateway.php
+++ b/app/Models/AccountGateway.php
@@ -1,5 +1,6 @@
gateway_id);
}
- public function isPaymentType($type) {
+ public function isPaymentType($type)
+ {
return $this->getPaymentType() == $type;
}
- public function isGateway($gatewayId) {
+ public function isGateway($gatewayId)
+ {
return $this->gateway_id == $gatewayId;
}
+
+ public function setConfig($config)
+ {
+ $this->config = Crypt::encrypt(json_encode($config));
+ }
+
+ public function getConfig()
+ {
+ return json_decode(Crypt::decrypt($this->config));
+ }
}
diff --git a/app/Models/Activity.php b/app/Models/Activity.php
index edb20a7c9..fdf41ce54 100644
--- a/app/Models/Activity.php
+++ b/app/Models/Activity.php
@@ -23,480 +23,60 @@ class Activity extends Eloquent
public function user()
{
- return $this->belongsTo('App\Models\User');
+ return $this->belongsTo('App\Models\User')->withTrashed();
}
- private static function getBlank($entity = false)
+ public function contact()
{
- $activity = new Activity();
-
- if ($entity) {
- $activity->user_id = $entity instanceof User ? $entity->id : $entity->user_id;
- $activity->account_id = $entity->account_id;
- } elseif (Auth::check()) {
- $activity->user_id = Auth::user()->id;
- $activity->account_id = Auth::user()->account_id;
- } else {
- Utils::fatalError();
- }
-
- $activity->token_id = Session::get('token_id', null);
- $activity->ip = Request::getClientIp();
-
- return $activity;
+ return $this->belongsTo('App\Models\Contact')->withTrashed();
}
- public static function createClient($client, $notify = true)
+ public function client()
{
- $activity = Activity::getBlank();
- $activity->client_id = $client->id;
- $activity->activity_type_id = ACTIVITY_TYPE_CREATE_CLIENT;
- $activity->message = Utils::encodeActivity(Auth::user(), 'created', $client);
- $activity->save();
-
- if ($notify) {
- Activity::checkSubscriptions(EVENT_CREATE_CLIENT, $client);
- }
+ return $this->belongsTo('App\Models\Client')->withTrashed();
}
- public static function updateClient($client)
+ public function invoice()
{
- if ($client->isBeingDeleted()) {
- $activity = Activity::getBlank();
- $activity->client_id = $client->id;
- $activity->activity_type_id = ACTIVITY_TYPE_DELETE_CLIENT;
- $activity->message = Utils::encodeActivity(Auth::user(), 'deleted', $client);
- $activity->balance = $client->balance;
- $activity->save();
- }
+ return $this->belongsTo('App\Models\Invoice')->withTrashed();
}
- public static function archiveClient($client)
+ public function credit()
{
- if (!$client->is_deleted) {
- $activity = Activity::getBlank();
- $activity->client_id = $client->id;
- $activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CLIENT;
- $activity->message = Utils::encodeActivity(Auth::user(), 'archived', $client);
- $activity->balance = $client->balance;
- $activity->save();
- }
+ return $this->belongsTo('App\Models\Credit')->withTrashed();
}
- public static function restoreClient($client)
+ public function payment()
{
- $activity = Activity::getBlank();
- $activity->client_id = $client->id;
- $activity->activity_type_id = ACTIVITY_TYPE_RESTORE_CLIENT;
- $activity->message = Utils::encodeActivity(Auth::user(), 'restored', $client);
- $activity->balance = $client->balance;
- $activity->save();
+ return $this->belongsTo('App\Models\Payment')->withTrashed();
}
- public static function createInvoice($invoice)
+ public static function calcMessage($activityTypeId, $client, $user, $invoice, $contactId, $payment, $credit, $isSystem)
{
- if (Auth::check()) {
- $message = Utils::encodeActivity(Auth::user(), 'created', $invoice);
- } else {
- $message = Utils::encodeActivity(null, 'created', $invoice);
- }
+ $data = [
+ 'client' => link_to($client->getRoute(), $client->getDisplayName()),
+ 'user' => $isSystem ? '' . trans('texts.system') . '' : $user->getDisplayName(),
+ 'invoice' => $invoice ? link_to($invoice->getRoute(), $invoice->getDisplayName()) : null,
+ 'quote' => $invoice ? link_to($invoice->getRoute(), $invoice->getDisplayName()) : null,
+ 'contact' => $contactId ? $client->getDisplayName() : $user->getDisplayName(),
+ 'payment' => $payment ? $payment->transaction_reference : null,
+ 'credit' => $credit ? Utils::formatMoney($credit->amount, $client->currency_id) : null,
+ ];
- $adjustment = 0;
- $client = $invoice->client;
- if (!$invoice->is_quote && !$invoice->is_recurring) {
- $adjustment = $invoice->amount;
- $client->balance = $client->balance + $adjustment;
- $client->save();
- }
-
- $activity = Activity::getBlank($invoice);
- $activity->invoice_id = $invoice->id;
- $activity->client_id = $invoice->client_id;
- $activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_CREATE_QUOTE : ACTIVITY_TYPE_CREATE_INVOICE;
- $activity->message = $message;
- $activity->balance = $client->balance;
- $activity->adjustment = $adjustment;
- $activity->save();
-
- Activity::checkSubscriptions($invoice->is_quote ? EVENT_CREATE_QUOTE : EVENT_CREATE_INVOICE, $invoice);
+ return trans("texts.activity_{$activityTypeId}", $data);
}
- public static function archiveInvoice($invoice)
+ public function getMessage()
{
- if (!$invoice->is_deleted) {
- $activity = Activity::getBlank();
- $activity->invoice_id = $invoice->id;
- $activity->client_id = $invoice->client_id;
- $activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_ARCHIVE_QUOTE : ACTIVITY_TYPE_ARCHIVE_INVOICE;
- $activity->message = Utils::encodeActivity(Auth::user(), 'archived', $invoice);
- $activity->balance = $invoice->client->balance;
-
- $activity->save();
- }
- }
-
- public static function restoreInvoice($invoice)
- {
- $activity = Activity::getBlank();
- $activity->invoice_id = $invoice->id;
- $activity->client_id = $invoice->client_id;
- $activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_RESTORE_QUOTE : ACTIVITY_TYPE_RESTORE_INVOICE;
- $activity->message = Utils::encodeActivity(Auth::user(), 'restored', $invoice);
- $activity->balance = $invoice->client->balance;
-
- $activity->save();
- }
-
- public static function emailInvoice($invitation)
- {
- $invoice = $invitation->invoice;
- $client = $invoice->client;
-
- if (!$invoice->isSent()) {
- $invoice->invoice_status_id = INVOICE_STATUS_SENT;
- $invoice->save();
- }
-
- $activity = Activity::getBlank($invitation);
- $activity->client_id = $invitation->invoice->client_id;
- $activity->invoice_id = $invitation->invoice_id;
- $activity->contact_id = $invitation->contact_id;
- $activity->activity_type_id = $invitation->invoice ? ACTIVITY_TYPE_EMAIL_QUOTE : ACTIVITY_TYPE_EMAIL_INVOICE;
- $activity->message = Utils::encodeActivity(Auth::check() ? Auth::user() : null, 'emailed', $invitation->invoice, $invitation->contact);
- $activity->balance = $client->balance;
- $activity->save();
- }
-
- public static function updateInvoice($invoice)
- {
- $client = $invoice->client;
-
- if ($invoice->isBeingDeleted()) {
- $adjustment = 0;
- if (!$invoice->is_quote && !$invoice->is_recurring) {
- $adjustment = $invoice->balance * -1;
- $client->balance = $client->balance - $invoice->balance;
- $client->paid_to_date = $client->paid_to_date - ($invoice->amount - $invoice->balance);
- $client->save();
- }
-
- $activity = Activity::getBlank();
- $activity->client_id = $invoice->client_id;
- $activity->invoice_id = $invoice->id;
- $activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_DELETE_QUOTE : ACTIVITY_TYPE_DELETE_INVOICE;
- $activity->message = Utils::encodeActivity(Auth::user(), 'deleted', $invoice);
- $activity->balance = $invoice->client->balance;
- $activity->adjustment = $adjustment;
- $activity->save();
-
- // Release any tasks associated with the deleted invoice
- Task::where('invoice_id', '=', $invoice->id)
- ->update(['invoice_id' => null]);
- } else {
- $diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
-
- $fieldChanged = false;
- 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;
- }
- }
-
- if ($diff != 0 || $fieldChanged) {
- $backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($invoice->id);
-
- if ($diff != 0 && !$invoice->is_quote && !$invoice->is_recurring) {
- $client->balance = $client->balance + $diff;
- $client->save();
- }
-
- $activity = Activity::getBlank($invoice);
- $activity->client_id = $invoice->client_id;
- $activity->invoice_id = $invoice->id;
- $activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
- $activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
- $activity->balance = $client->balance;
- $activity->adjustment = $invoice->is_quote || $invoice->is_recurring ? 0 : $diff;
- $activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
- $activity->save();
-
- if ($invoice->isPaid() && $invoice->balance > 0) {
- $invoice->invoice_status_id = INVOICE_STATUS_PARTIAL;
- } elseif ($invoice->invoice_status_id && $invoice->balance == 0) {
- $invoice->invoice_status_id = INVOICE_STATUS_PAID;
- }
- }
- }
- }
-
- public static function viewInvoice($invitation)
- {
- if (Session::get($invitation->invitation_key)) {
- return;
- }
-
- Session::put($invitation->invitation_key, true);
- $invoice = $invitation->invoice;
-
- if (!$invoice->isViewed()) {
- $invoice->invoice_status_id = INVOICE_STATUS_VIEWED;
- $invoice->save();
- }
-
- $now = Carbon::now()->toDateTimeString();
-
- $invitation->viewed_date = $now;
- $invitation->save();
-
- $client = $invoice->client;
- $client->last_login = $now;
- $client->save();
-
- $activity = Activity::getBlank($invitation);
- $activity->client_id = $invitation->invoice->client_id;
- $activity->invitation_id = $invitation->id;
- $activity->contact_id = $invitation->contact_id;
- $activity->invoice_id = $invitation->invoice_id;
- $activity->activity_type_id = $invitation->invoice->is_quote ? ACTIVITY_TYPE_VIEW_QUOTE : ACTIVITY_TYPE_VIEW_INVOICE;
- $activity->message = Utils::encodeActivity($invitation->contact, 'viewed', $invitation->invoice);
- $activity->balance = $invitation->invoice->client->balance;
- $activity->save();
- }
-
- public static function approveQuote($invitation) {
-
- $activity = Activity::getBlank($invitation);
- $activity->client_id = $invitation->invoice->client_id;
- $activity->invitation_id = $invitation->id;
- $activity->contact_id = $invitation->contact_id;
- $activity->invoice_id = $invitation->invoice_id;
- $activity->activity_type_id = ACTIVITY_TYPE_APPROVE_QUOTE;
- $activity->message = Utils::encodeActivity($invitation->contact, 'approved', $invitation->invoice);
- $activity->balance = $invitation->invoice->client->balance;
- $activity->save();
- }
-
- public static function createPayment($payment)
- {
- $client = $payment->client;
- $client->balance = $client->balance - $payment->amount;
- $client->paid_to_date = $client->paid_to_date + $payment->amount;
- $client->save();
-
- if ($payment->contact_id) {
- $activity = Activity::getBlank($client);
- $activity->contact_id = $payment->contact_id;
- $activity->message = Utils::encodeActivity($payment->invitation->contact, 'entered '.$payment->getName().' for ', $payment->invoice);
- } else {
- $activity = Activity::getBlank($client);
- $message = $payment->payment_type_id == PAYMENT_TYPE_CREDIT ? 'applied credit for ' : 'entered '.$payment->getName().' for ';
- $activity->message = Utils::encodeActivity(Auth::user(), $message, $payment->invoice);
- }
-
- $activity->payment_id = $payment->id;
-
- if ($payment->invoice_id) {
- $activity->invoice_id = $payment->invoice_id;
-
- $invoice = $payment->invoice;
- $invoice->balance = $invoice->balance - $payment->amount;
- $invoice->invoice_status_id = ($invoice->balance > 0) ? INVOICE_STATUS_PARTIAL : INVOICE_STATUS_PAID;
- if ($invoice->partial > 0) {
- $invoice->partial = max(0, $invoice->partial - $payment->amount);
- }
- $invoice->save();
- }
-
- $activity->payment_id = $payment->id;
- $activity->client_id = $payment->client_id;
- $activity->activity_type_id = ACTIVITY_TYPE_CREATE_PAYMENT;
- $activity->balance = $client->balance;
- $activity->adjustment = $payment->amount * -1;
- $activity->save();
-
- Activity::checkSubscriptions(EVENT_CREATE_PAYMENT, $payment);
- }
-
- public static function updatePayment($payment)
- {
- if ($payment->isBeingDeleted()) {
- $client = $payment->client;
- $client->balance = $client->balance + $payment->amount;
- $client->paid_to_date = $client->paid_to_date - $payment->amount;
- $client->save();
-
- $invoice = $payment->invoice;
- $invoice->balance = $invoice->balance + $payment->amount;
- if ($invoice->isPaid() && $invoice->balance > 0) {
- $invoice->invoice_status_id = ($invoice->balance == $invoice->amount ? INVOICE_STATUS_DRAFT : INVOICE_STATUS_PARTIAL);
- }
- $invoice->save();
-
- // deleting a payment from credit creates a new credit
- if ($payment->payment_type_id == PAYMENT_TYPE_CREDIT) {
- $credit = Credit::createNew();
- $credit->client_id = $client->id;
- $credit->credit_date = Carbon::now()->toDateTimeString();
- $credit->balance = $credit->amount = $payment->amount;
- $credit->private_notes = $payment->transaction_reference;
- $credit->save();
- }
-
- $activity = Activity::getBlank();
- $activity->payment_id = $payment->id;
- $activity->client_id = $invoice->client_id;
- $activity->invoice_id = $invoice->id;
- $activity->activity_type_id = ACTIVITY_TYPE_DELETE_PAYMENT;
- $activity->message = Utils::encodeActivity(Auth::user(), 'deleted '.$payment->getName());
- $activity->balance = $client->balance;
- $activity->adjustment = $payment->amount;
- $activity->save();
- } else {
- /*
- $diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
-
- if ($diff == 0)
- {
- return;
- }
-
- $client = $invoice->client;
- $client->balance = $client->balance + $diff;
- $client->save();
-
- $activity = Activity::getBlank($invoice);
- $activity->client_id = $invoice->client_id;
- $activity->invoice_id = $invoice->id;
- $activity->activity_type_id = ACTIVITY_TYPE_UPDATE_INVOICE;
- $activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
- $activity->balance = $client->balance;
- $activity->adjustment = $diff;
- $activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
- $activity->save();
- */
- }
- }
-
- public static function archivePayment($payment)
- {
- if ($payment->is_deleted) {
- return;
- }
-
- $client = $payment->client;
- $invoice = $payment->invoice;
-
- $activity = Activity::getBlank();
- $activity->payment_id = $payment->id;
- $activity->invoice_id = $invoice->id;
- $activity->client_id = $client->id;
- $activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_PAYMENT;
- $activity->message = Utils::encodeActivity(Auth::user(), 'archived '.$payment->getName());
- $activity->balance = $client->balance;
- $activity->adjustment = 0;
- $activity->save();
- }
-
- public static function restorePayment($payment)
- {
- $client = $payment->client;
- $invoice = $payment->invoice;
-
- $activity = Activity::getBlank();
- $activity->payment_id = $payment->id;
- $activity->invoice_id = $invoice->id;
- $activity->client_id = $client->id;
- $activity->activity_type_id = ACTIVITY_TYPE_RESTORE_PAYMENT;
- $activity->message = Utils::encodeActivity(Auth::user(), 'restored '.$payment->getName());
- $activity->balance = $client->balance;
- $activity->adjustment = 0;
- $activity->save();
- }
-
- public static function createCredit($credit)
- {
- $activity = Activity::getBlank();
- $activity->message = Utils::encodeActivity(Auth::user(), 'entered '.Utils::formatMoney($credit->amount, $credit->client->getCurrencyId()).' credit');
- $activity->credit_id = $credit->id;
- $activity->client_id = $credit->client_id;
- $activity->activity_type_id = ACTIVITY_TYPE_CREATE_CREDIT;
- $activity->balance = $credit->client->balance;
- $activity->save();
- }
-
- public static function updateCredit($credit)
- {
- if ($credit->isBeingDeleted()) {
- $activity = Activity::getBlank();
- $activity->credit_id = $credit->id;
- $activity->client_id = $credit->client_id;
- $activity->activity_type_id = ACTIVITY_TYPE_DELETE_CREDIT;
- $activity->message = Utils::encodeActivity(Auth::user(), 'deleted '.Utils::formatMoney($credit->balance, $credit->client->getCurrencyId()).' credit');
- $activity->balance = $credit->client->balance;
- $activity->save();
- } else {
- /*
- $diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
-
- if ($diff == 0)
- {
- return;
- }
-
- $client = $invoice->client;
- $client->balance = $client->balance + $diff;
- $client->save();
-
- $activity = Activity::getBlank($invoice);
- $activity->client_id = $invoice->client_id;
- $activity->invoice_id = $invoice->id;
- $activity->activity_type_id = ACTIVITY_TYPE_UPDATE_INVOICE;
- $activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
- $activity->balance = $client->balance;
- $activity->adjustment = $diff;
- $activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
- $activity->save();
- */
- }
- }
-
- public static function archiveCredit($credit)
- {
- if ($credit->is_deleted) {
- return;
- }
-
- $activity = Activity::getBlank();
- $activity->client_id = $credit->client_id;
- $activity->credit_id = $credit->id;
- $activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CREDIT;
- $activity->message = Utils::encodeActivity(Auth::user(), 'archived '.Utils::formatMoney($credit->balance, $credit->client->getCurrencyId()).' credit');
- $activity->balance = $credit->client->balance;
- $activity->save();
- }
-
- public static function restoreCredit($credit)
- {
- $activity = Activity::getBlank();
- $activity->client_id = $credit->client_id;
- $activity->credit_id = $credit->id;
- $activity->activity_type_id = ACTIVITY_TYPE_RESTORE_CREDIT;
- $activity->message = Utils::encodeActivity(Auth::user(), 'restored '.Utils::formatMoney($credit->balance, $credit->client->getCurrencyId()).' credit');
- $activity->balance = $credit->client->balance;
- $activity->save();
- }
-
- private static function checkSubscriptions($event, $data)
- {
- if (!Auth::check()) {
- return;
- }
-
- $subscription = Auth::user()->account->getSubscription($event);
-
- if ($subscription) {
- Utils::notifyZapier($subscription, $data);
- }
+ return static::calcMessage(
+ $this->activity_type_id,
+ $this->client,
+ $this->user,
+ $this->invoice,
+ $this->contact_id,
+ $this->payment,
+ $this->credit,
+ $this->is_system
+ );
}
}
diff --git a/app/Models/BalanceAffecting.php b/app/Models/BalanceAffecting.php
new file mode 100644
index 000000000..0ba99f73a
--- /dev/null
+++ b/app/Models/BalanceAffecting.php
@@ -0,0 +1,6 @@
+belongsTo('App\Models\Industry');
}
+ public function addContact($data, $isPrimary = false)
+ {
+ $publicId = isset($data['public_id']) ? $data['public_id'] : false;
+
+ if ($publicId && $publicId != '-1') {
+ $contact = Contact::scope($publicId)->firstOrFail();
+ } else {
+ $contact = Contact::createNew();
+ $contact->send_invoice = true;
+ }
+
+ $contact->fill($data);
+ $contact->is_primary = $isPrimary;
+
+ return $this->contacts()->save($contact);
+ }
+
+ public function updateBalances($balanceAdjustment, $paidToDateAdjustment)
+ {
+ if ($balanceAdjustment === 0 && $paidToDateAdjustment === 0) {
+ return;
+ }
+
+ $this->balance = $this->balance + $balanceAdjustment;
+ $this->paid_to_date = $this->paid_to_date + $paidToDateAdjustment;
+
+ $this->save();
+ }
+
+ public function getRoute()
+ {
+ return "/clients/{$this->public_id}";
+ }
+
public function getTotalCredit()
{
return DB::table('credits')
@@ -89,8 +147,11 @@ class Client extends EntityModel
return $this->name;
}
- $contact = $this->contacts()->first();
+ if ( ! count($this->contacts)) {
+ return '';
+ }
+ $contact = $this->contacts[0];
return $contact->getDisplayName();
}
@@ -178,24 +239,26 @@ class Client extends EntityModel
{
return $isQuote ? $this->quote_number_counter : $this->invoice_number_counter;
}
+
+ public function markLoggedIn()
+ {
+ $this->last_login = Carbon::now()->toDateTimeString();
+ $this->save();
+ }
}
-/*
-Client::created(function($client)
-{
- Activity::createClient($client);
+Client::creating(function ($client) {
+ $client->setNullValues();
+});
+
+Client::created(function ($client) {
+ event(new ClientWasCreated($client));
});
-*/
Client::updating(function ($client) {
- Activity::updateClient($client);
+ $client->setNullValues();
});
-Client::deleting(function ($client) {
- Activity::archiveClient($client);
+Client::updated(function ($client) {
+ event(new ClientWasUpdated($client));
});
-
-/*Client::restoring(function ($client) {
- Activity::restoreClient($client);
-});
-*/
\ No newline at end of file
diff --git a/app/Models/Contact.php b/app/Models/Contact.php
index 0856a6d43..731900e35 100644
--- a/app/Models/Contact.php
+++ b/app/Models/Contact.php
@@ -9,6 +9,14 @@ class Contact extends EntityModel
use SoftDeletes;
protected $dates = ['deleted_at'];
+ protected $fillable = [
+ 'first_name',
+ 'last_name',
+ 'email',
+ 'phone',
+ 'send_invoice',
+ ];
+
public static $fieldFirstName = 'Contact - First Name';
public static $fieldLastName = 'Contact - Last Name';
public static $fieldEmail = 'Contact - Email';
diff --git a/app/Models/Credit.php b/app/Models/Credit.php
index 9a507bc6b..a89e9e925 100644
--- a/app/Models/Credit.php
+++ b/app/Models/Credit.php
@@ -1,12 +1,19 @@
belongsTo('App\Models\Account');
+ }
+
public function invoice()
{
return $this->belongsTo('App\Models\Invoice')->withTrashed();
@@ -43,18 +50,10 @@ class Credit extends EntityModel
}
}
+Credit::creating(function ($credit) {
+
+});
+
Credit::created(function ($credit) {
- Activity::createCredit($credit);
-});
-
-Credit::updating(function ($credit) {
- Activity::updateCredit($credit);
-});
-
-Credit::deleting(function ($credit) {
- Activity::archiveCredit($credit);
-});
-
-Credit::restoring(function ($credit) {
- Activity::restoreCredit($credit);
-});
+ event(new CreditWasCreated($credit));
+});
\ No newline at end of file
diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php
index f9a375dc4..b8e7d651a 100644
--- a/app/Models/EntityModel.php
+++ b/app/Models/EntityModel.php
@@ -24,7 +24,10 @@ class EntityModel extends Eloquent
Utils::fatalError();
}
- $lastEntity = $className::withTrashed()->scope(false, $entity->account_id)->orderBy('public_id', 'DESC')->first();
+ $lastEntity = $className::withTrashed()
+ ->scope(false, $entity->account_id)
+ ->orderBy('public_id', 'DESC')
+ ->first();
if ($lastEntity) {
$entity->public_id = $lastEntity->public_id + 1;
@@ -39,7 +42,7 @@ class EntityModel extends Eloquent
{
$className = get_called_class();
- return $className::scope($publicId)->pluck('id');
+ return $className::scope($publicId)->withTrashed()->pluck('id');
}
public function getActivityKey()
@@ -112,8 +115,21 @@ class EntityModel extends Eloquent
return $data;
}
- public function isBeingDeleted()
+ public function setNullValues()
{
- return $this->is_deleted && !$this->getOriginal('is_deleted');
+ foreach ($this->fillable as $field) {
+ if (strstr($field, '_id') && !$this->$field) {
+ $this->$field = null;
+ }
+ }
+ }
+
+ // converts "App\Models\Client" to "client_id"
+ public function getKeyField()
+ {
+ $class = get_class($this);
+ $parts = explode('\\', $class);
+ $name = $parts[count($parts)-1];
+ return strtolower($name) . '_id';
}
}
diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php
index 00807b1a6..16d504349 100644
--- a/app/Models/Invitation.php
+++ b/app/Models/Invitation.php
@@ -1,6 +1,7 @@
invitation_key;
}
+ public function markSent($messageId = null)
+ {
+ $this->message_id = $messageId;
+ $this->email_error = null;
+ $this->sent_date = Carbon::now()->toDateTimeString();
+ $this->save();
+ }
+
+ public function markViewed()
+ {
+ $invoice = $this->invoice;
+ $client = $invoice->client;
+
+ $this->viewed_date = Carbon::now()->toDateTimeString();
+ $this->save();
+
+ $invoice->markViewed();
+ $client->markLoggedIn();
+ }
}
diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php
index 7a0a8c953..ab4d9bc14 100644
--- a/app/Models/Invoice.php
+++ b/app/Models/Invoice.php
@@ -2,9 +2,17 @@
use Utils;
use DateTime;
+use App\Models\BalanceAffecting;
use Illuminate\Database\Eloquent\SoftDeletes;
-class Invoice extends EntityModel
+use App\Events\QuoteWasCreated;
+use App\Events\QuoteWasUpdated;
+use App\Events\InvoiceWasCreated;
+use App\Events\InvoiceWasUpdated;
+use App\Events\InvoiceInvitationWasEmailed;
+use App\Events\QuoteInvitationWasEmailed;
+
+class Invoice extends EntityModel implements BalanceAffecting
{
use SoftDeletes {
SoftDeletes::trashed as parentTrashed;
@@ -26,6 +34,69 @@ class Invoice extends EntityModel
'year',
'date:',
];
+
+ public function getRoute()
+ {
+ $entityType = $this->getEntityType();
+ return "/{$entityType}s/{$this->public_id}/edit";
+ }
+
+ public function getDisplayName()
+ {
+ return $this->is_recurring ? trans('texts.recurring') : $this->invoice_number;
+ }
+
+ public function affectsBalance()
+ {
+ return !$this->is_quote && !$this->is_recurring;
+ }
+
+ public function getAdjustment()
+ {
+ if (!$this->affectsBalance()) {
+ return 0;
+ }
+
+ return $this->getRawAdjustment();
+ }
+
+ private function getRawAdjustment()
+ {
+ return floatval($this->amount) - floatval($this->getOriginal('amount'));
+ }
+
+ public function isChanged()
+ {
+ if ($this->getRawAdjustment() != 0) {
+ return true;
+ }
+
+ foreach ([
+ 'invoice_number',
+ 'po_number',
+ 'invoice_date',
+ 'due_date',
+ 'terms',
+ 'public_notes',
+ 'invoice_footer',
+ 'partial'
+ ] as $field) {
+ if ($this->$field != $this->getOriginal($field)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function getAmountPaid()
+ {
+ if ($this->is_quote || $this->is_recurring) {
+ return 0;
+ }
+
+ return ($this->amount - $this->balance);
+ }
public function trashed()
{
@@ -81,6 +152,69 @@ class Invoice extends EntityModel
return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id');
}
+ public function markInvitationsSent($notify = false)
+ {
+ foreach ($this->invitations as $invitation) {
+ $this->markInvitationSent($invitation, false, $notify);
+ }
+ }
+
+ public function markInvitationSent($invitation, $messageId = false, $notify = true)
+ {
+ if (!$this->isSent()) {
+ $this->invoice_status_id = INVOICE_STATUS_SENT;
+ $this->save();
+ }
+
+ $invitation->markSent($messageId);
+
+ // if the user marks it as sent rather than acually sending it
+ // then we won't track it in the activity log
+ if (!$notify) {
+ return;
+ }
+
+ if ($this->is_quote) {
+ event(new QuoteInvitationWasEmailed($invitation));
+ } else {
+ event(new InvoiceInvitationWasEmailed($invitation));
+ }
+ }
+
+ public function markViewed()
+ {
+ if (!$this->isViewed()) {
+ $this->invoice_status_id = INVOICE_STATUS_VIEWED;
+ $this->save();
+ }
+ }
+
+ public function updatePaidStatus()
+ {
+ if ($this->isPaid() && $this->balance > 0) {
+ $this->invoice_status_id = ($this->balance == $this->amount ? INVOICE_STATUS_SENT : INVOICE_STATUS_PARTIAL);
+ $this->save();
+ } elseif ($this->invoice_status_id && $this->amount > 0 && $this->balance == 0 && $this->invoice_status_id != INVOICE_STATUS_PAID) {
+ $this->invoice_status_id = INVOICE_STATUS_PAID;
+ $this->save();
+ }
+ }
+
+ public function updateBalances($balanceAdjustment, $partial = 0)
+ {
+ if ($this->is_deleted) {
+ return;
+ }
+
+ $this->balance = $this->balance + $balanceAdjustment;
+
+ if ($this->partial > 0) {
+ $this->partial = $partial;
+ }
+
+ $this->save();
+ }
+
public function getName()
{
return $this->is_recurring ? trans('texts.recurring') : $this->invoice_number;
@@ -458,17 +592,17 @@ Invoice::creating(function ($invoice) {
});
Invoice::created(function ($invoice) {
- Activity::createInvoice($invoice);
+ if ($invoice->is_quote) {
+ event(new QuoteWasCreated($invoice));
+ } else {
+ event(new InvoiceWasCreated($invoice));
+ }
});
Invoice::updating(function ($invoice) {
- Activity::updateInvoice($invoice);
-});
-
-Invoice::deleting(function ($invoice) {
- Activity::archiveInvoice($invoice);
-});
-
-Invoice::restoring(function ($invoice) {
- Activity::restoreInvoice($invoice);
+ if ($invoice->is_quote) {
+ event(new QuoteWasUpdated($invoice));
+ } else {
+ event(new InvoiceWasUpdated($invoice));
+ }
});
\ No newline at end of file
diff --git a/app/Models/Payment.php b/app/Models/Payment.php
index ee382dece..d0a1ae466 100644
--- a/app/Models/Payment.php
+++ b/app/Models/Payment.php
@@ -1,6 +1,7 @@
belongsTo('App\Models\Contact');
}
+ public function getRoute()
+ {
+ return "/payments/{$this->public_id}/edit";
+ }
+
public function getAmount()
{
return Utils::formatMoney($this->amount, $this->client->getCurrencyId());
@@ -53,18 +59,10 @@ class Payment extends EntityModel
}
}
+Payment::creating(function ($payment) {
+
+});
+
Payment::created(function ($payment) {
- Activity::createPayment($payment);
-});
-
-Payment::updating(function ($payment) {
- Activity::updatePayment($payment);
-});
-
-Payment::deleting(function ($payment) {
- Activity::archivePayment($payment);
-});
-
-Payment::restoring(function ($payment) {
- Activity::restorePayment($payment);
-});
+ event(new PaymentWasCreated($payment));
+});
\ No newline at end of file
diff --git a/app/Models/Task.php b/app/Models/Task.php
index 4ccbf9688..fbdd00c46 100644
--- a/app/Models/Task.php
+++ b/app/Models/Task.php
@@ -82,20 +82,4 @@ class Task extends EntityModel
{
return round($this->getDuration() / (60 * 60), 2);
}
-}
-
-Task::created(function ($task) {
- //Activity::createTask($task);
-});
-
-Task::updating(function ($task) {
- //Activity::updateTask($task);
-});
-
-Task::deleting(function ($task) {
- //Activity::archiveTask($task);
-});
-
-Task::restoring(function ($task) {
- //Activity::restoreTask($task);
-});
+}
\ No newline at end of file
diff --git a/app/Ninja/Mailers/ContactMailer.php b/app/Ninja/Mailers/ContactMailer.php
index ff4f28c51..cce2b5fc1 100644
--- a/app/Ninja/Mailers/ContactMailer.php
+++ b/app/Ninja/Mailers/ContactMailer.php
@@ -9,7 +9,9 @@ use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Activity;
use App\Models\Gateway;
-use App\Events\InvoiceSent;
+
+use App\Events\InvoiceWasEmailed;
+use App\Events\QuoteWasEmailed;
class ContactMailer extends Mailer
{
@@ -44,7 +46,11 @@ class ContactMailer extends Mailer
$account->loadLocalizationSettings();
if ($sent === true) {
- Event::fire(new InvoiceSent($invoice));
+ if ($invoice->is_quote) {
+ event(new QuoteWasEmailed($invoice));
+ } else {
+ event(new InvoiceWasEmailed($invoice));
+ }
}
return $sent ?: trans('texts.email_error');
@@ -94,10 +100,10 @@ class ContactMailer extends Mailer
$subject = $this->processVariables($subject, $variables);
$fromEmail = $user->email;
+
$response = $this->sendTo($invitation->contact->email, $fromEmail, $account->getDisplayName(), $subject, ENTITY_INVOICE, $data);
if ($response === true) {
- Activity::emailInvoice($invitation);
return true;
} else {
return false;
diff --git a/app/Ninja/Mailers/Mailer.php b/app/Ninja/Mailers/Mailer.php
index f129eefb6..34db634f8 100644
--- a/app/Ninja/Mailers/Mailer.php
+++ b/app/Ninja/Mailers/Mailer.php
@@ -50,16 +50,16 @@ class Mailer
{
if (isset($data['invitation'])) {
$invitation = $data['invitation'];
-
+ $invoice = $invitation->invoice;
+ $messageId = false;
+
// Track the Postmark message id
if (isset($_ENV['POSTMARK_API_TOKEN']) && $response) {
$json = $response->json();
- $invitation->message_id = $json['MessageID'];
+ $messageId = $json['MessageID'];
}
-
- $invitation->email_error = null;
- $invitation->sent_date = \Carbon::now()->toDateTimeString();
- $invitation->save();
+
+ $invoice->markInvitationSent($invitation, $messageId);
}
return true;
diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php
index 0cc11aa6c..2441330b9 100644
--- a/app/Ninja/Repositories/AccountRepository.php
+++ b/app/Ninja/Repositories/AccountRepository.php
@@ -18,6 +18,7 @@ use App\Models\Contact;
use App\Models\Account;
use App\Models\User;
use App\Models\UserAccount;
+use App\Models\AccountToken;
class AccountRepository
{
@@ -198,7 +199,7 @@ class AccountRepository
$accountGateway->user_id = $user->id;
$accountGateway->gateway_id = NINJA_GATEWAY_ID;
$accountGateway->public_id = 1;
- $accountGateway->config = env(NINJA_GATEWAY_CONFIG);
+ $accountGateway->setConfig(json_decode(env(NINJA_GATEWAY_CONFIG)));
$account->account_gateways()->save($accountGateway);
}
@@ -455,4 +456,14 @@ class AccountRepository
return $code;
}
+
+ public function createToken($name)
+ {
+ $token = AccountToken::createNew();
+ $token->name = trim($name) ?: 'TOKEN';
+ $token->token = str_random(RANDOM_KEY_LENGTH);
+ $token->save();
+
+ return $token->token;
+ }
}
diff --git a/app/Ninja/Repositories/ActivityRepository.php b/app/Ninja/Repositories/ActivityRepository.php
new file mode 100644
index 000000000..8b0692741
--- /dev/null
+++ b/app/Ninja/Repositories/ActivityRepository.php
@@ -0,0 +1,102 @@
+invoice->client;
+ } else {
+ $client = $entity->client;
+ }
+
+ // init activity and copy over context
+ $activity = self::getBlank($altEntity ?: $client);
+ $activity = Utils::copyContext($activity, $entity);
+ $activity = Utils::copyContext($activity, $altEntity);
+
+ $activity->client_id = $client->id;
+ $activity->activity_type_id = $activityTypeId;
+ $activity->adjustment = $balanceChange;
+ $activity->balance = $client->balance + $balanceChange;
+
+ $keyField = $entity->getKeyField();
+ $activity->$keyField = $entity->id;
+
+ $activity->ip = Request::getClientIp();
+ $activity->save();
+
+ $client->updateBalances($balanceChange, $paidToDateChange);
+
+ return $activity;
+ }
+
+ private function getBlank($entity)
+ {
+ $activity = new Activity();
+
+ if (Auth::check()) {
+ $activity->user_id = Auth::user()->id;
+ $activity->account_id = Auth::user()->account_id;
+ } else {
+ $activity->user_id = $entity->user_id;
+ $activity->account_id = $entity->account_id;
+
+ if ( ! $entity instanceof Invitation) {
+ $activity->is_system = true;
+ }
+ }
+
+ $activity->token_id = session('token_id');
+
+ return $activity;
+ }
+
+ public function findByClientId($clientId)
+ {
+ return DB::table('activities')
+ ->join('users', 'users.id', '=', 'activities.user_id')
+ ->join('clients', 'clients.id', '=', 'activities.client_id')
+ ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
+ ->leftJoin('invoices', 'invoices.id', '=', 'activities.invoice_id')
+ ->leftJoin('payments', 'payments.id', '=', 'activities.payment_id')
+ ->leftJoin('credits', 'credits.id', '=', 'activities.credit_id')
+ ->where('activities.client_id', '=', $clientId)
+ ->where('contacts.is_primary', '=', 1)
+ ->whereNull('contacts.deleted_at')
+ ->select(
+ 'activities.id',
+ 'activities.created_at',
+ 'activities.contact_id',
+ 'activities.activity_type_id',
+ 'activities.is_system',
+ 'clients.currency_id',
+ 'activities.balance',
+ 'activities.adjustment',
+ 'users.first_name as user_first_name',
+ 'users.last_name as user_last_name',
+ 'users.email as user_email',
+ 'invoices.invoice_number as invoice',
+ 'invoices.public_id as invoice_public_id',
+ 'invoices.is_recurring',
+ 'clients.currency_id',
+ 'clients.name as client_name',
+ 'clients.public_id as client_public_id',
+ 'contacts.id as contact',
+ 'contacts.first_name as first_name',
+ 'contacts.last_name as last_name',
+ 'contacts.email as email',
+ 'payments.transaction_reference as payment',
+ 'credits.amount as credit');
+ }
+
+}
\ No newline at end of file
diff --git a/app/Ninja/Repositories/BaseRepository.php b/app/Ninja/Repositories/BaseRepository.php
new file mode 100644
index 000000000..a91e8e0ef
--- /dev/null
+++ b/app/Ninja/Repositories/BaseRepository.php
@@ -0,0 +1,64 @@
+getClassName();
+ return new $className();
+ }
+
+ private function getEventClass($entity, $type)
+ {
+ return 'App\Events\\' . ucfirst($entity->getEntityType()) . 'Was' . $type;
+ }
+
+ public function archive($entity)
+ {
+ $entity->delete();
+
+ $className = $this->getEventClass($entity, 'Archived');
+ event(new $className($entity));
+ }
+
+ public function restore($entity)
+ {
+ $fromDeleted = false;
+ $entity->restore();
+
+ if ($entity->is_deleted) {
+ $fromDeleted = true;
+ $entity->is_deleted = false;
+ $entity->save();
+ }
+
+ $className = $this->getEventClass($entity, 'Restored');
+ event(new $className($entity, $fromDeleted));
+ }
+
+ public function delete($entity)
+ {
+ $entity->is_deleted = true;
+ $entity->save();
+
+ $entity->delete();
+
+ $className = $this->getEventClass($entity, 'Deleted');
+ event(new $className($entity));
+ }
+
+ public function findByPublicIds($ids)
+ {
+ return $this->getInstance()->scope($ids)->get();
+ }
+
+ public function findByPublicIdsWithTrashed($ids)
+ {
+ return $this->getInstance()->scope($ids)->withTrashed()->get();
+ }
+}
diff --git a/app/Ninja/Repositories/ClientRepository.php b/app/Ninja/Repositories/ClientRepository.php
index 091c66f9c..728b0bcca 100644
--- a/app/Ninja/Repositories/ClientRepository.php
+++ b/app/Ninja/Repositories/ClientRepository.php
@@ -1,11 +1,17 @@
'email|required_without:first_name',
- 'first_name' => 'required_without:email',
- ]);
- if ($validator->fails()) {
- return $validator->messages();
- }
+ $publicId = isset($data['public_id']) ? $data['public_id'] : false;
- return false;
- }
-
- public function save($publicId, $data, $notify = true)
- {
- if (!$publicId || $publicId == "-1") {
+ if (!$publicId || $publicId == '-1') {
$client = Client::createNew();
- $contact = Contact::createNew();
- $contact->is_primary = true;
- $contact->send_invoice = true;
} else {
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
- $contact = $client->contacts()->where('is_primary', '=', true)->firstOrFail();
- }
-
- if (isset($data['name'])) {
- $client->name = trim($data['name']);
- }
- if (isset($data['id_number'])) {
- $client->id_number = trim($data['id_number']);
- }
- if (isset($data['vat_number'])) {
- $client->vat_number = trim($data['vat_number']);
- }
- if (isset($data['work_phone'])) {
- $client->work_phone = trim($data['work_phone']);
- }
- if (isset($data['custom_value1'])) {
- $client->custom_value1 = trim($data['custom_value1']);
- }
- if (isset($data['custom_value2'])) {
- $client->custom_value2 = trim($data['custom_value2']);
- }
- if (isset($data['address1'])) {
- $client->address1 = trim($data['address1']);
- }
- if (isset($data['address2'])) {
- $client->address2 = trim($data['address2']);
- }
- if (isset($data['city'])) {
- $client->city = trim($data['city']);
- }
- if (isset($data['state'])) {
- $client->state = trim($data['state']);
- }
- if (isset($data['postal_code'])) {
- $client->postal_code = trim($data['postal_code']);
- }
- if (isset($data['country_id'])) {
- $client->country_id = $data['country_id'] ? $data['country_id'] : null;
- }
- if (isset($data['private_notes'])) {
- $client->private_notes = trim($data['private_notes']);
- }
- if (isset($data['size_id'])) {
- $client->size_id = $data['size_id'] ? $data['size_id'] : null;
- }
- if (isset($data['industry_id'])) {
- $client->industry_id = $data['industry_id'] ? $data['industry_id'] : null;
- }
- if (isset($data['currency_id'])) {
- $client->currency_id = $data['currency_id'] ? $data['currency_id'] : null;
- }
- if (isset($data['language_id'])) {
- $client->language_id = $data['language_id'] ? $data['language_id'] : null;
- }
- if (isset($data['payment_terms'])) {
- $client->payment_terms = $data['payment_terms'];
- }
- if (isset($data['website'])) {
- $client->website = trim($data['website']);
}
+ $client->fill($data);
$client->save();
- $isPrimary = true;
+ $first = true;
+ $contacts = isset($data['contact']) ? [$data['contact']] : $data['contacts'];
$contactIds = [];
- if (isset($data['contact'])) {
- $info = $data['contact'];
- if (isset($info['email'])) {
- $contact->email = trim($info['email']);
- }
- if (isset($info['first_name'])) {
- $contact->first_name = trim($info['first_name']);
- }
- if (isset($info['last_name'])) {
- $contact->last_name = trim($info['last_name']);
- }
- if (isset($info['phone'])) {
- $contact->phone = trim($info['phone']);
- }
- $contact->is_primary = true;
- $contact->send_invoice = true;
- $client->contacts()->save($contact);
- } else {
- foreach ($data['contacts'] as $record) {
- $record = (array) $record;
-
- if ($publicId != "-1" && isset($record['public_id']) && $record['public_id']) {
- $contact = Contact::scope($record['public_id'])->firstOrFail();
- } else {
- $contact = Contact::createNew();
- }
-
- if (isset($record['email'])) {
- $contact->email = trim($record['email']);
- }
- if (isset($record['first_name'])) {
- $contact->first_name = trim($record['first_name']);
- }
- if (isset($record['last_name'])) {
- $contact->last_name = trim($record['last_name']);
- }
- if (isset($record['phone'])) {
- $contact->phone = trim($record['phone']);
- }
- $contact->is_primary = $isPrimary;
- $contact->send_invoice = isset($record['send_invoice']) ? $record['send_invoice'] : true;
- $isPrimary = false;
-
- $client->contacts()->save($contact);
- $contactIds[] = $contact->public_id;
- }
-
- foreach ($client->contacts as $contact) {
- if (!in_array($contact->public_id, $contactIds)) {
- $contact->delete();
- }
- }
+ foreach ($data['contacts'] as $contact) {
+ $contact = $client->addContact($contact, $first);
+ $contactIds[] = $contact->public_id;
+ $first = false;
}
- $client->save();
-
- if (!$publicId || $publicId == "-1") {
- Activity::createClient($client, $notify);
+ foreach ($client->contacts as $contact) {
+ if (!in_array($contact->public_id, $contactIds)) {
+ $contact->delete();
+ }
}
return $client;
}
-
- public function bulk($ids, $action)
- {
- $clients = Client::withTrashed()->scope($ids)->get();
-
- foreach ($clients as $client) {
- if ($action == 'restore') {
- $client->restore();
-
- $client->is_deleted = false;
- $client->save();
- } else {
- if ($action == 'delete') {
- $client->is_deleted = true;
- $client->save();
- }
-
- $client->delete();
- }
- }
-
- return count($clients);
- }
}
diff --git a/app/Ninja/Repositories/CreditRepository.php b/app/Ninja/Repositories/CreditRepository.php
index 700467620..1ac543c5b 100644
--- a/app/Ninja/Repositories/CreditRepository.php
+++ b/app/Ninja/Repositories/CreditRepository.php
@@ -1,11 +1,17 @@
firstOrFail();
} else {
@@ -50,28 +58,4 @@ class CreditRepository
return $credit;
}
-
- public function bulk($ids, $action)
- {
- if (!$ids) {
- return 0;
- }
-
- $credits = Credit::withTrashed()->scope($ids)->get();
-
- foreach ($credits as $credit) {
- if ($action == 'restore') {
- $credit->restore();
- } else {
- if ($action == 'delete') {
- $credit->is_deleted = true;
- $credit->save();
- }
-
- $credit->delete();
- }
- }
-
- return count($credits);
- }
}
diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php
index 912310c76..b5daa89cd 100644
--- a/app/Ninja/Repositories/InvoiceRepository.php
+++ b/app/Ninja/Repositories/InvoiceRepository.php
@@ -8,9 +8,15 @@ use App\Models\Invitation;
use App\Models\Product;
use App\Models\Task;
use App\Services\PaymentService;
+use App\Ninja\Repositories\BaseRepository;
-class InvoiceRepository
+class InvoiceRepository extends BaseRepository
{
+ public function getClassName()
+ {
+ return 'App\Models\Invoice';
+ }
+
public function __construct(PaymentService $paymentService)
{
$this->paymentService = $paymentService;
@@ -145,10 +151,6 @@ class InvoiceRepository
->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 = '
-
{!! trans('texts.custom_fields') !!}
@@ -190,6 +189,46 @@
+
+
+
{!! trans('texts.default_messages') !!}
+
+
+
+
+
+
+
+ {!! Former::textarea('invoice_terms')
+ ->label(trans('texts.default_invoice_terms'))
+ ->rows(4) !!}
+
+
+
+
+
+ {!! Former::textarea('quote_terms')
+ ->label(trans('texts.default_quote_terms'))
+ ->rows(4) !!}
+
+
+
+
+
+
+
@if (Auth::user()->isPro())
diff --git a/resources/views/accounts/notifications.blade.php b/resources/views/accounts/notifications.blade.php
index c92bf3fc7..2b1ebd484 100644
--- a/resources/views/accounts/notifications.blade.php
+++ b/resources/views/accounts/notifications.blade.php
@@ -48,20 +48,7 @@
-->
-
-
-
-
{!! trans('texts.custom_messages') !!}
-
-
- {!! Former::textarea('invoice_terms')->label(trans('texts.default_invoice_terms'))->rows(4)
- ->onchange("$('#invoice_terms').val(wordWrapText($('#invoice_terms').val(), 300))") !!}
- {!! Former::textarea('invoice_footer')->label(trans('texts.default_invoice_footer'))->rows(4)
- ->onchange("$('#invoice_footer').val(wordWrapText($('#invoice_footer').val(), 600))") !!}
- {!! Former::textarea('email_footer')->label(trans('texts.default_email_footer'))->rows(4) !!}
-
-
-
+
{!! Former::actions(
Button::success(trans('texts.save'))
->submit()->large()
diff --git a/resources/views/accounts/token.blade.php b/resources/views/accounts/token.blade.php
index 6fd73ec2f..8fc4a3b14 100644
--- a/resources/views/accounts/token.blade.php
+++ b/resources/views/accounts/token.blade.php
@@ -25,7 +25,7 @@
@if (Auth::user()->isPro())
{!! Former::actions(
- Button::normal(trans('texts.cancel'))->asLinkTo(URL::to('/settings/token_management'))->appendIcon(Icon::create('remove-circle'))->large(),
+ Button::normal(trans('texts.cancel'))->asLinkTo(URL::to('/settings/api_tokens'))->appendIcon(Icon::create('remove-circle'))->large(),
Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))
) !!}
@else
diff --git a/resources/views/accounts/user_details.blade.php b/resources/views/accounts/user_details.blade.php
index 071b95cb9..2066e0d79 100644
--- a/resources/views/accounts/user_details.blade.php
+++ b/resources/views/accounts/user_details.blade.php
@@ -38,7 +38,7 @@
- @if (Utils::isNinja())
+ @if (Utils::isOAuthEnabled())
{!! Former::plaintext('oneclick_login')->value(
$user->oauth_provider_id ?
$oauthProviderName . ' - ' . link_to('#', trans('texts.disable'), ['onclick' => 'disableSocialLogin()']) :
@@ -49,10 +49,14 @@
@if (Utils::isNinja())
@if ($user->referral_code)
+ {{ Former::setOption('capitalize_translations', false) }}
{!! Former::plaintext('referral_code')
- ->help(trans('texts.referral_code_help'))
- ->value($user->referral_code . ' ' . Icon::create('question-sign') . '') !!}
- @elseif (Input::has('affiliate'))
+ ->help(NINJA_APP_URL . '/invoice_now?rc=' . $user->referral_code)
+ ->value($user->referral_code . ' - '.
+ $referralCounts['free'] . ' ' . trans('texts.free') . ' | ' .
+ $referralCounts['pro'] . ' ' . trans('texts.pro') . ' ' .
+ '' . Icon::create('question-sign') . '') !!}
+ @else
{!! Former::checkbox('referral_code')
->help(trans('texts.referral_code_help'))
->text(trans('texts.enable') . ' ' . Icon::create('question-sign') . '') !!}
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php
index bdda6f1b6..33e2d84ad 100644
--- a/resources/views/auth/login.blade.php
+++ b/resources/views/auth/login.blade.php
@@ -89,16 +89,16 @@
@if (Input::get('new_company') && Utils::allowNewAccounts())
- {{ trans('texts.or') }} -
{!! Button::primary(trans('texts.new_company'))->asLinkTo(URL::to('/invoice_now?new_company=true&sign_up=true'))->large()->submit()->block() !!}
- @elseif (Utils::isNinja())
+ @elseif (Utils::isOAuthEnabled())
- {{ trans('texts.or') }} -
@endforeach
@endif
diff --git a/resources/views/clients/edit.blade.php b/resources/views/clients/edit.blade.php
index 1891cc6f7..becbfbdd9 100644
--- a/resources/views/clients/edit.blade.php
+++ b/resources/views/clients/edit.blade.php
@@ -1,4 +1,3 @@
-
@extends('header')
@@ -7,6 +6,11 @@
@stop
@section('content')
+
+@if ($errors->first('contacts'))
+ {{ trans($errors->first('contacts')) }}
+@endif
+
{!! Former::open($url)
@@ -17,6 +21,7 @@
@if ($client)
{!! Former::populate($client) !!}
+ {!! Former::hidden('public_id') !!}
@endif
@@ -75,11 +80,16 @@
- {!! Former::hidden('public_id')->data_bind("value: public_id, valueUpdate: 'afterkeydown'") !!}
- {!! Former::text('first_name')->data_bind("value: first_name, valueUpdate: 'afterkeydown'") !!}
- {!! Former::text('last_name')->data_bind("value: last_name, valueUpdate: 'afterkeydown'") !!}
- {!! Former::text('email')->data_bind('value: email, valueUpdate: \'afterkeydown\', attr: {id:\'email\'+$index()}') !!}
- {!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown'") !!}
+ {!! Former::hidden('public_id')->data_bind("value: public_id, valueUpdate: 'afterkeydown',
+ attr: {name: 'contacts[' + \$index() + '][public_id]'}") !!}
+ {!! Former::text('first_name')->data_bind("value: first_name, valueUpdate: 'afterkeydown',
+ attr: {name: 'contacts[' + \$index() + '][first_name]'}") !!}
+ {!! Former::text('last_name')->data_bind("value: last_name, valueUpdate: 'afterkeydown',
+ attr: {name: 'contacts[' + \$index() + '][last_name]'}") !!}
+ {!! Former::text('email')->data_bind("value: email, valueUpdate: 'afterkeydown',
+ attr: {name: 'contacts[' + \$index() + '][email]', id:'email'+\$index()}") !!}
+ {!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown',
+ attr: {name: 'contacts[' + \$index() + '][phone]'}") !!}