diff --git a/.env.example b/.env.example index abc9da3a5254..643dda91d834 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,4 @@ MAIL_HOST MAIL_USERNAME MAIL_FROM_ADDRESS MAIL_FROM_NAME -MAIL_PASSWORD - -ALLOW_NEW_ACCOUNTS \ No newline at end of file +MAIL_PASSWORD \ No newline at end of file diff --git a/app/Console/Commands/SendRecurringInvoices.php b/app/Console/Commands/SendRecurringInvoices.php index 8f65214f7fc1..6acd87aa0c6e 100644 --- a/app/Console/Commands/SendRecurringInvoices.php +++ b/app/Console/Commands/SendRecurringInvoices.php @@ -7,6 +7,7 @@ use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use App\Ninja\Mailers\ContactMailer as Mailer; +use App\Ninja\Repositories\InvoiceRepository; use App\Models\Invoice; use App\Models\InvoiceItem; use App\Models\Invitation; @@ -16,12 +17,14 @@ class SendRecurringInvoices extends Command protected $name = 'ninja:send-invoices'; protected $description = 'Send recurring invoices'; protected $mailer; + protected $invoiceRepo; - public function __construct(Mailer $mailer) + public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo) { parent::__construct(); $this->mailer = $mailer; + $this->invoiceRepo = $invoiceRepo; } public function fire() @@ -34,74 +37,14 @@ class SendRecurringInvoices extends Command $this->info(count($invoices).' recurring invoice(s) found'); foreach ($invoices as $recurInvoice) { - if ($recurInvoice->client->deleted_at) { - continue; + $this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO')); + + $invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice); + + if ($invoice) { + $recurInvoice->account->loadLocalizationSettings(); + $this->mailer->sendInvoice($invoice); } - - if (!$recurInvoice->user->confirmed) { - continue; - } - - $this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO')); - - if (!$recurInvoice->shouldSendToday()) { - continue; - } - - $invoice = Invoice::createNew($recurInvoice); - $invoice->client_id = $recurInvoice->client_id; - $invoice->recurring_invoice_id = $recurInvoice->id; - $invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber(false, 'R'); - $invoice->amount = $recurInvoice->amount; - $invoice->balance = $recurInvoice->amount; - $invoice->invoice_date = date_create()->format('Y-m-d'); - $invoice->discount = $recurInvoice->discount; - $invoice->po_number = $recurInvoice->po_number; - $invoice->public_notes = Utils::processVariables($recurInvoice->public_notes); - $invoice->terms = Utils::processVariables($recurInvoice->terms); - $invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer); - $invoice->tax_name = $recurInvoice->tax_name; - $invoice->tax_rate = $recurInvoice->tax_rate; - $invoice->invoice_design_id = $recurInvoice->invoice_design_id; - $invoice->custom_value1 = $recurInvoice->custom_value1; - $invoice->custom_value2 = $recurInvoice->custom_value2; - $invoice->custom_taxes1 = $recurInvoice->custom_taxes1; - $invoice->custom_taxes2 = $recurInvoice->custom_taxes2; - $invoice->is_amount_discount = $recurInvoice->is_amount_discount; - - if ($invoice->client->payment_terms != 0) { - $days = $invoice->client->payment_terms; - if ($days == -1) { - $days = 0; - } - $invoice->due_date = date_create()->modify($days.' day')->format('Y-m-d'); - } - - $invoice->save(); - - foreach ($recurInvoice->invoice_items as $recurItem) { - $item = InvoiceItem::createNew($recurItem); - $item->product_id = $recurItem->product_id; - $item->qty = $recurItem->qty; - $item->cost = $recurItem->cost; - $item->notes = Utils::processVariables($recurItem->notes); - $item->product_key = Utils::processVariables($recurItem->product_key); - $item->tax_name = $recurItem->tax_name; - $item->tax_rate = $recurItem->tax_rate; - $invoice->invoice_items()->save($item); - } - - foreach ($recurInvoice->invitations as $recurInvitation) { - $invitation = Invitation::createNew($recurInvitation); - $invitation->contact_id = $recurInvitation->contact_id; - $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH); - $invoice->invitations()->save($invitation); - } - - $this->mailer->sendInvoice($invoice); - - $recurInvoice->last_sent_date = Carbon::now()->toDateTimeString(); - $recurInvoice->save(); } $this->info('Done'); diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 204e270c7e3a..42737940bbe8 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -75,18 +75,18 @@ class AccountController extends BaseController public function getStarted() { - if (Auth::check()) { - return Redirect::to('invoices/create'); - } - - if (!Utils::isNinja() && !Utils::allowNewAccounts() && Account::count() > 0) { - return Redirect::to('/login'); - } - $user = false; $guestKey = Input::get('guest_key'); // local storage key to login until registered $prevUserId = Session::pull(PREV_USER_ID); // last user id used to link to new account + if (Auth::check()) { + return Redirect::to('invoices/create'); + } + + if (!Utils::isNinja() && (Account::count() > 0 && !$prevUserId)) { + return Redirect::to('/login'); + } + if ($guestKey && !$prevUserId) { $user = User::where('password', '=', $guestKey)->first(); @@ -149,6 +149,7 @@ class AccountController extends BaseController public function showSection($section = ACCOUNT_DETAILS, $subSection = false) { if ($section == ACCOUNT_DETAILS) { + $primaryUser = Auth::user()->account->users()->orderBy('id')->first(); $data = [ 'account' => Account::with('users')->findOrFail(Auth::user()->account_id), 'countries' => Cache::get('countries'), @@ -159,8 +160,9 @@ class AccountController extends BaseController 'datetimeFormats' => Cache::get('datetimeFormats'), 'currencies' => Cache::get('currencies'), 'languages' => Cache::get('languages'), - 'showUser' => Auth::user()->id === Auth::user()->account->users()->first()->id, + 'showUser' => Auth::user()->id === $primaryUser->id, 'title' => trans('texts.company_details'), + 'primaryUser' => $primaryUser, ]; return View::make('accounts.details', $data); @@ -211,7 +213,7 @@ class AccountController extends BaseController $client->work_email = ''; $invoice->invoice_number = $account->getNextInvoiceNumber(); - $invoice->invoice_date = date_create()->format('Y-m-d'); + $invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d')); $invoice->account = json_decode($account->toJson()); $invoice->amount = $invoice->balance = 100; @@ -637,9 +639,10 @@ class AccountController extends BaseController { $rules = array( 'name' => 'required', + 'logo' => 'sometimes|max:1024|mimes:jpeg,gif,png', ); - $user = Auth::user()->account->users()->first(); + $user = Auth::user()->account->users()->orderBy('id')->first(); if (Auth::user()->id === $user->id) { $rules['email'] = 'email|required|unique:users,email,'.$user->id.',id'; diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index dac4b85ccbbb..bc6c699e728b 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -88,7 +88,7 @@ class AppController extends BaseController "MAIL_HOST={$mail['host']}\n". "MAIL_USERNAME={$mail['username']}\n". "MAIL_FROM_NAME={$mail['from']['name']}\n". - "MAIL_PASSWORD={$mail['password']}\n"; + "MAIL_PASSWORD={$mail['password']}"; // Write Config Settings $fp = fopen(base_path()."/.env", 'w'); diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index e17135fe7146..a5897ac6879d 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -62,7 +62,7 @@ class AuthController extends Controller { $userId = Auth::check() ? Auth::user()->id : null; $user = User::where('email', '=', $request->input('email'))->first(); - if ($user->failed_logins >= 3) { + if ($user && $user->failed_logins >= 3) { Session::flash('error', 'These credentials do not match our records.'); return redirect()->to('login'); } diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index e286b885cbf6..6ac4de2a4039 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -5,6 +5,7 @@ use DB; use View; use App\Models\Activity; use App\Models\Invoice; +use App\Models\Payment; class DashboardController extends BaseController { @@ -50,41 +51,81 @@ class DashboardController extends BaseController ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) ->get(); - + $select = DB::raw('SUM(clients.balance) as value, clients.currency_id as currency_id'); + $balances = DB::table('accounts') + ->select($select) + ->leftJoin('clients', 'accounts.id', '=', 'clients.account_id') + ->where('accounts.id', '=', Auth::user()->account_id) + ->where('clients.is_deleted', '=', false) + ->groupBy('accounts.id') + ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) + ->get(); $activities = Activity::where('activities.account_id', '=', Auth::user()->account_id) ->where('activity_type_id', '>', 0) - ->orderBy('created_at', 'desc')->take(14)->get(); + ->orderBy('created_at', 'desc') + ->take(50) + ->get(); - $pastDue = Invoice::scope()->whereHas('client', function($query) { - $query->where('deleted_at', '=', null); - }) - ->where('due_date', '<', date('Y-m-d')) - ->where('balance', '>', 0) - ->where('is_recurring', '=', false) - ->where('is_quote', '=', false) - ->where('is_deleted', '=', false) - ->orderBy('due_date', 'asc')->take(6)->get(); + $pastDue = DB::table('invoices') + ->leftJoin('clients', 'clients.id', '=', 'invoices.client_id') + ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id') + ->where('invoices.account_id', '=', Auth::user()->account_id) + ->where('clients.deleted_at', '=', null) + ->where('contacts.deleted_at', '=', null) + ->where('invoices.is_recurring', '=', false) + ->where('invoices.is_quote', '=', false) + ->where('invoices.balance', '>', 0) + ->where('invoices.is_deleted', '=', false) + ->where('contacts.is_primary', '=', true) + ->where('invoices.due_date', '<', date('Y-m-d')) + ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id']) + ->orderBy('invoices.due_date', 'asc') + ->take(50) + ->get(); + + $upcoming = DB::table('invoices') + ->leftJoin('clients', 'clients.id', '=', 'invoices.client_id') + ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id') + ->where('invoices.account_id', '=', Auth::user()->account_id) + ->where('clients.deleted_at', '=', null) + ->where('contacts.deleted_at', '=', null) + ->where('invoices.is_recurring', '=', false) + ->where('invoices.is_quote', '=', false) + ->where('invoices.balance', '>', 0) + ->where('invoices.is_deleted', '=', false) + ->where('contacts.is_primary', '=', true) + ->where('invoices.due_date', '>=', date('Y-m-d')) + ->orderBy('invoices.due_date', 'asc') + ->take(50) + ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id']) + ->get(); + + $payments = DB::table('payments') + ->leftJoin('clients', 'clients.id', '=', 'payments.client_id') + ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id') + ->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id') + ->where('payments.account_id', '=', Auth::user()->account_id) + ->where('clients.deleted_at', '=', null) + ->where('contacts.deleted_at', '=', null) + ->where('contacts.is_primary', '=', true) + ->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id']) + ->orderBy('payments.id', 'desc') + ->take(50) + ->get(); - $upcoming = Invoice::scope()->whereHas('client', function($query) { - $query->where('deleted_at', '=', null); - }) - ->where('due_date', '>=', date('Y-m-d')) - ->where('balance', '>', 0) - ->where('is_recurring', '=', false) - ->where('is_quote', '=', false) - ->where('is_deleted', '=', false) - ->orderBy('due_date', 'asc')->take(6)->get(); $data = [ + 'account' => Auth::user()->account, 'paidToDate' => $paidToDate, + 'balances' => $balances, 'averageInvoice' => $averageInvoice, - //'billedClients' => $metrics ? $metrics->billed_clients : 0, 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'activeClients' => $metrics ? $metrics->active_clients : 0, 'activities' => $activities, 'pastDue' => $pastDue, 'upcoming' => $upcoming, + 'payments' => $payments, 'title' => trans('texts.dashboard'), ]; diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index a58245e0e25b..0f8226f077b1 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -43,7 +43,7 @@ class HomeController extends BaseController public function invoiceNow() { - if (Auth::check() && Input::get('new_account')) { + if (Auth::check() && Input::get('new_company')) { Session::put(PREV_USER_ID, Auth::user()->id); Auth::user()->clearSession(); Auth::logout(); @@ -72,9 +72,9 @@ class HomeController extends BaseController $user->news_feed_id = $newsFeedId; $user->save(); } - - Session::forget('news_feed_message'); } + + Session::forget('news_feed_message'); return 'success'; } diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index 43911db7d8c3..4c13259aecb2 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -26,7 +26,11 @@ class InvoiceApiController extends Controller public function index() { - $invoices = Invoice::scope()->with('client', 'invitations.account')->where('invoices.is_quote', '=', false)->orderBy('created_at', 'desc')->get(); + $invoices = Invoice::scope() + ->with('client', 'invitations.account') + ->where('invoices.is_quote', '=', false) + ->orderBy('created_at', 'desc') + ->get(); // Add the first invitation link to the data foreach ($invoices as $key => $invoice) { @@ -50,12 +54,14 @@ class InvoiceApiController extends Controller $error = null; // check if the invoice number is set and unique - if (!isset($data['invoice_number'])) { + if (!isset($data['invoice_number']) && !isset($data['id'])) { $data['invoice_number'] = Auth::user()->account->getNextInvoiceNumber(); - } else { + } else if (isset($data['invoice_number'])) { $invoice = Invoice::scope()->where('invoice_number', '=', $data['invoice_number'])->first(); if ($invoice) { $error = trans('validation.unique', ['attribute' => 'texts.invoice_number']); + } else { + $data['id'] = $invoice->public_id; } } @@ -100,11 +106,13 @@ class InvoiceApiController extends Controller $data['client_id'] = $client->id; $invoice = $this->invoiceRepo->save(false, $data, false); - $invitation = Invitation::createNew(); - $invitation->invoice_id = $invoice->id; - $invitation->contact_id = $client->contacts[0]->id; - $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH); - $invitation->save(); + if (!isset($data['id'])) { + $invitation = Invitation::createNew(); + $invitation->invoice_id = $invoice->id; + $invitation->contact_id = $client->contacts[0]->id; + $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH); + $invitation->save(); + } if (isset($data['email_invoice']) && $data['email_invoice']) { $this->mailer->sendInvoice($invoice); diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 1939ce9c65da..6426333f1d5b 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -60,8 +60,7 @@ class InvoiceController extends BaseController 'columns' => Utils::trans(['checkbox', 'invoice_number', 'client', 'invoice_date', 'invoice_total', 'balance_due', 'due_date', 'status', 'action']), ]; - $recurringInvoices = Invoice::scope() - ->where('is_recurring', '=', true); + $recurringInvoices = Invoice::scope()->where('is_recurring', '=', true); if (Session::get('show_trash:invoice')) { $recurringInvoices->withTrashed(); @@ -86,11 +85,12 @@ class InvoiceController extends BaseController } $invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first(); - $color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78'; + $account = $invitation->account; + $color = $account->primary_color ? $account->primary_color : '#0b4d78'; $data = [ 'color' => $color, - 'hideLogo' => Session::get('white_label'), + 'hideLogo' => $account->isWhiteLabel(), 'title' => trans('texts.invoices'), 'entityType' => ENTITY_INVOICE, 'columns' => Utils::trans(['invoice_number', 'invoice_date', 'invoice_total', 'balance_due', 'due_date']), @@ -205,7 +205,6 @@ class InvoiceController extends BaseController Session::set($invitationKey, true); Session::set('invitation_key', $invitationKey); - Session::set('white_label', $account->isWhiteLabel()); $account->loadLocalizationSettings(); @@ -215,6 +214,8 @@ class InvoiceController extends BaseController if ($invoice->invoice_design_id == CUSTOM_DESIGN) { $invoice->invoice_design->javascript = $account->custom_design; + } elseif ($account->utf8_invoices) { + $invoice->invoice_design->javascript = $invoice->invoice_design->pdfmake; } $contact = $invitation->contact; @@ -254,7 +255,7 @@ class InvoiceController extends BaseController 'invoiceLabels' => $account->getInvoiceLabels(), 'contact' => $contact, 'paymentTypes' => $paymentTypes, - 'paymentURL' => $paymentURL + 'paymentURL' => $paymentURL, ); return View::make('invoices.view', $data); @@ -281,7 +282,7 @@ class InvoiceController extends BaseController $method = 'POST'; $url = "{$entityType}s"; } else { - Utils::trackViewed($invoice->invoice_number.' - '.$invoice->client->getDisplayName(), $invoice->getEntityType()); + Utils::trackViewed($invoice->getDisplayName().' - '.$invoice->client->getDisplayName(), $invoice->getEntityType()); $method = 'PUT'; $url = "{$entityType}s/{$publicId}"; } @@ -336,6 +337,7 @@ class InvoiceController extends BaseController 'url' => $url, 'title' => trans("texts.edit_{$entityType}"), 'client' => $invoice->client, + 'isRecurring' => $invoice->is_recurring, 'actions' => $actions); $data = array_merge($data, self::getViewModel()); @@ -359,10 +361,10 @@ class InvoiceController extends BaseController return View::make('invoices.edit', $data); } - public function create($clientPublicId = 0) + public function create($clientPublicId = 0, $isRecurring = false) { $client = null; - $invoiceNumber = Auth::user()->account->getNextInvoiceNumber(); + $invoiceNumber = $isRecurring ? microtime(true) : Auth::user()->account->getNextInvoiceNumber(); if ($clientPublicId) { $client = Client::scope($clientPublicId)->firstOrFail(); @@ -376,12 +378,18 @@ class InvoiceController extends BaseController 'method' => 'POST', 'url' => 'invoices', 'title' => trans('texts.new_invoice'), + 'isRecurring' => $isRecurring, 'client' => $client); $data = array_merge($data, self::getViewModel()); return View::make('invoices.edit', $data); } + public function createRecurring($clientPublicId = 0) + { + return self::create($clientPublicId, true); + } + private static function getViewModel() { $recurringHelp = ''; @@ -511,7 +519,16 @@ class InvoiceController extends BaseController return $this->convertQuote($publicId); } elseif ($action == 'email') { if (Auth::user()->confirmed && !Auth::user()->isDemo()) { - $response = $this->mailer->sendInvoice($invoice); + if ($invoice->is_recurring) { + if ($invoice->shouldSendToday()) { + $invoice = $this->invoiceRepo->createRecurringInvoice($invoice); + $response = $this->mailer->sendInvoice($invoice); + } else { + $response = trans('texts.recurring_too_soon'); + } + } else { + $response = $this->mailer->sendInvoice($invoice); + } if ($response === true) { $message = trans("texts.emailed_{$entityType}"); Session::flash('message', $message); diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 6248a8e6917b..712f19dce6c5 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -61,11 +61,12 @@ class PaymentController extends BaseController } $invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first(); - $color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78'; + $account = $invitation->account; + $color = $account->primary_color ? $account->primary_color : '#0b4d78'; $data = [ 'color' => $color, - 'hideLogo' => Session::get('white_label'), + 'hideLogo' => $account->isWhiteLabel(), 'entityType' => ENTITY_PAYMENT, 'title' => trans('texts.payments'), 'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date']) @@ -336,6 +337,7 @@ class PaymentController extends BaseController 'acceptedCreditCardTypes' => $acceptedCreditCardTypes, 'countries' => Cache::get('countries'), 'currencyId' => $client->getCurrencyId(), + 'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'), 'account' => $client->account, 'hideLogo' => $account->isWhiteLabel(), 'showAddress' => $accountGateway->show_address, @@ -387,6 +389,7 @@ class PaymentController extends BaseController 'currencyId' => 1, 'paymentTitle' => $affiliate->payment_title, 'paymentSubtitle' => $affiliate->payment_subtitle, + 'showAddress' => true, ]; return View::make('payments.payment', $data); @@ -541,18 +544,19 @@ class PaymentController extends BaseController ->withErrors($validator) ->withInput(); } + + + if ($accountGateway->update_address) { + $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'); + $client->save(); + } } - - if ($onSite && $accountGateway->update_address) { - $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'); - $client->save(); - } - + try { $gateway = self::createGateway($accountGateway); $details = self::getPaymentDetails($invitation, ($useToken || !$onSite) ? false : Input::all()); diff --git a/app/Http/Controllers/QuoteApiController.php b/app/Http/Controllers/QuoteApiController.php index 70257644c544..83e5e8781179 100644 --- a/app/Http/Controllers/QuoteApiController.php +++ b/app/Http/Controllers/QuoteApiController.php @@ -16,7 +16,11 @@ class QuoteApiController extends Controller public function index() { - $invoices = Invoice::scope()->with('client', 'user')->where('invoices.is_quote', '=', true)->orderBy('created_at', 'desc')->get(); + $invoices = Invoice::scope() + ->with('client', 'user') + ->where('invoices.is_quote', '=', true) + ->orderBy('created_at', 'desc') + ->get(); $invoices = Utils::remapPublicIds($invoices); $response = json_encode($invoices, JSON_PRETTY_PRINT); diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index b8dfd095e44b..589841a314bf 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -75,11 +75,12 @@ class QuoteController extends BaseController } $invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first(); - $color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78'; + $account = $invitation->account; + $color = $account->primary_color ? $account->primary_color : '#0b4d78'; $data = [ 'color' => $color, - 'hideLogo' => Session::get('white_label'), + 'hideLogo' => $account->isWhiteLabel(), 'title' => trans('texts.quotes'), 'entityType' => ENTITY_QUOTE, 'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']), @@ -157,7 +158,8 @@ class QuoteController extends BaseController 'paymentTerms' => Cache::get('paymentTerms'), 'industries' => Cache::get('industries'), 'invoiceDesigns' => InvoiceDesign::getDesigns(), - 'invoiceLabels' => Auth::user()->account->getInvoiceLabels() + 'invoiceLabels' => Auth::user()->account->getInvoiceLabels(), + 'isRecurring' => false, ]; } diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index da37c9eb9bec..dfd7a96470b3 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -20,7 +20,9 @@ class ReportController extends BaseController $fileName = storage_path() . '/dataviz_sample.txt'; if (Auth::user()->account->isPro()) { - $account = Account::where('id', '=', Auth::user()->account->id)->with(['clients.invoices.invoice_items', 'clients.contacts'])->first(); + $account = Account::where('id', '=', Auth::user()->account->id) + ->with(['clients.invoices.invoice_items', 'clients.contacts']) + ->first(); $account = $account->hideFieldsForViz(); $clients = $account->clients->toJson(); } elseif (file_exists($fileName)) { diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index dedd752190fb..85b4cede7d9a 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -95,7 +95,7 @@ class UserController extends BaseController $user->force_pdfjs = true; $user->save(); - Session::flash('message', trans('texts.security.updated_settings')); + Session::flash('message', trans('texts.updated_settings')); return Redirect::to('/dashboard'); } @@ -132,9 +132,12 @@ class UserController extends BaseController */ public function create() { - if (!Auth::user()->confirmed) { + if (!Auth::user()->registered) { Session::flash('error', trans('texts.register_to_add_user')); - + return Redirect::to('company/advanced_settings/user_management'); + } + if (!Auth::user()->confirmed) { + Session::flash('error', trans('texts.confirmation_required')); return Redirect::to('company/advanced_settings/user_management'); } @@ -374,6 +377,11 @@ class UserController extends BaseController Session::put(SESSION_USER_ACCOUNTS, $users); Session::flash('message', trans('texts.unlinked_account')); - return Redirect::to($referer); + return Redirect::to('/dashboard'); + } + + public function manageCompanies() + { + return View::make('users.account_management'); } } diff --git a/app/Http/Middleware/StartupCheck.php b/app/Http/Middleware/StartupCheck.php index a6c2741977c8..a0b7fd2b1d09 100644 --- a/app/Http/Middleware/StartupCheck.php +++ b/app/Http/Middleware/StartupCheck.php @@ -158,7 +158,7 @@ class StartupCheck } } - if (preg_match('/(?i)msie [2-8]/', $_SERVER['HTTP_USER_AGENT'])) { + if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(?i)msie [2-8]/', $_SERVER['HTTP_USER_AGENT'])) { Session::flash('error', trans('texts.old_browser')); } diff --git a/app/Http/routes.php b/app/Http/routes.php index 156e66ea6573..f7c829070f3a 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -95,6 +95,7 @@ Route::group(['middleware' => 'auth'], function() { Route::post('users/change_password', 'UserController@changePassword'); Route::get('/switch_account/{user_id}', 'UserController@switchAccount'); Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount'); + Route::get('/manage_companies', 'UserController@manageCompanies'); Route::get('api/tokens', array('as'=>'api.tokens', 'uses'=>'TokenController@getDatatable')); Route::resource('tokens', 'TokenController'); @@ -130,7 +131,6 @@ Route::group(['middleware' => 'auth'], function() { Route::get('tasks/create/{client_id?}', 'TaskController@create'); Route::post('tasks/bulk', 'TaskController@bulk'); - Route::get('recurring_invoices', 'InvoiceController@recurringIndex'); Route::get('api/recurring_invoices/{client_id?}', array('as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable')); Route::get('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory'); @@ -139,6 +139,7 @@ Route::group(['middleware' => 'auth'], function() { Route::resource('invoices', 'InvoiceController'); 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('invoices/{public_id}/clone', 'InvoiceController@cloneInvoice'); Route::post('invoices/bulk', 'InvoiceController@bulk'); @@ -325,6 +326,7 @@ define('SESSION_LAST_REQUEST_TIME', 'SESSION_LAST_REQUEST_TIME'); define('DEFAULT_TIMEZONE', 'US/Eastern'); define('DEFAULT_CURRENCY', 1); // US Dollar +define('DEFAULT_LANGUAGE', 1); // English define('DEFAULT_DATE_FORMAT', 'M j, Y'); define('DEFAULT_DATE_PICKER_FORMAT', 'M d, yyyy'); define('DEFAULT_DATETIME_FORMAT', 'F j, Y, g:i a'); @@ -363,7 +365,7 @@ define('NINJA_GATEWAY_ID', GATEWAY_STRIPE); define('NINJA_GATEWAY_CONFIG', ''); define('NINJA_WEB_URL', 'https://www.invoiceninja.com'); define('NINJA_APP_URL', 'https://app.invoiceninja.com'); -define('NINJA_VERSION', '2.2.2'); +define('NINJA_VERSION', '2.3.0'); define('NINJA_DATE', '2000-01-01'); define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'); @@ -472,4 +474,4 @@ if (Auth::check() && Auth::user()->id === 1) { Auth::loginUsingId(1); } -*/ +*/ \ No newline at end of file diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 184dd453e498..1263b4ed6763 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -3,6 +3,7 @@ use Auth; use Cache; use DB; +use App; use Schema; use Session; use Request; @@ -61,7 +62,7 @@ class Utils public static function allowNewAccounts() { - return Utils::isNinja() || (isset($_ENV['ALLOW_NEW_ACCOUNTS']) && $_ENV['ALLOW_NEW_ACCOUNTS'] == 'true'); + return Utils::isNinja() || Auth::check(); } public static function isPro() @@ -69,6 +70,11 @@ class Utils return Auth::check() && Auth::user()->isPro(); } + public static function isEnglish() + { + return App::getLocale() == 'en'; + } + public static function getUserType() { if (Utils::isNinja()) { @@ -335,10 +341,11 @@ class Utils return; } - $timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE); + //$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE); $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT); - $dateTime = DateTime::createFromFormat($format, $date, new DateTimeZone($timezone)); + //$dateTime = DateTime::createFromFormat($format, $date, new DateTimeZone($timezone)); + $dateTime = DateTime::createFromFormat($format, $date); return $formatResult ? $dateTime->format('Y-m-d') : $dateTime; } @@ -349,11 +356,11 @@ class Utils return ''; } - $timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE); + //$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE); $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT); $dateTime = DateTime::createFromFormat('Y-m-d', $date); - $dateTime->setTimeZone(new DateTimeZone($timezone)); + //$dateTime->setTimeZone(new DateTimeZone($timezone)); return $formatResult ? $dateTime->format($format) : $dateTime; } @@ -400,10 +407,12 @@ class Utils } $object = new stdClass(); + $object->accountId = Auth::user()->account_id; $object->url = $url; $object->name = ucwords($type).': '.$name; $data = []; + $counts = []; for ($i = 0; $iaccountId])) { + $counts[$item->accountId]++; + } else { + $counts[$item->accountId] = 1; + } } array_unshift($data, $object); - - if (count($data) > RECENTLY_VIEWED_LIMIT) { + + if (isset($counts[Auth::user()->account_id]) && $counts[Auth::user()->account_id] > RECENTLY_VIEWED_LIMIT) { array_pop($data); } diff --git a/app/Models/Account.php b/app/Models/Account.php index 10ba19d99ecd..243715ceaadd 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -4,7 +4,7 @@ use Eloquent; use Utils; use Session; use DateTime; - +use App; use Illuminate\Database\Eloquent\SoftDeletes; class Account extends Eloquent @@ -92,6 +92,11 @@ class Account extends Eloquent } } + public function isEnglish() + { + return !$this->language_id || $this->language_id == DEFAULT_LANGUAGE; + } + public function getDisplayName() { if ($this->name) { @@ -147,7 +152,8 @@ class Account extends Eloquent public function getLogoPath() { $fileName = 'logo/' . $this->account_key; - return file_exists($fileName.'.png') ? $fileName.'.png' : $fileName.'.jpg'; + + return file_exists($fileName.'.png') && $this->utf8_invoices ? $fileName.'.png' : $fileName.'.jpg'; } public function getLogoWidth() @@ -176,37 +182,36 @@ class Account extends Eloquent { $counter = $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter; $prefix .= $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix; - + $counterOffset = 0; + // confirm the invoice number isn't already taken do { $number = $prefix.str_pad($counter, 4, "0", STR_PAD_LEFT); $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); $counter++; + $counterOffset++; } while ($check); + // update the invoice counter to be caught up + if ($counterOffset > 1) { + if ($isQuote && !$this->share_counter) { + $this->quote_number_counter += $counterOffset - 1; + } else { + $this->invoice_number_counter += $counterOffset - 1; + } + + $this->save(); + } + return $number; } - public function incrementCounter($invoiceNumber, $isQuote = false, $isRecurring) + public function incrementCounter($isQuote = false) { - // check if the user modified the invoice number - if (!$isRecurring && $invoiceNumber != $this->getNextInvoiceNumber($isQuote)) { - // remove the prefix - $prefix = $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix; - $invoiceNumber = preg_replace('/^'.$prefix.'/', '', $invoiceNumber); - $invoiceNumber = intval(preg_replace('/[^0-9]/', '', $invoiceNumber)); - if ($isQuote && !$this->share_counter) { - $this->quote_number_counter = $invoiceNumber + 1; - } else { - $this->invoice_number_counter = $invoiceNumber + 1; - } - // otherwise, just increment the counter + if ($isQuote && !$this->share_counter) { + $this->quote_number_counter += 1; } else { - if ($isQuote && !$this->share_counter) { - $this->quote_number_counter += 1; - } else { - $this->invoice_number_counter += 1; - } + $this->invoice_number_counter += 1; } $this->save(); @@ -229,6 +234,8 @@ class Account extends Eloquent Session::put(SESSION_DATETIME_FORMAT, $this->datetime_format ? $this->datetime_format->format : DEFAULT_DATETIME_FORMAT); Session::put(SESSION_CURRENCY, $this->currency_id ? $this->currency_id : DEFAULT_CURRENCY); Session::put(SESSION_LOCALE, $this->language_id ? $this->language->locale : DEFAULT_LOCALE); + + App::setLocale(session(SESSION_LOCALE)); } public function getInvoiceLabels() @@ -277,7 +284,7 @@ class Account extends Eloquent if (isset($custom[$field]) && $custom[$field]) { $data[$field] = $custom[$field]; } else { - $data[$field] = uctrans("texts.$field"); + $data[$field] = $this->isEnglish() ? uctrans("texts.$field") : trans("texts.$field"); } } @@ -315,11 +322,11 @@ class Account extends Eloquent public function isWhiteLabel() { - if (Utils::isNinjaProd()) { - return false; + if (Utils::isNinja()) { + return self::isPro() && $this->pro_plan_paid != NINJA_DATE; + } else { + return $this->pro_plan_paid == NINJA_DATE; } - - return $this->pro_plan_paid == NINJA_DATE; } public function getSubscription($eventId) @@ -348,6 +355,8 @@ class Account extends Eloquent 'invoice_status_id', 'invoice_items', 'created_at', + 'is_recurring', + 'is_quote', ]); foreach ($invoice->invoice_items as $invoiceItem) { diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 5997412ebf2b..0dd5ec54bf37 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -214,6 +214,8 @@ class Activity extends Eloquent 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; } } } diff --git a/app/Models/Client.php b/app/Models/Client.php index f2357742e0bd..554f74556910 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -76,15 +76,13 @@ class Client extends EntityModel { return $this->name; } - + public function getDisplayName() { if ($this->name) { return $this->name; } - - $this->load('contacts'); - + $contact = $this->contacts()->first(); return $contact->getDisplayName(); @@ -152,11 +150,15 @@ class Client extends EntityModel public function getCurrencyId() { + if ($this->currency_id) { + return $this->currency_id; + } + if (!$this->account) { $this->load('account'); } - return $this->currency_id ?: ($this->account->currency_id ?: DEFAULT_CURRENCY); + return $this->account->currency_id ?: DEFAULT_CURRENCY; } } diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index bf44b6f6d886..550de1d3cef0 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -44,7 +44,7 @@ class EntityModel extends Eloquent public function getActivityKey() { - return '[' . $this->getEntityType().':'.$this->public_id.':'.$this->getName() . ']'; + return '[' . $this->getEntityType().':'.$this->public_id.':'.$this->getDisplayName() . ']'; } /* @@ -83,6 +83,11 @@ class EntityModel extends Eloquent return $this->public_id; } + public function getDisplayName() + { + return $this->getName(); + } + // Remap ids to public_ids and show name public function toPublicArray() { diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 27c0999c7a2c..aeebfaed6ab0 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -48,6 +48,11 @@ class Invoice extends EntityModel return $this->belongsTo('App\Models\Invoice'); } + public function recurring_invoices() + { + return $this->hasMany('App\Models\Invoice', 'recurring_invoice_id'); + } + public function invitations() { return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id'); @@ -55,7 +60,7 @@ class Invoice extends EntityModel public function getName() { - return $this->invoice_number; + return $this->is_recurring ? trans('texts.recurring') : $this->invoice_number; } public function getFileName() @@ -69,9 +74,14 @@ class Invoice extends EntityModel return storage_path() . '/pdfcache/cache-' . $this->id . '.pdf'; } + public static function calcLink($invoice) + { + return link_to('invoices/' . $invoice->public_id, $invoice->invoice_number); + } + public function getLink() { - return link_to('invoices/'.$this->public_id, $this->invoice_number); + return self::calcLink($this); } public function getEntityType() @@ -253,7 +263,9 @@ class Invoice extends EntityModel } Invoice::creating(function ($invoice) { - $invoice->account->incrementCounter($invoice->invoice_number, $invoice->is_quote, $invoice->recurring_invoice_id); + if (!$invoice->is_recurring) { + $invoice->account->incrementCounter($invoice->is_quote); + } }); Invoice::created(function ($invoice) { diff --git a/app/Ninja/Mailers/UserMailer.php b/app/Ninja/Mailers/UserMailer.php index 710e436c11e8..3f2b4290a7eb 100644 --- a/app/Ninja/Mailers/UserMailer.php +++ b/app/Ninja/Mailers/UserMailer.php @@ -47,7 +47,7 @@ class UserMailer extends Mailer 'clientName' => $invoice->client->getDisplayName(), 'accountName' => $invoice->account->getDisplayName(), 'userName' => $user->getDisplayName(), - 'invoiceAmount' => Utils::formatMoney($invoice->amount, $invoice->client->getCurrencyId()), + 'invoiceAmount' => Utils::formatMoney($invoice->getRequestedAmount(), $invoice->client->getCurrencyId()), 'invoiceNumber' => $invoice->invoice_number, 'invoiceLink' => SITE_URL."/{$entityType}s/{$invoice->public_id}", ]; diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index 98ef973f674f..d33c08f7abdf 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -6,7 +6,7 @@ use Session; use Utils; use DB; use stdClass; - +use Schema; use App\Models\AccountGateway; use App\Models\Invitation; use App\Models\Invoice; @@ -250,6 +250,10 @@ class AccountRepository public function findUserAccounts($userId1, $userId2 = false) { + if (!Schema::hasTable('user_accounts')) { + return false; + } + $query = UserAccount::where('user_id1', '=', $userId1) ->orWhere('user_id2', '=', $userId1) ->orWhere('user_id3', '=', $userId1) @@ -294,7 +298,7 @@ class AccountRepository $item->account_id = $user->account->id; $item->account_name = $user->account->getDisplayName(); $item->pro_plan_paid = $user->account->pro_plan_paid; - $item->account_key = file_exists($user->account->getLogoPath()) ? $user->account->account_key : null; + $item->logo_path = file_exists($user->account->getLogoPath()) ? $user->account->getLogoPath() : null; $data[] = $item; } @@ -312,6 +316,9 @@ class AccountRepository } public function syncUserAccounts($users, $proPlanPaid = false) { + if (!$users) { + return; + } if (!$proPlanPaid) { foreach ($users as $user) { diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index ce0397d64b4d..bfbf62658f45 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -1,11 +1,12 @@ save(); } - $invoice->client_id = $data['client_id']; + if (isset($data['invoice_number'])) { + $invoice->invoice_number = trim($data['invoice_number']); + } + $invoice->discount = round(Utils::parseFloat($data['discount']), 2); $invoice->is_amount_discount = $data['is_amount_discount'] ? true : false; - $invoice->invoice_number = trim($data['invoice_number']); $invoice->partial = round(Utils::parseFloat($data['partial']), 2); $invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']); $invoice->has_tasks = isset($data['has_tasks']) ? $data['has_tasks'] : false; if (!$publicId) { + $invoice->client_id = $data['client_id']; $invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false; } @@ -387,7 +391,7 @@ class InvoiceRepository $task->invoice_id = $invoice->id; $task->client_id = $invoice->client_id; $task->save(); - } else if ($item['product_key']) { + } else if ($item['product_key'] && !$invoice->has_tasks) { $product = Product::findProductByKey(trim($item['product_key'])); if (!$product) { @@ -543,9 +547,82 @@ class InvoiceRepository ->whereClientId($clientId) ->whereIsQuote(false) ->whereIsRecurring(false) + ->whereDeletedAt(null) ->whereHasTasks(true) - ->where('balance', '>', 0) + ->where('invoice_status_id', '<', 5) ->select(['public_id', 'invoice_number']) ->get(); } + + public function createRecurringInvoice($recurInvoice) + { + $recurInvoice->load('account.timezone', 'invoice_items', 'client', 'user'); + + if ($recurInvoice->client->deleted_at) { + return false; + } + + if (!$recurInvoice->user->confirmed) { + return false; + } + + if (!$recurInvoice->shouldSendToday()) { + return false; + } + + $invoice = Invoice::createNew($recurInvoice); + $invoice->client_id = $recurInvoice->client_id; + $invoice->recurring_invoice_id = $recurInvoice->id; + $invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber(false, 'R'); + $invoice->amount = $recurInvoice->amount; + $invoice->balance = $recurInvoice->amount; + $invoice->invoice_date = date_create()->format('Y-m-d'); + $invoice->discount = $recurInvoice->discount; + $invoice->po_number = $recurInvoice->po_number; + $invoice->public_notes = Utils::processVariables($recurInvoice->public_notes); + $invoice->terms = Utils::processVariables($recurInvoice->terms); + $invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer); + $invoice->tax_name = $recurInvoice->tax_name; + $invoice->tax_rate = $recurInvoice->tax_rate; + $invoice->invoice_design_id = $recurInvoice->invoice_design_id; + $invoice->custom_value1 = $recurInvoice->custom_value1; + $invoice->custom_value2 = $recurInvoice->custom_value2; + $invoice->custom_taxes1 = $recurInvoice->custom_taxes1; + $invoice->custom_taxes2 = $recurInvoice->custom_taxes2; + $invoice->is_amount_discount = $recurInvoice->is_amount_discount; + + if ($invoice->client->payment_terms != 0) { + $days = $invoice->client->payment_terms; + if ($days == -1) { + $days = 0; + } + $invoice->due_date = date_create()->modify($days.' day')->format('Y-m-d'); + } + + $invoice->save(); + + foreach ($recurInvoice->invoice_items as $recurItem) { + $item = InvoiceItem::createNew($recurItem); + $item->product_id = $recurItem->product_id; + $item->qty = $recurItem->qty; + $item->cost = $recurItem->cost; + $item->notes = Utils::processVariables($recurItem->notes); + $item->product_key = Utils::processVariables($recurItem->product_key); + $item->tax_name = $recurItem->tax_name; + $item->tax_rate = $recurItem->tax_rate; + $invoice->invoice_items()->save($item); + } + + foreach ($recurInvoice->invitations as $recurInvitation) { + $invitation = Invitation::createNew($recurInvitation); + $invitation->contact_id = $recurInvitation->contact_id; + $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH); + $invoice->invitations()->save($invitation); + } + + $recurInvoice->last_sent_date = Carbon::now()->toDateTimeString(); + $recurInvoice->save(); + + return $invoice; + } } diff --git a/app/Ninja/Repositories/TaskRepository.php b/app/Ninja/Repositories/TaskRepository.php index 4504e78fb7fa..47761900e1e6 100644 --- a/app/Ninja/Repositories/TaskRepository.php +++ b/app/Ninja/Repositories/TaskRepository.php @@ -46,7 +46,7 @@ class TaskRepository } public function save($publicId, $data) - { + { if ($publicId) { $task = Task::scope($publicId)->firstOrFail(); } else { @@ -60,8 +60,14 @@ class TaskRepository $task->description = trim($data['description']); } - //$timeLog = $task->time_log ? json_decode($task->time_log, true) : []; - $timeLog = isset($data['time_log']) ? json_decode($data['time_log']) : []; + if (isset($data['time_log'])) { + $timeLog = json_decode($data['time_log']); + } elseif ($task->time_log) { + $timeLog = json_decode($task->time_log); + } else { + $timeLog = []; + } + if ($data['action'] == 'start') { $task->is_running = true; $timeLog[] = [strtotime('now'), false]; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 9efba90b91af..d9fc74798f69 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -40,10 +40,13 @@ class AppServiceProvider extends ServiceProvider {