mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-11-01 20:17:35 -04:00 
			
		
		
		
	Merge branch 'release-4.2.0'
This commit is contained in:
		
						commit
						1633f30a38
					
				| @ -73,7 +73,7 @@ class ChargeRenewalInvoices extends Command | ||||
|                         ->orderBy('id') | ||||
|                         ->get(); | ||||
| 
 | ||||
|         $this->info(count($invoices).' invoices found'); | ||||
|         $this->info($invoices->count() . ' invoices found'); | ||||
| 
 | ||||
|         foreach ($invoices as $invoice) { | ||||
| 
 | ||||
|  | ||||
| @ -5,6 +5,7 @@ namespace App\Console\Commands; | ||||
| use Carbon; | ||||
| use App\Libraries\CurlUtils; | ||||
| use DB; | ||||
| use App; | ||||
| use Exception; | ||||
| use Illuminate\Console\Command; | ||||
| use Mail; | ||||
| @ -81,6 +82,7 @@ class CheckData extends Command | ||||
|         } | ||||
| 
 | ||||
|         //$this->checkInvoices();
 | ||||
|         $this->checkTranslations(); | ||||
|         $this->checkInvoiceBalances(); | ||||
|         $this->checkClientBalances(); | ||||
|         $this->checkContacts(); | ||||
| @ -115,6 +117,40 @@ class CheckData extends Command | ||||
|         $this->log .= $str . "\n"; | ||||
|     } | ||||
| 
 | ||||
|     private function checkTranslations() | ||||
|     { | ||||
|         $invalid = 0; | ||||
| 
 | ||||
|         foreach (cache('languages') as $language) { | ||||
|             App::setLocale($language->locale); | ||||
|             foreach (trans('texts') as $text) { | ||||
|                 if (strpos($text, '=') !== false) { | ||||
|                     $invalid++; | ||||
|                     $this->logMessage($language->locale . ' is invalid: ' . $text); | ||||
|                 } | ||||
| 
 | ||||
|                 preg_match('/(.script)/', strtolower($text), $matches); | ||||
|                 if (count($matches)) { | ||||
|                     foreach ($matches as $match) { | ||||
|                         if (in_array($match, ['escript', 'bscript', 'nscript'])) { | ||||
|                             continue; | ||||
|                         } | ||||
|                         $invalid++; | ||||
|                         $this->logMessage(sprintf('%s is invalid: %s', $language->locale, $text)); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if ($invalid > 0) { | ||||
|             $this->isValid = false; | ||||
|         } | ||||
| 
 | ||||
|         App::setLocale('en'); | ||||
|         $this->logMessage($invalid . ' invalid text strings'); | ||||
|     } | ||||
| 
 | ||||
|     private function checkDraftSentInvoices() | ||||
|     { | ||||
|         $invoices = Invoice::whereInvoiceStatusId(INVOICE_STATUS_SENT) | ||||
| @ -122,9 +158,9 @@ class CheckData extends Command | ||||
|                         ->withTrashed() | ||||
|                         ->get(); | ||||
| 
 | ||||
|         $this->logMessage(count($invoices) . ' draft sent invoices'); | ||||
|         $this->logMessage($invoices->count() . ' draft sent invoices'); | ||||
| 
 | ||||
|         if (count($invoices) > 0) { | ||||
|         if ($invoices->count() > 0) { | ||||
|             $this->isValid = false; | ||||
|         } | ||||
| 
 | ||||
| @ -190,9 +226,9 @@ class CheckData extends Command | ||||
|                     ->havingRaw('count(users.id) > 1') | ||||
|                     ->get(['users.oauth_user_id']); | ||||
| 
 | ||||
|         $this->logMessage(count($users) . ' users with duplicate oauth ids'); | ||||
|         $this->logMessage($users->count() . ' users with duplicate oauth ids'); | ||||
| 
 | ||||
|         if (count($users) > 0) { | ||||
|         if ($users->count() > 0) { | ||||
|             $this->isValid = false; | ||||
|         } | ||||
| 
 | ||||
| @ -308,9 +344,9 @@ class CheckData extends Command | ||||
|                         ->whereNull('contact_key') | ||||
|                         ->orderBy('id') | ||||
|                         ->get(['id']); | ||||
|         $this->logMessage(count($contacts) . ' contacts without a contact_key'); | ||||
|         $this->logMessage($contacts->count() . ' contacts without a contact_key'); | ||||
| 
 | ||||
|         if (count($contacts) > 0) { | ||||
|         if ($contacts->count() > 0) { | ||||
|             $this->isValid = false; | ||||
|         } | ||||
| 
 | ||||
| @ -339,9 +375,9 @@ class CheckData extends Command | ||||
|         } | ||||
| 
 | ||||
|         $clients = $clients->get(['clients.id', 'clients.user_id', 'clients.account_id']); | ||||
|         $this->logMessage(count($clients) . ' clients without any contacts'); | ||||
|         $this->logMessage($clients->count() . ' clients without any contacts'); | ||||
| 
 | ||||
|         if (count($clients) > 0) { | ||||
|         if ($clients->count() > 0) { | ||||
|             $this->isValid = false; | ||||
|         } | ||||
| 
 | ||||
| @ -374,9 +410,9 @@ class CheckData extends Command | ||||
|         } | ||||
| 
 | ||||
|         $clients = $clients->get(['clients.id', DB::raw('count(contacts.id)')]); | ||||
|         $this->logMessage(count($clients) . ' clients without a single primary contact'); | ||||
|         $this->logMessage($clients->count() . ' clients without a single primary contact'); | ||||
| 
 | ||||
|         if (count($clients) > 0) { | ||||
|         if ($clients->count() > 0) { | ||||
|             $this->isValid = false; | ||||
|         } | ||||
|     } | ||||
| @ -423,9 +459,9 @@ class CheckData extends Command | ||||
|                     ->havingRaw('count(invitations.id) = 0') | ||||
|                     ->get(['invoices.id', 'invoices.user_id', 'invoices.account_id', 'invoices.client_id']); | ||||
| 
 | ||||
|         $this->logMessage(count($invoices) . ' invoices without any invitations'); | ||||
|         $this->logMessage($invoices->count() . ' invoices without any invitations'); | ||||
| 
 | ||||
|         if (count($invoices) > 0) { | ||||
|         if ($invoices->count() > 0) { | ||||
|             $this->isValid = false; | ||||
|         } | ||||
| 
 | ||||
| @ -469,6 +505,10 @@ class CheckData extends Command | ||||
|                 ENTITY_INVOICE, | ||||
|                 ENTITY_CLIENT, | ||||
|                 ENTITY_USER, | ||||
|                 ENTITY_TASK_STATUS, | ||||
|             ], | ||||
|             'task_statuses' => [ | ||||
|                 ENTITY_USER, | ||||
|             ], | ||||
|             'credits' => [ | ||||
|                 ENTITY_CLIENT, | ||||
| @ -496,6 +536,25 @@ class CheckData extends Command | ||||
|                 ENTITY_USER, | ||||
|                 ENTITY_CLIENT, | ||||
|             ], | ||||
|             'proposals' => [ | ||||
|                 ENTITY_USER, | ||||
|                 ENTITY_INVOICE, | ||||
|                 ENTITY_PROPOSAL_TEMPLATE, | ||||
|             ], | ||||
|             'proposal_categories' => [ | ||||
|                 ENTITY_USER, | ||||
|             ], | ||||
|             'proposal_templates' => [ | ||||
|                 ENTITY_USER, | ||||
|             ], | ||||
|             'proposal_snippets' => [ | ||||
|                 ENTITY_USER, | ||||
|                 ENTITY_PROPOSAL_CATEGORY, | ||||
|             ], | ||||
|             'proposal_invitations' => [ | ||||
|                 ENTITY_USER, | ||||
|                 ENTITY_PROPOSAL, | ||||
|             ], | ||||
|         ]; | ||||
| 
 | ||||
|         foreach ($tables as $table => $entityTypes) { | ||||
| @ -512,9 +571,9 @@ class CheckData extends Command | ||||
|                                 ->where("{$table}.{$accountId}", '!=', DB::raw("{$tableName}.account_id")) | ||||
|                                 ->get(["{$table}.id"]); | ||||
| 
 | ||||
|                 if (count($records)) { | ||||
|                 if ($records->count()) { | ||||
|                     $this->isValid = false; | ||||
|                     $this->logMessage(count($records) . " {$table} records with incorrect {$entityType} account id"); | ||||
|                     $this->logMessage($records->count() . " {$table} records with incorrect {$entityType} account id"); | ||||
| 
 | ||||
|                     if ($this->option('fix') == 'true') { | ||||
|                         foreach ($records as $record) { | ||||
| @ -549,9 +608,9 @@ class CheckData extends Command | ||||
|                     ->groupBy('clients.id') | ||||
|                     ->havingRaw('clients.paid_to_date != sum(coalesce(payments.amount - payments.refunded, 0)) and clients.paid_to_date != 999999999.9999') | ||||
|                     ->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(coalesce(payments.amount - payments.refunded, 0)) as amount')]); | ||||
|         $this->logMessage(count($clients) . ' clients with incorrect paid to date'); | ||||
|         $this->logMessage($clients->count() . ' clients with incorrect paid to date'); | ||||
| 
 | ||||
|         if (count($clients) > 0) { | ||||
|         if ($clients->count() > 0) { | ||||
|             $this->isValid = false; | ||||
|         } | ||||
| 
 | ||||
| @ -580,9 +639,9 @@ class CheckData extends Command | ||||
|                     ->havingRaw('(invoices.amount - invoices.balance) != coalesce(sum(payments.amount - payments.refunded), 0)') | ||||
|                     ->get(['invoices.id', 'invoices.amount', 'invoices.balance', DB::raw('coalesce(sum(payments.amount - payments.refunded), 0)')]); | ||||
| 
 | ||||
|         $this->logMessage(count($invoices) . ' invoices with incorrect balances'); | ||||
|         $this->logMessage($invoices->count() . ' invoices with incorrect balances'); | ||||
| 
 | ||||
|         if (count($invoices) > 0) { | ||||
|         if ($invoices->count() > 0) { | ||||
|             $this->isValid = false; | ||||
|         } | ||||
|     } | ||||
| @ -608,9 +667,9 @@ class CheckData extends Command | ||||
|         $clients = $clients->groupBy('clients.id', 'clients.balance') | ||||
|                 ->orderBy('accounts.company_id', 'DESC') | ||||
|                 ->get(['accounts.company_id', 'clients.account_id', 'clients.id', 'clients.balance', 'clients.paid_to_date', DB::raw('sum(invoices.balance) actual_balance')]); | ||||
|         $this->logMessage(count($clients) . ' clients with incorrect balance/activities'); | ||||
|         $this->logMessage($clients->count() . ' clients with incorrect balance/activities'); | ||||
| 
 | ||||
|         if (count($clients) > 0) { | ||||
|         if ($clients->count() > 0) { | ||||
|             $this->isValid = false; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -360,6 +360,7 @@ class InitLookup extends Command | ||||
|         DB::statement('truncate lookup_users'); | ||||
|         DB::statement('truncate lookup_contacts'); | ||||
|         DB::statement('truncate lookup_invitations'); | ||||
|         DB::statement('truncate lookup_proposal_invitations'); | ||||
|         DB::statement('truncate lookup_account_tokens'); | ||||
|         DB::statement('SET FOREIGN_KEY_CHECKS = 1'); | ||||
|     } | ||||
|  | ||||
| @ -32,7 +32,7 @@ class RemoveOrphanedDocuments extends Command | ||||
|         $documents = Document::whereRaw('invoice_id IS NULL AND expense_id IS NULL AND updated_at <= ?', [new DateTime('-1 hour')]) | ||||
|             ->get(); | ||||
| 
 | ||||
|         $this->info(count($documents).' orphaned document(s) found'); | ||||
|         $this->info($documents->count() . ' orphaned document(s) found'); | ||||
| 
 | ||||
|         foreach ($documents as $document) { | ||||
|             $document->delete(); | ||||
|  | ||||
| @ -98,7 +98,7 @@ class SendRecurringInvoices extends Command | ||||
|             ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND is_public IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today]) | ||||
|             ->orderBy('id', 'asc') | ||||
|             ->get(); | ||||
|         $this->info(count($invoices).' recurring invoice(s) found'); | ||||
|         $this->info($invoices->count() . ' recurring invoice(s) found'); | ||||
| 
 | ||||
|         foreach ($invoices as $recurInvoice) { | ||||
|             $shouldSendToday = $recurInvoice->shouldSendToday(); | ||||
| @ -140,7 +140,7 @@ class SendRecurringInvoices extends Command | ||||
|                 [$today->format('Y-m-d')]) | ||||
|             ->orderBy('invoices.id', 'asc') | ||||
|             ->get(); | ||||
|         $this->info(count($delayedAutoBillInvoices).' due recurring invoice instance(s) found'); | ||||
|         $this->info($delayedAutoBillInvoices->count() . ' due recurring invoice instance(s) found'); | ||||
| 
 | ||||
|         /** @var Invoice $invoice */ | ||||
|         foreach ($delayedAutoBillInvoices as $invoice) { | ||||
| @ -165,7 +165,7 @@ class SendRecurringInvoices extends Command | ||||
|                         ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today]) | ||||
|                         ->orderBy('id', 'asc') | ||||
|                         ->get(); | ||||
|         $this->info(count($expenses).' recurring expenses(s) found'); | ||||
|         $this->info($expenses->count() . ' recurring expenses(s) found'); | ||||
| 
 | ||||
|         foreach ($expenses as $expense) { | ||||
|             $shouldSendToday = $expense->shouldSendToday(); | ||||
|  | ||||
| @ -92,7 +92,7 @@ class SendReminders extends Command | ||||
|     private function chargeLateFees() | ||||
|     { | ||||
|         $accounts = $this->accountRepo->findWithFees(); | ||||
|         $this->info(count($accounts) . ' accounts found with fees'); | ||||
|         $this->info($accounts->count() . ' accounts found with fees'); | ||||
| 
 | ||||
|         foreach ($accounts as $account) { | ||||
|             if (! $account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) { | ||||
| @ -100,7 +100,7 @@ class SendReminders extends Command | ||||
|             } | ||||
| 
 | ||||
|             $invoices = $this->invoiceRepo->findNeedingReminding($account, false); | ||||
|             $this->info($account->name . ': ' . count($invoices) . ' invoices found'); | ||||
|             $this->info($account->name . ': ' . $invoices->count() . ' invoices found'); | ||||
| 
 | ||||
|             foreach ($invoices as $invoice) { | ||||
|                 if ($reminder = $account->getInvoiceReminder($invoice, false)) { | ||||
| @ -128,7 +128,7 @@ class SendReminders extends Command | ||||
| 
 | ||||
|             // standard reminders
 | ||||
|             $invoices = $this->invoiceRepo->findNeedingReminding($account); | ||||
|             $this->info($account->name . ': ' . count($invoices) . ' invoices found'); | ||||
|             $this->info($account->name . ': ' . $invoices->count() . ' invoices found'); | ||||
| 
 | ||||
|             foreach ($invoices as $invoice) { | ||||
|                 if ($reminder = $account->getInvoiceReminder($invoice)) { | ||||
| @ -142,7 +142,7 @@ class SendReminders extends Command | ||||
| 
 | ||||
|             // endless reminders
 | ||||
|             $invoices = $this->invoiceRepo->findNeedingEndlessReminding($account); | ||||
|             $this->info($account->name . ': ' . count($invoices) . ' endless invoices found'); | ||||
|             $this->info($account->name . ': ' . $invoices->count() . ' endless invoices found'); | ||||
| 
 | ||||
|             foreach ($invoices as $invoice) { | ||||
|                 if ($invoice->last_sent_date == date('Y-m-d')) { | ||||
| @ -159,7 +159,7 @@ class SendReminders extends Command | ||||
|         $scheduledReports = ScheduledReport::where('send_date', '<=', date('Y-m-d')) | ||||
|             ->with('user', 'account.company') | ||||
|             ->get(); | ||||
|         $this->info(count($scheduledReports) . ' scheduled reports'); | ||||
|         $this->info($scheduledReports->count() . ' scheduled reports'); | ||||
| 
 | ||||
|         foreach ($scheduledReports as $scheduledReport) { | ||||
|             $user = $scheduledReport->user; | ||||
|  | ||||
| @ -60,10 +60,10 @@ class SendRenewalInvoices extends Command | ||||
|         $companies = Company::whereRaw("datediff(plan_expires, curdate()) = 10 and (plan = 'pro' or plan = 'enterprise')") | ||||
|                         ->orderBy('id') | ||||
|                         ->get(); | ||||
|         $this->info(count($companies).' companies found renewing in 10 days'); | ||||
|         $this->info($companies->count() . ' companies found renewing in 10 days'); | ||||
| 
 | ||||
|         foreach ($companies as $company) { | ||||
|             if (! count($company->accounts)) { | ||||
|             if (! $company->accounts->count()) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -28,10 +28,10 @@ class $CLASS$ extends AuthServiceProvider | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function boot(GateContract $gate) | ||||
|     public function boot() | ||||
|     { | ||||
|         parent::boot($gate); | ||||
|          | ||||
|         parent::boot(); | ||||
| 
 | ||||
|         $this->registerTranslations(); | ||||
|         $this->registerConfig(); | ||||
|         $this->registerViews(); | ||||
|  | ||||
| @ -53,17 +53,5 @@ class Kernel extends ConsoleKernel | ||||
|             ->command('ninja:send-reminders --force') | ||||
|             ->sendOutputTo($logFile) | ||||
|             ->daily(); | ||||
| 
 | ||||
|         if (Utils::isNinja()) { | ||||
|             $schedule | ||||
|                 ->command('ninja:send-renewals --force') | ||||
|                 ->sendOutputTo($logFile) | ||||
|                 ->daily(); | ||||
|         } | ||||
| 
 | ||||
|         $schedule | ||||
|             ->command('updater:check-for-update --prefixVersionWith=v') | ||||
|             ->sendOutputTo($logFile) | ||||
|             ->daily(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -42,6 +42,11 @@ if (! defined('APP_NAME')) { | ||||
|     define('ENTITY_RECURRING_EXPENSE', 'recurring_expense'); | ||||
|     define('ENTITY_CUSTOMER', 'customer'); | ||||
|     define('ENTITY_SUBSCRIPTION', 'subscription'); | ||||
|     define('ENTITY_PROPOSAL', 'proposal'); | ||||
|     define('ENTITY_PROPOSAL_TEMPLATE', 'proposal_template'); | ||||
|     define('ENTITY_PROPOSAL_SNIPPET', 'proposal_snippet'); | ||||
|     define('ENTITY_PROPOSAL_CATEGORY', 'proposal_category'); | ||||
|     define('ENTITY_PROPOSAL_INVITATION', 'proposal_invitation'); | ||||
| 
 | ||||
|     define('INVOICE_TYPE_STANDARD', 1); | ||||
|     define('INVOICE_TYPE_QUOTE', 2); | ||||
| @ -153,6 +158,7 @@ if (! defined('APP_NAME')) { | ||||
|     define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000)); // KB
 | ||||
|     define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000)); // Total KB
 | ||||
|     define('MAX_ZIP_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 30000)); // Total KB (uncompressed)
 | ||||
|     define('MAX_EMAILS_SENT_PER_HOUR', 90); | ||||
|     define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 300)); // pixels
 | ||||
|     define('DEFAULT_FONT_SIZE', 9); | ||||
|     define('DEFAULT_HEADER_FONT', 1); // Roboto
 | ||||
| @ -290,6 +296,7 @@ if (! defined('APP_NAME')) { | ||||
|     define('GATEWAY_DWOLLA', 43); | ||||
|     define('GATEWAY_CHECKOUT_COM', 47); | ||||
|     define('GATEWAY_CYBERSOURCE', 49); | ||||
|     define('GATEWAY_PAYTRACE', 56); | ||||
|     define('GATEWAY_WEPAY', 60); | ||||
|     define('GATEWAY_BRAINTREE', 61); | ||||
|     define('GATEWAY_CUSTOM', 62); | ||||
| @ -331,7 +338,7 @@ if (! defined('APP_NAME')) { | ||||
|     define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com')); | ||||
|     define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest')); | ||||
|     define('NINJA_DATE', '2000-01-01'); | ||||
|     define('NINJA_VERSION', '4.1.5' . env('NINJA_VERSION_SUFFIX')); | ||||
|     define('NINJA_VERSION', '4.2.0' . env('NINJA_VERSION_SUFFIX')); | ||||
| 
 | ||||
|     define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja')); | ||||
|     define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja')); | ||||
| @ -452,6 +459,7 @@ if (! defined('APP_NAME')) { | ||||
| 
 | ||||
|     define('TEMPLATE_INVOICE', 'invoice'); | ||||
|     define('TEMPLATE_QUOTE', 'quote'); | ||||
|     define('TEMPLATE_PROPOSAL', 'proposal'); | ||||
|     define('TEMPLATE_PARTIAL', 'partial'); | ||||
|     define('TEMPLATE_PAYMENT', 'payment'); | ||||
|     define('TEMPLATE_REMINDER1', 'reminder1'); | ||||
| @ -517,6 +525,9 @@ if (! defined('APP_NAME')) { | ||||
|     define('PLAN_TERM_MONTHLY', 'month'); | ||||
|     define('PLAN_TERM_YEARLY', 'year'); | ||||
| 
 | ||||
|     define('SUBSCRIPTION_FORMAT_JSON', 'JSON'); | ||||
|     define('SUBSCRIPTION_FORMAT_UBL', 'UBL'); | ||||
| 
 | ||||
|     // Pro
 | ||||
|     define('FEATURE_CUSTOMIZE_INVOICE_DESIGN', 'customize_invoice_design'); | ||||
|     define('FEATURE_REMOVE_CREATED_BY', 'remove_created_by'); | ||||
| @ -602,7 +613,6 @@ if (! defined('APP_NAME')) { | ||||
|         'dateFormats' => 'App\Models\DateFormat', | ||||
|         'datetimeFormats' => 'App\Models\DatetimeFormat', | ||||
|         'languages' => 'App\Models\Language', | ||||
|         'paymentTerms' => 'App\Models\PaymentTerm', | ||||
|         'paymentTypes' => 'App\Models\PaymentType', | ||||
|         'countries' => 'App\Models\Country', | ||||
|         'invoiceDesigns' => 'App\Models\InvoiceDesign', | ||||
|  | ||||
							
								
								
									
										21
									
								
								app/Events/SubdomainWasRemoved.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/Events/SubdomainWasRemoved.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Events; | ||||
| 
 | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class SubdomainWasRemoved extends Event | ||||
| { | ||||
|     use SerializesModels; | ||||
|     public $account; | ||||
| 
 | ||||
|     /** | ||||
|      * Create a new event instance. | ||||
|      * | ||||
|      * @param $account | ||||
|      */ | ||||
|     public function __construct($account) | ||||
|     { | ||||
|         $this->account = $account; | ||||
|     } | ||||
| } | ||||
| @ -2,7 +2,6 @@ | ||||
| 
 | ||||
| namespace App\Exceptions; | ||||
| 
 | ||||
| use Crawler; | ||||
| use Exception; | ||||
| use Illuminate\Auth\AuthenticationException; | ||||
| use Illuminate\Auth\Access\AuthorizationException; | ||||
| @ -51,11 +50,12 @@ class Handler extends ExceptionHandler | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if (! class_exists('Utils')) { | ||||
|         // if these classes don't exist the install is broken, maybe due to permissions
 | ||||
|         if (! class_exists('Utils') || ! class_exists('Crawler')) { | ||||
|             return parent::report($e); | ||||
|         } | ||||
| 
 | ||||
|         if (Crawler::isCrawler()) { | ||||
|         if (\Crawler::isCrawler()) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -184,12 +184,10 @@ class AccountApiController extends BaseAPIController | ||||
| 
 | ||||
|         $devices = json_decode($account->devices, true); | ||||
| 
 | ||||
|         foreach($devices as $key => $value) | ||||
|         for($x=0; $x<count($devices); $x++) | ||||
|         { | ||||
| 
 | ||||
|             if($request->token == $value['token']) | ||||
|                 unset($devices[$key]); | ||||
| 
 | ||||
|             if($request->token == $devices[$x]['token']) | ||||
|                 unset($devices[$x]); | ||||
|         } | ||||
| 
 | ||||
|         $account->devices = json_encode(array_values($devices)); | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -152,7 +152,7 @@ class AccountGatewayController extends BaseController | ||||
|             'config' => false, | ||||
|             'gateways' => $gateways, | ||||
|             'creditCardTypes' => $creditCards, | ||||
|             'countGateways' => count($currentGateways), | ||||
|             'countGateways' => $currentGateways->count(), | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -175,14 +175,18 @@ class AppController extends BaseController | ||||
|         $_ENV['DB_PASSWORD'] = $db['type']['password']; | ||||
| 
 | ||||
|         if ($mail) { | ||||
|             $_ENV['MAIL_DRIVER'] = $mail['driver']; | ||||
|             $_ENV['MAIL_PORT'] = $mail['port']; | ||||
|             $_ENV['MAIL_ENCRYPTION'] = $mail['encryption']; | ||||
|             $_ENV['MAIL_HOST'] = $mail['host']; | ||||
|             $_ENV['MAIL_USERNAME'] = $mail['username']; | ||||
|             $_ENV['MAIL_FROM_NAME'] = $mail['from']['name']; | ||||
|             $_ENV['MAIL_FROM_ADDRESS'] = $mail['from']['address']; | ||||
|             $_ENV['MAIL_PASSWORD'] = $mail['password']; | ||||
|             $prefix = ''; | ||||
|             if (($user = auth()->user()) && Account::count() > 1) { | ||||
|                 $prefix = $user->account_id . '_'; | ||||
|             } | ||||
|             $_ENV[$prefix . 'MAIL_DRIVER'] = $mail['driver']; | ||||
|             $_ENV[$prefix . 'MAIL_PORT'] = $mail['port']; | ||||
|             $_ENV[$prefix . 'MAIL_ENCRYPTION'] = $mail['encryption']; | ||||
|             $_ENV[$prefix . 'MAIL_HOST'] = $mail['host']; | ||||
|             $_ENV[$prefix . 'MAIL_USERNAME'] = $mail['username']; | ||||
|             $_ENV[$prefix . 'MAIL_FROM_NAME'] = $mail['from']['name']; | ||||
|             $_ENV[$prefix . 'MAIL_FROM_ADDRESS'] = $mail['from']['address']; | ||||
|             $_ENV[$prefix . 'MAIL_PASSWORD'] = $mail['password']; | ||||
|             $_ENV['MAILGUN_DOMAIN'] = $mail['mailgun_domain']; | ||||
|             $_ENV['MAILGUN_SECRET'] = $mail['mailgun_secret']; | ||||
|         } | ||||
|  | ||||
| @ -4,7 +4,6 @@ namespace App\Http\Controllers\Auth; | ||||
| 
 | ||||
| use Event; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Models\PasswordReset; | ||||
| use App\Events\UserLoggedIn; | ||||
| use App\Http\Controllers\Controller; | ||||
| use Illuminate\Foundation\Auth\ResetsPasswords; | ||||
|  | ||||
| @ -92,9 +92,11 @@ class BankAccountController extends BaseController | ||||
|         if ($publicId) { | ||||
|             $bankAccount = BankAccount::scope($publicId)->firstOrFail(); | ||||
|             if ($username != $bankAccount->username) { | ||||
|                 // TODO update username
 | ||||
|                 $bankAccount->setUsername($username); | ||||
|                 $bankAccount->save(); | ||||
|             } else { | ||||
|                 $username = Crypt::decrypt($username); | ||||
|             } | ||||
|             $username = Crypt::decrypt($username); | ||||
|             $bankId = $bankAccount->bank_id; | ||||
|         } else { | ||||
|             $bankAccount = new BankAccount; | ||||
|  | ||||
| @ -99,15 +99,17 @@ class BaseAPIController extends Controller | ||||
| 
 | ||||
|         $query->with($includes); | ||||
| 
 | ||||
|         if ($updatedAt = intval(Input::get('updated_at'))) { | ||||
|             $query->where('updated_at', '>=', date('Y-m-d H:i:s', $updatedAt)); | ||||
|         if (Input::get('updated_at') > 0) { | ||||
|                 $updatedAt = intval(Input::get('updated_at')); | ||||
|                 $query->where('updated_at', '>=', date('Y-m-d H:i:s', $updatedAt)); | ||||
|         } | ||||
| 
 | ||||
|         if ($clientPublicId = Input::get('client_id')) { | ||||
|             $filter = function ($query) use ($clientPublicId) { | ||||
|   | ||||
|         if (Input::get('client_id') > 0) { | ||||
|                 $clientPublicId = Input::get('client_id'); | ||||
|                 $filter = function ($query) use ($clientPublicId) { | ||||
|                 $query->where('public_id', '=', $clientPublicId); | ||||
|             }; | ||||
|             $query->whereHas('client', $filter); | ||||
|              }; | ||||
|              $query->whereHas('client', $filter); | ||||
|         } | ||||
| 
 | ||||
|         if (! Utils::hasPermission('view_all')) { | ||||
|  | ||||
| @ -15,6 +15,7 @@ class CalendarController extends BaseController | ||||
|     public function showCalendar() | ||||
|     { | ||||
|         $data = [ | ||||
|             'title' => trans('texts.calendar'), | ||||
|             'account' => auth()->user()->account, | ||||
|         ]; | ||||
| 
 | ||||
|  | ||||
| @ -58,7 +58,7 @@ class LoginController extends Controller | ||||
|     public function showLoginForm() | ||||
|     { | ||||
|         $subdomain = Utils::getSubdomain(\Request::server('HTTP_HOST')); | ||||
|         $hasAccountIndentifier = request()->account_key || ($subdomain && $subdomain != 'app'); | ||||
|         $hasAccountIndentifier = request()->account_key || ($subdomain && ! in_array($subdomain, ['www', 'app'])); | ||||
| 
 | ||||
|         if (! session('contact_key')) { | ||||
|             if (Utils::isNinja()) { | ||||
|  | ||||
| @ -4,7 +4,6 @@ namespace App\Http\Controllers\ClientAuth; | ||||
| 
 | ||||
| use Password; | ||||
| use Config; | ||||
| use App\Models\PasswordReset; | ||||
| use App\Http\Controllers\Controller; | ||||
| use Illuminate\Foundation\Auth\ResetsPasswords; | ||||
| use Illuminate\Http\Request; | ||||
| @ -55,14 +54,8 @@ class ResetPasswordController extends Controller | ||||
| 
 | ||||
|     public function showResetForm(Request $request, $token = null) | ||||
|     { | ||||
|         $passwordReset = PasswordReset::whereToken($token)->first(); | ||||
| 
 | ||||
|         if (! $passwordReset) { | ||||
|             return redirect('login')->withMessage(trans('texts.invalid_code')); | ||||
|         } | ||||
| 
 | ||||
|         return view('clientauth.passwords.reset')->with( | ||||
|             ['token' => $token, 'email' => $passwordReset->email] | ||||
|             ['token' => $token] | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -4,6 +4,7 @@ namespace App\Http\Controllers; | ||||
| 
 | ||||
| use App\Events\InvoiceInvitationWasViewed; | ||||
| use App\Events\QuoteInvitationWasViewed; | ||||
| use App\Models\Account; | ||||
| use App\Models\Contact; | ||||
| use App\Models\Document; | ||||
| use App\Models\Gateway; | ||||
| @ -55,7 +56,7 @@ class ClientPortalController extends BaseController | ||||
|         $this->taskRepo = $taskRepo; | ||||
|     } | ||||
| 
 | ||||
|     public function view($invitationKey) | ||||
|     public function viewInvoice($invitationKey) | ||||
|     { | ||||
|         if (! $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) { | ||||
|             return $this->returnError(); | ||||
| @ -76,8 +77,6 @@ class ClientPortalController extends BaseController | ||||
|             ]); | ||||
|         } | ||||
| 
 | ||||
|         $account->loadLocalizationSettings($client); | ||||
| 
 | ||||
|         if (! Input::has('phantomjs') && ! session('silent:' . $client->id) && ! Session::has($invitation->invitation_key) | ||||
|             && (! Auth::check() || Auth::user()->account_id != $invoice->account_id)) { | ||||
|             if ($invoice->isType(INVOICE_TYPE_QUOTE)) { | ||||
| @ -117,10 +116,10 @@ class ClientPortalController extends BaseController | ||||
| 
 | ||||
|         // translate the country names
 | ||||
|         if ($invoice->client->country) { | ||||
|             $invoice->client->country->name = trans('texts.country_' . $invoice->client->country->name); | ||||
|             $invoice->client->country->name = $invoice->client->country->getName(); | ||||
|         } | ||||
|         if ($invoice->account->country) { | ||||
|             $invoice->account->country->name = trans('texts.country_' . $invoice->account->country->name); | ||||
|             $invoice->account->country->name = $invoice->account->country->getName(); | ||||
|         } | ||||
| 
 | ||||
|         $data = []; | ||||
| @ -226,7 +225,7 @@ class ClientPortalController extends BaseController | ||||
|         return $pdfString; | ||||
|     } | ||||
| 
 | ||||
|     public function sign($invitationKey) | ||||
|     public function authorizeInvoice($invitationKey) | ||||
|     { | ||||
|         if (! $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) { | ||||
|             return RESULT_FAILURE; | ||||
| @ -262,13 +261,13 @@ class ClientPortalController extends BaseController | ||||
|             return redirect(request()->url()); | ||||
|         } | ||||
| 
 | ||||
|         $account->loadLocalizationSettings($client); | ||||
|         $color = $account->primary_color ? $account->primary_color : '#0b4d78'; | ||||
|         $customer = false; | ||||
| 
 | ||||
|         if (! $account->enable_client_portal) { | ||||
|             return $this->returnError(); | ||||
|         } elseif (! $account->enable_client_portal_dashboard) { | ||||
|             session()->reflash(); | ||||
|             return redirect()->to('/client/invoices/'); | ||||
|         } | ||||
| 
 | ||||
| @ -334,7 +333,6 @@ class ClientPortalController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         $account = $contact->account; | ||||
|         $account->loadLocalizationSettings($contact->client); | ||||
| 
 | ||||
|         if (! $account->enable_client_portal) { | ||||
|             return $this->returnError(); | ||||
| @ -368,7 +366,6 @@ class ClientPortalController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         $account = $contact->account; | ||||
|         $account->loadLocalizationSettings($contact->client); | ||||
| 
 | ||||
|         if (! $account->enable_client_portal) { | ||||
|             return $this->returnError(); | ||||
| @ -414,7 +411,6 @@ class ClientPortalController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         $account = $contact->account; | ||||
|         $account->loadLocalizationSettings($contact->client); | ||||
| 
 | ||||
|         if (! $account->enable_client_portal) { | ||||
|             return $this->returnError(); | ||||
| @ -499,7 +495,6 @@ class ClientPortalController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         $account = $contact->account; | ||||
|         $account->loadLocalizationSettings($contact->client); | ||||
| 
 | ||||
|         if (! $account->enable_client_portal) { | ||||
|             return $this->returnError(); | ||||
| @ -535,7 +530,6 @@ class ClientPortalController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         $account = $contact->account; | ||||
|         $account->loadLocalizationSettings($contact->client); | ||||
| 
 | ||||
|         if (! $account->enable_client_portal) { | ||||
|             return $this->returnError(); | ||||
| @ -571,7 +565,6 @@ class ClientPortalController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         $account = $contact->account; | ||||
|         $account->loadLocalizationSettings($contact->client); | ||||
| 
 | ||||
|         if (! $contact->client->show_tasks_in_portal) { | ||||
|             return redirect()->to($account->enable_client_portal_dashboard ? '/client/dashboard' : '/client/payment_methods/'); | ||||
| @ -611,7 +604,6 @@ class ClientPortalController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         $account = $contact->account; | ||||
|         $account->loadLocalizationSettings($contact->client); | ||||
| 
 | ||||
|         if (! $account->enable_client_portal) { | ||||
|             return $this->returnError(); | ||||
| @ -962,4 +954,65 @@ class ClientPortalController extends BaseController | ||||
| 
 | ||||
|         return Redirect::to('client/invoices/recurring'); | ||||
|     } | ||||
| 
 | ||||
|     public function showDetails() | ||||
|     { | ||||
|         if (! $contact = $this->getContact()) { | ||||
|             return $this->returnError(); | ||||
|         } | ||||
| 
 | ||||
|         $data = [ | ||||
|             'contact' => $contact, | ||||
|             'client' => $contact->client, | ||||
|             'account' => $contact->account, | ||||
|         ]; | ||||
| 
 | ||||
|         return view('invited.details', $data); | ||||
|     } | ||||
| 
 | ||||
|     public function updateDetails(\Illuminate\Http\Request $request) | ||||
|     { | ||||
|         if (! $contact = $this->getContact()) { | ||||
|             return $this->returnError(); | ||||
|         } | ||||
| 
 | ||||
|         $client = $contact->client; | ||||
|         $account = $contact->account; | ||||
| 
 | ||||
|         if (! $account->enable_client_portal) { | ||||
|             return $this->returnError(); | ||||
|         } | ||||
| 
 | ||||
|         $rules = [ | ||||
|             'email' => 'required', | ||||
|             'address1' => 'required', | ||||
|             'city' => 'required', | ||||
|             'state' => 'required', | ||||
|             'postal_code' => 'required', | ||||
|             'country_id' => 'required', | ||||
|         ]; | ||||
| 
 | ||||
|         if ($client->name) { | ||||
|             $rules['name'] = 'required'; | ||||
|         } else { | ||||
|             $rules['first_name'] = 'required'; | ||||
|             $rules['last_name'] = 'required'; | ||||
|         } | ||||
|         if ($account->vat_number || $account->isNinjaAccount()) { | ||||
|             $rules['vat_number'] = 'required'; | ||||
|         } | ||||
| 
 | ||||
|         $this->validate($request, $rules); | ||||
| 
 | ||||
|         $contact->fill(request()->all()); | ||||
|         $contact->save(); | ||||
| 
 | ||||
|         $client->fill(request()->all()); | ||||
|         $client->save(); | ||||
| 
 | ||||
|         event(new \App\Events\ClientWasUpdated($client)); | ||||
| 
 | ||||
|         return redirect($account->enable_client_portal_dashboard ? '/client/dashboard' : '/client/payment_methods') | ||||
|             ->withMessage(trans('texts.updated_client_details')); | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										70
									
								
								app/Http/Controllers/ClientPortalProposalController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								app/Http/Controllers/ClientPortalProposalController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Controllers; | ||||
| 
 | ||||
| use mPDF; | ||||
| use App\Models\Account; | ||||
| use App\Models\Document; | ||||
| use App\Models\Invitation; | ||||
| use App\Ninja\Repositories\ProposalRepository; | ||||
| 
 | ||||
| class ClientPortalProposalController extends BaseController | ||||
| { | ||||
|     private $invoiceRepo; | ||||
|     private $paymentRepo; | ||||
|     private $documentRepo; | ||||
|     private $propoosalRepo; | ||||
| 
 | ||||
|     public function __construct(ProposalRepository $propoosalRepo) | ||||
|     { | ||||
|         $this->propoosalRepo = $propoosalRepo; | ||||
|     } | ||||
| 
 | ||||
|     public function viewProposal($invitationKey) | ||||
|     { | ||||
|         if (! $invitation = $this->propoosalRepo->findInvitationByKey($invitationKey)) { | ||||
|             return $this->returnError(trans('texts.proposal_not_found')); | ||||
|         } | ||||
| 
 | ||||
|         $account = $invitation->account; | ||||
|         $proposal = $invitation->proposal; | ||||
|         $invoiceInvitation = Invitation::whereContactId($invitation->contact_id) | ||||
|                 ->whereInvoiceId($proposal->invoice_id) | ||||
|                 ->firstOrFail(); | ||||
| 
 | ||||
|         $data = [ | ||||
|             'proposal' => $proposal, | ||||
|             'account' => $account, | ||||
|             'invoiceInvitation' => $invoiceInvitation, | ||||
|             'proposalInvitation' => $invitation, | ||||
|         ]; | ||||
| 
 | ||||
|         return view('invited.proposal', $data); | ||||
|     } | ||||
| 
 | ||||
|     public function downloadProposal($invitationKey) | ||||
|     { | ||||
|         if (! $invitation = $this->propoosalRepo->findInvitationByKey($invitationKey)) { | ||||
|             return $this->returnError(trans('texts.proposal_not_found')); | ||||
|         } | ||||
| 
 | ||||
|         $proposal = $invitation->proposal; | ||||
| 
 | ||||
|         $mpdf = new mPDF(); | ||||
|         $mpdf->WriteHTML($proposal->present()->htmlDocument); | ||||
|         $mpdf->Output($proposal->present()->filename, 'D'); | ||||
|     } | ||||
| 
 | ||||
|     public function getProposalImage($accountKey, $documentKey) | ||||
|     { | ||||
|         $account = Account::whereAccountKey($accountKey) | ||||
|                         ->firstOrFail(); | ||||
| 
 | ||||
|         $document = Document::whereAccountId($account->id) | ||||
|                         ->whereDocumentKey($documentKey) | ||||
|                         ->whereIsProposal(true) | ||||
|                         ->firstOrFail(); | ||||
| 
 | ||||
|         return DocumentController::getDownloadResponse($document); | ||||
|     } | ||||
| } | ||||
| @ -43,12 +43,12 @@ class DashboardApiController extends BaseAPIController | ||||
| 
 | ||||
|         $data = [ | ||||
|             'id' => 1, | ||||
|             'paidToDate' => count($paidToDate) && $paidToDate[0]->value ? $paidToDate[0]->value : 0, | ||||
|             'paidToDateCurrency' => count($paidToDate) && $paidToDate[0]->currency_id ? $paidToDate[0]->currency_id : 0, | ||||
|             'balances' => count($balances) && $balances[0]->value ? $balances[0]->value : 0, | ||||
|             'balancesCurrency' => count($balances) && $balances[0]->currency_id ? $balances[0]->currency_id : 0, | ||||
|             'averageInvoice' => count($averageInvoice) && $averageInvoice[0]->invoice_avg ? $averageInvoice[0]->invoice_avg : 0, | ||||
|             'averageInvoiceCurrency' => count($averageInvoice) && $averageInvoice[0]->currency_id ? $averageInvoice[0]->currency_id : 0, | ||||
|             'paidToDate' => $paidToDate->count() && $paidToDate[0]->value ? $paidToDate[0]->value : 0, | ||||
|             'paidToDateCurrency' => $paidToDate->count() && $paidToDate[0]->currency_id ? $paidToDate[0]->currency_id : 0, | ||||
|             'balances' => $balances->count() && $balances[0]->value ? $balances[0]->value : 0, | ||||
|             'balancesCurrency' => $balances->count() && $balances[0]->currency_id ? $balances[0]->currency_id : 0, | ||||
|             'averageInvoice' => $averageInvoice->count() && $averageInvoice[0]->invoice_avg ? $averageInvoice[0]->invoice_avg : 0, | ||||
|             'averageInvoiceCurrency' => $averageInvoice->count() && $averageInvoice[0]->currency_id ? $averageInvoice[0]->currency_id : 0, | ||||
|             'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, | ||||
|             'activeClients' => $metrics ? $metrics->active_clients : 0, | ||||
|             'activities' => $this->createCollection($activities, new ActivityTransformer(), ENTITY_ACTIVITY), | ||||
|  | ||||
| @ -83,7 +83,7 @@ class DashboardController extends BaseController | ||||
|             'tasks' => $tasks, | ||||
|             'showBlueVinePromo' => $showBlueVinePromo, | ||||
|             'showWhiteLabelExpired' => $showWhiteLabelExpired, | ||||
|             'showExpenses' => count($expenses) && $account->isModuleEnabled(ENTITY_EXPENSE), | ||||
|             'showExpenses' => $expenses->count() && $account->isModuleEnabled(ENTITY_EXPENSE), | ||||
|             'headerClass' => in_array(\App::getLocale(), ['lt', 'pl', 'cs', 'sl', 'tr_TR']) ? 'in-large' : 'in-thin', | ||||
|             'footerClass' => in_array(\App::getLocale(), ['lt', 'pl', 'cs', 'sl', 'tr_TR']) ? '' : 'in-thin', | ||||
|         ]; | ||||
|  | ||||
| @ -105,11 +105,20 @@ class DocumentController extends BaseController | ||||
|                 'code' => 400, | ||||
|             ], 400); | ||||
|         } else { | ||||
|             return Response::json([ | ||||
|                 'error' => false, | ||||
|                 'document' => $doc_array, | ||||
|                 'code' => 200, | ||||
|             ], 200); | ||||
|             if ($request->grapesjs) { | ||||
|                 $response = [ | ||||
|                     'data' => [ | ||||
|                         $result->getProposalUrl() | ||||
|                     ] | ||||
|                 ]; | ||||
|             } else { | ||||
|                 $response = [ | ||||
|                     'error' => false, | ||||
|                     'document' => $doc_array, | ||||
|                     'code' => 200, | ||||
|                 ]; | ||||
|             } | ||||
|             return Response::json($response, 200); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -161,7 +161,7 @@ class HomeController extends BaseController | ||||
|                 } | ||||
|                 $subject .= '] '; | ||||
|             } else { | ||||
|                 $subject .= 'Self-Host | '; | ||||
|                 $subject .= 'Self-Host] | '; | ||||
|             } | ||||
|             $subject .= date('M jS, g:ia'); | ||||
|             $message->to(env('CONTACT_EMAIL', 'contact@invoiceninja.com')) | ||||
|  | ||||
| @ -68,6 +68,11 @@ class InvoiceApiController extends BaseAPIController | ||||
|             $invoices->whereInvoiceNumber($invoiceNumber); | ||||
|         } | ||||
| 
 | ||||
|         // Fllter by status
 | ||||
|         if ($statusId = Input::get('status_id')) { | ||||
|             $invoices->where('invoice_status_id', '>=', $statusId); | ||||
|         } | ||||
| 
 | ||||
|         return $this->listResponse($invoices); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -403,7 +403,11 @@ class InvoiceController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         if (! Auth::user()->confirmed) { | ||||
|             $errorMessage = trans(Auth::user()->registered ? 'texts.confirmation_required' : 'texts.registration_required'); | ||||
|             if (Auth::user()->registered) { | ||||
|                 $errorMessage = trans('texts.confirmation_required', ['link' => link_to('/resend_confirmation', trans('texts.click_here'))]); | ||||
|             } else { | ||||
|                 $errorMessage = trans('texts.registration_required'); | ||||
|             } | ||||
|             Session::flash('error', $errorMessage); | ||||
| 
 | ||||
|             return Redirect::to('invoices/'.$invoice->public_id.'/edit'); | ||||
|  | ||||
| @ -294,6 +294,7 @@ class NinjaController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         $user = Auth::user(); | ||||
|         $account = $user->account; | ||||
|         $url = NINJA_APP_URL . '/buy_now'; | ||||
|         $contactKey = $user->primaryAccount()->account_key; | ||||
| 
 | ||||
| @ -301,9 +302,17 @@ class NinjaController extends BaseController | ||||
|             'account_key' => NINJA_LICENSE_ACCOUNT_KEY, | ||||
|             'contact_key' => $contactKey, | ||||
|             'product_id' => PRODUCT_WHITE_LABEL, | ||||
|             'first_name' => Auth::user()->first_name, | ||||
|             'last_name' => Auth::user()->last_name, | ||||
|             'email' => Auth::user()->email, | ||||
|             'first_name' => $user->first_name, | ||||
|             'last_name' => $user->last_name, | ||||
|             'email' => $user->email, | ||||
|             'name' => $account->name, | ||||
|             'address1' => $account->address1, | ||||
|             'address2' => $account->address2, | ||||
|             'city' => $account->city, | ||||
|             'state' => $account->state, | ||||
|             'postal_code' => $account->postal_code, | ||||
|             'country_id' => $account->country_id, | ||||
|             'vat_number' => $account->vat_number, | ||||
|             'return_link' => true, | ||||
|         ]; | ||||
| 
 | ||||
|  | ||||
| @ -356,12 +356,14 @@ class OnlinePaymentController extends BaseController | ||||
|                 return redirect()->to("{$failureUrl}/?error=" . $validator->errors()->first()); | ||||
|             } | ||||
| 
 | ||||
|             $data = [ | ||||
|                 'currency_id' => $account->currency_id, | ||||
|                 'contact' => Input::all(), | ||||
|                 'custom_value1' => Input::get('custom_client1'), | ||||
|                 'custom_value2' => Input::get('custom_client2'), | ||||
|             ]; | ||||
|             $data = request()->all(); | ||||
|             $data['currency_id'] = $account->currency_id; | ||||
|             $data['custom_value1'] = request()->custom_client1; | ||||
|             $data['custom_value2'] = request()->custom_client2; | ||||
|             $data['contact'] = request()->all(); | ||||
|             $data['contact']['custom_value1'] = request()->custom_contact1; | ||||
|             $data['contact']['custom_value2'] = request()->custom_contact2; | ||||
| 
 | ||||
|             if (request()->currency_code) { | ||||
|                 $data['currency_code'] = request()->currency_code; | ||||
|             } | ||||
| @ -425,20 +427,23 @@ class OnlinePaymentController extends BaseController | ||||
|     { | ||||
|         if (Utils::isNinja()) { | ||||
|             $subdomain = Utils::getSubdomain(\Request::server('HTTP_HOST')); | ||||
|             if (! $subdomain || $subdomain == 'app') { | ||||
|                 exit('Invalid subdomain'); | ||||
|             } | ||||
|             $account = Account::whereSubdomain($subdomain)->first(); | ||||
|         } else { | ||||
|             $account = Account::first(); | ||||
|         } | ||||
| 
 | ||||
|         if (! $account) { | ||||
|             exit("Account not found"); | ||||
|             exit('Account not found'); | ||||
|         } | ||||
| 
 | ||||
|         $accountGateway = $account->account_gateways() | ||||
|             ->whereGatewayId(GATEWAY_STRIPE)->first(); | ||||
| 
 | ||||
|         if (! $account) { | ||||
|             exit("Apple merchant id not set"); | ||||
|             exit('Apple merchant id not set'); | ||||
|         } | ||||
| 
 | ||||
|         echo $accountGateway->getConfigField('appleMerchantId'); | ||||
|  | ||||
| @ -89,16 +89,29 @@ class PaymentController extends BaseController | ||||
|      */ | ||||
|     public function create(PaymentRequest $request) | ||||
|     { | ||||
|         $user = auth()->user(); | ||||
|         $account = $user->account; | ||||
| 
 | ||||
|         $invoices = Invoice::scope() | ||||
|                     ->invoices() | ||||
|                     ->where('invoices.invoice_status_id', '!=', INVOICE_STATUS_PAID) | ||||
|                     ->with('client', 'invoice_status') | ||||
|                     ->orderBy('invoice_number')->get(); | ||||
| 
 | ||||
|         $clientPublicId = Input::old('client') ? Input::old('client') : ($request->client_id ?: 0); | ||||
|         $invoicePublicId = Input::old('invoice') ? Input::old('invoice') : ($request->invoice_id ?: 0); | ||||
| 
 | ||||
|         $totalCredit = false; | ||||
|         if ($clientPublicId && $client = Client::scope($clientPublicId)->first()) { | ||||
|             $totalCredit = $account->formatMoney($client->getTotalCredit(), $client); | ||||
|         } elseif ($invoicePublicId && $invoice = Invoice::scope($invoicePublicId)->first()) { | ||||
|             $totalCredit = $account->formatMoney($invoice->client->getTotalCredit(), $client); | ||||
|         } | ||||
| 
 | ||||
|         $data = [ | ||||
|             'account' => Auth::user()->account, | ||||
|             'clientPublicId' => Input::old('client') ? Input::old('client') : ($request->client_id ?: 0), | ||||
|             'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : ($request->invoice_id ?: 0), | ||||
|             'clientPublicId' => $clientPublicId, | ||||
|             'invoicePublicId' => $invoicePublicId, | ||||
|             'invoice' => null, | ||||
|             'invoices' => $invoices, | ||||
|             'payment' => null, | ||||
| @ -106,7 +119,9 @@ class PaymentController extends BaseController | ||||
|             'url' => 'payments', | ||||
|             'title' => trans('texts.new_payment'), | ||||
|             'paymentTypeId' => Input::get('paymentTypeId'), | ||||
|             'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), ]; | ||||
|             'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), | ||||
|             'totalCredit' => $totalCredit, | ||||
|         ]; | ||||
| 
 | ||||
|         return View::make('payments.edit', $data); | ||||
|     } | ||||
|  | ||||
							
								
								
									
										160
									
								
								app/Http/Controllers/PaymentTermApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								app/Http/Controllers/PaymentTermApiController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,160 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Controllers; | ||||
| 
 | ||||
| use App\Http\Requests\CreatePaymentTermAPIRequest; | ||||
| use App\Http\Requests\PaymentTermRequest; | ||||
| use App\Http\Requests\UpdatePaymentTermRequest; | ||||
| use App\Libraries\Utils; | ||||
| use App\Models\PaymentTerm; | ||||
| use App\Ninja\Repositories\PaymentTermRepository; | ||||
| use Illuminate\Support\Facades\Input; | ||||
| 
 | ||||
| class PaymentTermApiController extends BaseAPIController | ||||
| { | ||||
|     /** | ||||
|      * @var PaymentTermRepository | ||||
|      */ | ||||
|     protected $paymentTermRepo; | ||||
|     protected $entityType = ENTITY_PAYMENT_TERM; | ||||
| 
 | ||||
|     /** | ||||
|      * PaymentTermApiController constructor. | ||||
|      * | ||||
|      * @param PaymentTermRepository $paymentTermRepo | ||||
|      */ | ||||
|     public function __construct(PaymentTermRepository $paymentTermRepo) | ||||
|     { | ||||
|         parent::__construct(); | ||||
| 
 | ||||
|         $this->paymentTermRepo = $paymentTermRepo; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @SWG\Get( | ||||
|      *   path="/paymentTerms", | ||||
|      *   summary="List payment terms", | ||||
|      *   operationId="listPaymentTerms", | ||||
|      *   tags={"payment terms"}, | ||||
|      *   @SWG\Response( | ||||
|      *     response=200, | ||||
|      *     description="A list of payment terms", | ||||
|      *      @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/PaymentTerms")) | ||||
|      *   ), | ||||
|      *   @SWG\Response( | ||||
|      *     response="default", | ||||
|      *     description="an ""unexpected"" error" | ||||
|      *   ) | ||||
|      * ) | ||||
|      */ | ||||
| 
 | ||||
|     public function index() | ||||
|     { | ||||
| 
 | ||||
|         $paymentTerms = PaymentTerm::scope() | ||||
|             ->orWhere('account_id',0) | ||||
|             ->orderBy('num_days', 'asc'); | ||||
| 
 | ||||
|         return $this->listResponse($paymentTerms); | ||||
|     } | ||||
| 
 | ||||
|         /** | ||||
|          * @SWG\Get( | ||||
|          *   path="/paymentTerms/{payment_term_id}", | ||||
|          *   summary="Retrieve a payment term", | ||||
|          *   operationId="getPaymentTermId", | ||||
|          *   tags={"payment term"}, | ||||
|          *   @SWG\Parameter( | ||||
|          *     in="path", | ||||
|          *     name="payment_term_id", | ||||
|          *     type="integer", | ||||
|          *     required=true | ||||
|          *   ), | ||||
|          *   @SWG\Response( | ||||
|          *     response=200, | ||||
|          *     description="A single payment term", | ||||
|          *      @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/PaymentTerms")) | ||||
|          *   ), | ||||
|          *   @SWG\Response( | ||||
|          *     response="default", | ||||
|          *     description="an ""unexpected"" error" | ||||
|          *   ) | ||||
|          * ) | ||||
|          */ | ||||
| 
 | ||||
|     public function show(PaymentTermRequest $request) | ||||
|     { | ||||
|         return $this->itemResponse($request->entity()); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|         /** | ||||
|          * @SWG\Post( | ||||
|          *   path="/paymentTerms", | ||||
|          *   summary="Create a payment Term", | ||||
|          *   operationId="createPaymentTerm", | ||||
|          *   tags={"payment term"}, | ||||
|          *   @SWG\Parameter( | ||||
|          *     in="body", | ||||
|          *     name="payment term", | ||||
|          *     @SWG\Schema(ref="#/definitions/PaymentTerm") | ||||
|          *   ), | ||||
|          *   @SWG\Response( | ||||
|          *     response=200, | ||||
|          *     description="New payment Term", | ||||
|          *      @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/PaymentTerm")) | ||||
|          *   ), | ||||
|          *   @SWG\Response( | ||||
|          *     response="default", | ||||
|          *     description="an ""unexpected"" error" | ||||
|          *   ) | ||||
|          * ) | ||||
|          */ | ||||
|     public function store(CreatePaymentTermAPIRequest $request) | ||||
|     { | ||||
| 
 | ||||
|         $paymentTerm = PaymentTerm::createNew(); | ||||
| 
 | ||||
|         $paymentTerm->num_days = Utils::parseInt(Input::get('num_days')); | ||||
|         $paymentTerm->name = 'Net ' . $paymentTerm->num_days; | ||||
|         $paymentTerm->save(); | ||||
| 
 | ||||
|         return $this->itemResponse($paymentTerm); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @SWG\Delete( | ||||
|      *   path="/paymentTerm/{num_days}", | ||||
|      *   summary="Delete a payment term", | ||||
|      *   operationId="deletePaymentTerm", | ||||
|      *   tags={"payment term"}, | ||||
|      *   @SWG\Parameter( | ||||
|      *     in="path", | ||||
|      *     name="num_days", | ||||
|      *     type="integer", | ||||
|      *     required=true | ||||
|      *   ), | ||||
|      *   @SWG\Response( | ||||
|      *     response=200, | ||||
|      *     description="Deleted payment Term", | ||||
|      *      @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/PaymentTerm")) | ||||
|      *   ), | ||||
|      *   @SWG\Response( | ||||
|      *     response="default", | ||||
|      *     description="an ""unexpected"" error" | ||||
|      *   ) | ||||
|      * ) | ||||
|      */ | ||||
|     public function destroy($numDays) | ||||
|     { | ||||
| 
 | ||||
|         $paymentTerm = PaymentTerm::where('num_days', $numDays)->first(); | ||||
| 
 | ||||
|         if(!$paymentTerm || $paymentTerm->account_id == 0) | ||||
|             return $this->errorResponse(['message'=>'Cannot delete a default or non existent Payment Term'], 400); | ||||
| 
 | ||||
|         $this->paymentTermRepo->archive($paymentTerm); | ||||
| 
 | ||||
|         return $this->itemResponse($paymentTerm); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										128
									
								
								app/Http/Controllers/ProposalCategoryController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								app/Http/Controllers/ProposalCategoryController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Controllers; | ||||
| 
 | ||||
| use App\Http\Requests\CreateProposalCategoryRequest; | ||||
| use App\Http\Requests\ProposalCategoryRequest; | ||||
| use App\Http\Requests\UpdateProposalCategoryRequest; | ||||
| use App\Models\Invoice; | ||||
| use App\Models\ProposalCategory; | ||||
| use App\Ninja\Datatables\ProposalCategoryDatatable; | ||||
| use App\Ninja\Repositories\ProposalCategoryRepository; | ||||
| use App\Services\ProposalCategoryService; | ||||
| use Auth; | ||||
| use Input; | ||||
| use Session; | ||||
| use View; | ||||
| 
 | ||||
| class ProposalCategoryController extends BaseController | ||||
| { | ||||
|     protected $proposalCategoryRepo; | ||||
|     protected $proposalCategoryService; | ||||
|     protected $entityType = ENTITY_PROPOSAL_CATEGORY; | ||||
| 
 | ||||
|     public function __construct(ProposalCategoryRepository $proposalCategoryRepo, ProposalCategoryService $proposalCategoryService) | ||||
|     { | ||||
|         $this->proposalCategoryRepo = $proposalCategoryRepo; | ||||
|         $this->proposalCategoryService = $proposalCategoryService; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Display a listing of the resource. | ||||
|      * | ||||
|      * @return Response | ||||
|      */ | ||||
|     public function index() | ||||
|     { | ||||
|         return View::make('list_wrapper', [ | ||||
|             'entityType' => ENTITY_PROPOSAL_CATEGORY, | ||||
|             'datatable' => new ProposalCategoryDatatable(), | ||||
|             'title' => trans('texts.proposal_categories'), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public function getDatatable($expensePublicId = null) | ||||
|     { | ||||
|         $search = Input::get('sSearch'); | ||||
|         $userId = Auth::user()->filterId(); | ||||
| 
 | ||||
|         return $this->proposalCategoryService->getDatatable($search, $userId); | ||||
|     } | ||||
| 
 | ||||
|     public function create(ProposalCategoryRequest $request) | ||||
|     { | ||||
|         $data = [ | ||||
|             'account' => auth()->user()->account, | ||||
|             'category' => null, | ||||
|             'method' => 'POST', | ||||
|             'url' => 'proposals/categories', | ||||
|             'title' => trans('texts.new_proposal_category'), | ||||
|             'quotes' => Invoice::scope()->with('client.contacts')->quotes()->orderBy('id')->get(), | ||||
|             'templates' => ProposalCategory::scope()->orderBy('name')->get(), | ||||
|             'quotePublicId' => $request->quote_id, | ||||
|         ]; | ||||
| 
 | ||||
|         return View::make('proposals/categories.edit', $data); | ||||
|     } | ||||
| 
 | ||||
|     public function show($publicId) | ||||
|     { | ||||
|         Session::reflash(); | ||||
| 
 | ||||
|         return redirect("proposals/categories/$publicId/edit"); | ||||
|     } | ||||
| 
 | ||||
|     public function edit(ProposalCategoryRequest $request) | ||||
|     { | ||||
|         $proposalCategory = $request->entity(); | ||||
| 
 | ||||
|         $data = [ | ||||
|             'account' => auth()->user()->account, | ||||
|             'category' => $proposalCategory, | ||||
|             'method' => 'PUT', | ||||
|             'url' => 'proposals/categories/' . $proposalCategory->public_id, | ||||
|             'title' => trans('texts.edit_proposal_category'), | ||||
|         ]; | ||||
| 
 | ||||
|         return View::make('proposals/categories.edit', $data); | ||||
|     } | ||||
| 
 | ||||
|     public function store(CreateProposalCategoryRequest $request) | ||||
|     { | ||||
|         $proposalCategory = $this->proposalCategoryService->save($request->input()); | ||||
| 
 | ||||
|         Session::flash('message', trans('texts.created_proposal_category')); | ||||
| 
 | ||||
|         return redirect()->to($proposalCategory->getRoute()); | ||||
|     } | ||||
| 
 | ||||
|     public function update(UpdateProposalCategoryRequest $request) | ||||
|     { | ||||
|         $proposalCategory = $this->proposalCategoryService->save($request->input(), $request->entity()); | ||||
| 
 | ||||
|         Session::flash('message', trans('texts.updated_proposal_category')); | ||||
| 
 | ||||
|         $action = Input::get('action'); | ||||
|         if (in_array($action, ['archive', 'delete', 'restore'])) { | ||||
|             return self::bulk(); | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->to($proposalCategory->getRoute()); | ||||
|     } | ||||
| 
 | ||||
|     public function bulk() | ||||
|     { | ||||
|         $action = Input::get('action'); | ||||
|         $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids'); | ||||
| 
 | ||||
|         $count = $this->proposalCategoryService->bulk($ids, $action); | ||||
| 
 | ||||
|         if ($count > 0) { | ||||
|             $field = $count == 1 ? "{$action}d_proposal_category" : "{$action}d_proposal_categories"; | ||||
|             $message = trans("texts.$field", ['count' => $count]); | ||||
|             Session::flash('message', $message); | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->to('/proposals/categories'); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										178
									
								
								app/Http/Controllers/ProposalController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								app/Http/Controllers/ProposalController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,178 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Controllers; | ||||
| 
 | ||||
| use App\Http\Requests\CreateProposalRequest; | ||||
| use App\Http\Requests\ProposalRequest; | ||||
| use App\Http\Requests\UpdateProposalRequest; | ||||
| use App\Jobs\SendInvoiceEmail; | ||||
| use App\Models\Invoice; | ||||
| use App\Models\Proposal; | ||||
| use App\Models\ProposalTemplate; | ||||
| use App\Ninja\Mailers\ContactMailer; | ||||
| use App\Ninja\Datatables\ProposalDatatable; | ||||
| use App\Ninja\Repositories\ProposalRepository; | ||||
| use App\Services\ProposalService; | ||||
| use Auth; | ||||
| use Input; | ||||
| use Session; | ||||
| use View; | ||||
| use mPDF; | ||||
| 
 | ||||
| class ProposalController extends BaseController | ||||
| { | ||||
|     protected $proposalRepo; | ||||
|     protected $proposalService; | ||||
|     protected $contactMailer; | ||||
| 
 | ||||
|     protected $entityType = ENTITY_PROPOSAL; | ||||
| 
 | ||||
|     public function __construct(ProposalRepository $proposalRepo, ProposalService $proposalService, ContactMailer $contactMailer) | ||||
|     { | ||||
|         $this->proposalRepo = $proposalRepo; | ||||
|         $this->proposalService = $proposalService; | ||||
|         $this->contactMailer = $contactMailer; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Display a listing of the resource. | ||||
|      * | ||||
|      * @return Response | ||||
|      */ | ||||
|     public function index() | ||||
|     { | ||||
|         return View::make('list_wrapper', [ | ||||
|             'entityType' => ENTITY_PROPOSAL, | ||||
|             'datatable' => new ProposalDatatable(), | ||||
|             'title' => trans('texts.proposals'), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public function getDatatable($expensePublicId = null) | ||||
|     { | ||||
|         $search = Input::get('sSearch'); | ||||
|         $userId = Auth::user()->filterId(); | ||||
| 
 | ||||
|         return $this->proposalService->getDatatable($search, $userId); | ||||
|     } | ||||
| 
 | ||||
|     public function create(ProposalRequest $request) | ||||
|     { | ||||
|         $data = array_merge($this->getViewmodel(), [ | ||||
|             'proposal' => null, | ||||
|             'method' => 'POST', | ||||
|             'url' => 'proposals', | ||||
|             'title' => trans('texts.new_proposal'), | ||||
|             'invoices' => Invoice::scope()->with('client.contacts', 'client.country')->unapprovedQuotes()->orderBy('id')->get(), | ||||
|             'invoicePublicId' => $request->invoice_id, | ||||
|             'templatePublicId' => $request->proposal_template_id, | ||||
|         ]); | ||||
| 
 | ||||
|         return View::make('proposals.edit', $data); | ||||
|     } | ||||
| 
 | ||||
|     public function show($publicId) | ||||
|     { | ||||
|         Session::reflash(); | ||||
| 
 | ||||
|         return redirect("proposals/$publicId/edit"); | ||||
|     } | ||||
| 
 | ||||
|     public function edit(ProposalRequest $request) | ||||
|     { | ||||
|         $proposal = $request->entity(); | ||||
| 
 | ||||
|         $data = array_merge($this->getViewmodel(), [ | ||||
|             'proposal' => $proposal, | ||||
|             'entity' => $proposal, | ||||
|             'method' => 'PUT', | ||||
|             'url' => 'proposals/' . $proposal->public_id, | ||||
|             'title' => trans('texts.edit_proposal'), | ||||
|             'invoices' => Invoice::scope()->with('client.contacts', 'client.country')->unapprovedQuotes($proposal->invoice_id)->orderBy('id')->get(), | ||||
|             'invoicePublicId' => $proposal->invoice ? $proposal->invoice->public_id : null, | ||||
|             'templatePublicId' => $proposal->proposal_template ? $proposal->proposal_template->public_id : null, | ||||
|         ]); | ||||
| 
 | ||||
|         return View::make('proposals.edit', $data); | ||||
|     } | ||||
| 
 | ||||
|     private function getViewmodel() | ||||
|     { | ||||
|         $account = auth()->user()->account; | ||||
|         $templates = ProposalTemplate::whereAccountId($account->id)->orderBy('name')->get(); | ||||
| 
 | ||||
|         if (! $templates->count()) { | ||||
|             $templates = ProposalTemplate::whereNull('account_id')->orderBy('name')->get(); | ||||
|         } | ||||
| 
 | ||||
|         $data = [ | ||||
|             'templates' => $templates, | ||||
|             'account' => $account, | ||||
|         ]; | ||||
| 
 | ||||
|         return $data; | ||||
|     } | ||||
| 
 | ||||
|     public function store(CreateProposalRequest $request) | ||||
|     { | ||||
|         $proposal = $this->proposalService->save($request->input()); | ||||
|         $action = Input::get('action'); | ||||
| 
 | ||||
|         if ($action == 'email') { | ||||
|             $this->dispatch(new SendInvoiceEmail($proposal->invoice, auth()->user()->id, false, false, $proposal)); | ||||
|             Session::flash('message', trans('texts.emailed_proposal')); | ||||
|         } else { | ||||
|             Session::flash('message', trans('texts.created_proposal')); | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->to($proposal->getRoute()); | ||||
|     } | ||||
| 
 | ||||
|     public function update(UpdateProposalRequest $request) | ||||
|     { | ||||
|         $proposal = $this->proposalService->save($request->input(), $request->entity()); | ||||
|         $action = Input::get('action'); | ||||
| 
 | ||||
|         if (in_array($action, ['archive', 'delete', 'restore'])) { | ||||
|             return self::bulk(); | ||||
|         } | ||||
| 
 | ||||
|         if ($action == 'email') { | ||||
|             $this->dispatch(new SendInvoiceEmail($proposal->invoice, auth()->user()->id, false, false, $proposal)); | ||||
|             Session::flash('message', trans('texts.emailed_proposal')); | ||||
|         } else { | ||||
|             Session::flash('message', trans('texts.updated_proposal')); | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->to($proposal->getRoute()); | ||||
|     } | ||||
| 
 | ||||
|     public function bulk() | ||||
|     { | ||||
|         $action = Input::get('bulk_action') ?: Input::get('action'); | ||||
|         $ids = Input::get('bulk_public_id') ?: (Input::get('public_id') ?: Input::get('ids')); | ||||
| 
 | ||||
|         $count = $this->proposalService->bulk($ids, $action); | ||||
| 
 | ||||
|         if ($count > 0) { | ||||
|             $field = $count == 1 ? "{$action}d_proposal" : "{$action}d_proposals"; | ||||
|             $message = trans("texts.$field", ['count' => $count]); | ||||
|             Session::flash('message', $message); | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->to('/proposals'); | ||||
|     } | ||||
| 
 | ||||
|     public function download(ProposalRequest $request) | ||||
|     { | ||||
|         $proposal = $request->entity(); | ||||
| 
 | ||||
|         $mpdf = new mPDF(); | ||||
|         $mpdf->showImageErrors = true; | ||||
|         $mpdf->WriteHTML($proposal->present()->htmlDocument); | ||||
| 
 | ||||
|         //$mpdf->Output();
 | ||||
| 
 | ||||
|         $mpdf->Output($proposal->present()->filename, 'D'); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										732
									
								
								app/Http/Controllers/ProposalSnippetController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										732
									
								
								app/Http/Controllers/ProposalSnippetController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,732 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Controllers; | ||||
| 
 | ||||
| use App\Http\Requests\CreateProposalSnippetRequest; | ||||
| use App\Http\Requests\ProposalSnippetRequest; | ||||
| use App\Http\Requests\UpdateProposalSnippetRequest; | ||||
| use App\Models\Invoice; | ||||
| use App\Models\ProposalSnippet; | ||||
| use App\Models\ProposalCategory; | ||||
| use App\Ninja\Datatables\ProposalSnippetDatatable; | ||||
| use App\Ninja\Repositories\ProposalSnippetRepository; | ||||
| use App\Services\ProposalSnippetService; | ||||
| use Auth; | ||||
| use Input; | ||||
| use Session; | ||||
| use View; | ||||
| 
 | ||||
| class ProposalSnippetController extends BaseController | ||||
| { | ||||
|     protected $proposalSnippetRepo; | ||||
|     protected $proposalSnippetService; | ||||
|     protected $entityType = ENTITY_PROPOSAL_SNIPPET; | ||||
| 
 | ||||
|     public function __construct(ProposalSnippetRepository $proposalSnippetRepo, ProposalSnippetService $proposalSnippetService) | ||||
|     { | ||||
|         $this->proposalSnippetRepo = $proposalSnippetRepo; | ||||
|         $this->proposalSnippetService = $proposalSnippetService; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Display a listing of the resource. | ||||
|      * | ||||
|      * @return Response | ||||
|      */ | ||||
|     public function index() | ||||
|     { | ||||
|         return View::make('list_wrapper', [ | ||||
|             'entityType' => ENTITY_PROPOSAL_SNIPPET, | ||||
|             'datatable' => new ProposalSnippetDatatable(), | ||||
|             'title' => trans('texts.proposal_snippets'), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public function getDatatable($expensePublicId = null) | ||||
|     { | ||||
|         $search = Input::get('sSearch'); | ||||
|         $userId = Auth::user()->filterId(); | ||||
| 
 | ||||
|         return $this->proposalSnippetService->getDatatable($search, $userId); | ||||
|     } | ||||
| 
 | ||||
|     public function create(ProposalSnippetRequest $request) | ||||
|     { | ||||
|         $data = [ | ||||
|             'account' => auth()->user()->account, | ||||
|             'snippet' => null, | ||||
|             'method' => 'POST', | ||||
|             'url' => 'proposals/snippets', | ||||
|             'title' => trans('texts.new_proposal_snippet'), | ||||
|             'categories' => ProposalCategory::scope()->orderBy('name')->get(), | ||||
|             'categoryPublicId' => 0, | ||||
|             'icons' => $this->getIcons(), | ||||
|         ]; | ||||
| 
 | ||||
|         return View::make('proposals/snippets/edit', $data); | ||||
|     } | ||||
| 
 | ||||
|     public function show($publicId) | ||||
|     { | ||||
|         Session::reflash(); | ||||
| 
 | ||||
|         return redirect("proposals/snippets/$publicId/edit"); | ||||
|     } | ||||
| 
 | ||||
|     public function edit(ProposalSnippetRequest $request) | ||||
|     { | ||||
|         $proposalSnippet = $request->entity(); | ||||
| 
 | ||||
|         $data = [ | ||||
|             'account' => auth()->user()->account, | ||||
|             'snippet' => $proposalSnippet, | ||||
|             'entity' => $proposalSnippet, | ||||
|             'method' => 'PUT', | ||||
|             'url' => 'proposals/snippets/' . $proposalSnippet->public_id, | ||||
|             'title' => trans('texts.edit_proposal_snippet'), | ||||
|             'categories' => ProposalCategory::scope()->orderBy('name')->get(), | ||||
|             'categoryPublicId' => $proposalSnippet->proposal_category ? $proposalSnippet->proposal_category->public_id : null, | ||||
|             'icons' => $this->getIcons(), | ||||
|         ]; | ||||
| 
 | ||||
|         return View::make('proposals/snippets.edit', $data); | ||||
|     } | ||||
| 
 | ||||
|     public function store(CreateProposalSnippetRequest $request) | ||||
|     { | ||||
|         $proposalSnippet = $this->proposalSnippetService->save($request->input()); | ||||
| 
 | ||||
|         Session::flash('message', trans('texts.created_proposal_snippet')); | ||||
| 
 | ||||
|         return redirect()->to($proposalSnippet->getRoute()); | ||||
|     } | ||||
| 
 | ||||
|     public function update(UpdateProposalSnippetRequest $request) | ||||
|     { | ||||
|         $proposalSnippet = $this->proposalSnippetService->save($request->input(), $request->entity()); | ||||
| 
 | ||||
|         Session::flash('message', trans('texts.updated_proposal_snippet')); | ||||
| 
 | ||||
|         $action = Input::get('action'); | ||||
|         if (in_array($action, ['archive', 'delete', 'restore'])) { | ||||
|             return self::bulk(); | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->to($proposalSnippet->getRoute()); | ||||
|     } | ||||
| 
 | ||||
|     public function bulk() | ||||
|     { | ||||
|         $action = Input::get('action'); | ||||
|         $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids'); | ||||
| 
 | ||||
|         $count = $this->proposalSnippetService->bulk($ids, $action); | ||||
| 
 | ||||
|         if ($count > 0) { | ||||
|             $field = $count == 1 ? "{$action}d_proposal_snippet" : "{$action}d_proposal_snippets"; | ||||
|             $message = trans("texts.$field", ['count' => $count]); | ||||
|             Session::flash('message', $message); | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->to('/proposals/snippets'); | ||||
|     } | ||||
| 
 | ||||
|     private function getIcons() { | ||||
|         $data = []; | ||||
|         $icons = [ | ||||
|             ['name'=>'glass','code'=>'f000'], | ||||
|             ['name'=>'music','code'=>'f001'], | ||||
|             ['name'=>'search','code'=>'f002'], | ||||
|             ['name'=>'envelope-o','code'=>'f003'], | ||||
|             ['name'=>'heart','code'=>'f004'], | ||||
|             ['name'=>'star','code'=>'f005'], | ||||
|             ['name'=>'star-o','code'=>'f006'], | ||||
|             ['name'=>'user','code'=>'f007'], | ||||
|             ['name'=>'film','code'=>'f008'], | ||||
|             ['name'=>'th-large','code'=>'f009'], | ||||
|             ['name'=>'th','code'=>'f00a'], | ||||
|             ['name'=>'th-list','code'=>'f00b'], | ||||
|             ['name'=>'check','code'=>'f00c'], | ||||
|             ['name'=>'times','code'=>'f00d'], | ||||
|             ['name'=>'search-plus','code'=>'f00e'], | ||||
|             ['name'=>'search-minus','code'=>'f010'], | ||||
|             ['name'=>'power-off','code'=>'f011'], | ||||
|             ['name'=>'signal','code'=>'f012'], | ||||
|             ['name'=>'cog','code'=>'f013'], | ||||
|             ['name'=>'trash-o','code'=>'f014'], | ||||
|             ['name'=>'home','code'=>'f015'], | ||||
|             ['name'=>'file-o','code'=>'f016'], | ||||
|             ['name'=>'clock-o','code'=>'f017'], | ||||
|             ['name'=>'road','code'=>'f018'], | ||||
|             ['name'=>'download','code'=>'f019'], | ||||
|             ['name'=>'arrow-circle-o-down','code'=>'f01a'], | ||||
|             ['name'=>'arrow-circle-o-up','code'=>'f01b'], | ||||
|             ['name'=>'inbox','code'=>'f01c'], | ||||
|             ['name'=>'play-circle-o','code'=>'f01d'], | ||||
|             ['name'=>'repeat','code'=>'f01e'], | ||||
|             ['name'=>'refresh','code'=>'f021'], | ||||
|             ['name'=>'list-alt','code'=>'f022'], | ||||
|             ['name'=>'lock','code'=>'f023'], | ||||
|             ['name'=>'flag','code'=>'f024'], | ||||
|             ['name'=>'headphones','code'=>'f025'], | ||||
|             ['name'=>'volume-off','code'=>'f026'], | ||||
|             ['name'=>'volume-down','code'=>'f027'], | ||||
|             ['name'=>'volume-up','code'=>'f028'], | ||||
|             ['name'=>'qrcode','code'=>'f029'], | ||||
|             ['name'=>'barcode','code'=>'f02a'], | ||||
|             ['name'=>'tag','code'=>'f02b'], | ||||
|             ['name'=>'tags','code'=>'f02c'], | ||||
|             ['name'=>'book','code'=>'f02d'], | ||||
|             ['name'=>'bookmark','code'=>'f02e'], | ||||
|             ['name'=>'print','code'=>'f02f'], | ||||
|             ['name'=>'camera','code'=>'f030'], | ||||
|             ['name'=>'font','code'=>'f031'], | ||||
|             ['name'=>'bold','code'=>'f032'], | ||||
|             ['name'=>'italic','code'=>'f033'], | ||||
|             ['name'=>'text-height','code'=>'f034'], | ||||
|             ['name'=>'text-width','code'=>'f035'], | ||||
|             ['name'=>'align-left','code'=>'f036'], | ||||
|             ['name'=>'align-center','code'=>'f037'], | ||||
|             ['name'=>'align-right','code'=>'f038'], | ||||
|             ['name'=>'align-justify','code'=>'f039'], | ||||
|             ['name'=>'list','code'=>'f03a'], | ||||
|             ['name'=>'outdent','code'=>'f03b'], | ||||
|             ['name'=>'indent','code'=>'f03c'], | ||||
|             ['name'=>'video-camera','code'=>'f03d'], | ||||
|             ['name'=>'picture-o','code'=>'f03e'], | ||||
|             ['name'=>'pencil','code'=>'f040'], | ||||
|             ['name'=>'map-marker','code'=>'f041'], | ||||
|             ['name'=>'adjust','code'=>'f042'], | ||||
|             ['name'=>'tint','code'=>'f043'], | ||||
|             ['name'=>'pencil-square-o','code'=>'f044'], | ||||
|             ['name'=>'share-square-o','code'=>'f045'], | ||||
|             ['name'=>'check-square-o','code'=>'f046'], | ||||
|             ['name'=>'arrows','code'=>'f047'], | ||||
|             ['name'=>'step-backward','code'=>'f048'], | ||||
|             ['name'=>'fast-backward','code'=>'f049'], | ||||
|             ['name'=>'backward','code'=>'f04a'], | ||||
|             ['name'=>'play','code'=>'f04b'], | ||||
|             ['name'=>'pause','code'=>'f04c'], | ||||
|             ['name'=>'stop','code'=>'f04d'], | ||||
|             ['name'=>'forward','code'=>'f04e'], | ||||
|             ['name'=>'fast-forward','code'=>'f050'], | ||||
|             ['name'=>'step-forward','code'=>'f051'], | ||||
|             ['name'=>'eject','code'=>'f052'], | ||||
|             ['name'=>'chevron-left','code'=>'f053'], | ||||
|             ['name'=>'chevron-right','code'=>'f054'], | ||||
|             ['name'=>'plus-circle','code'=>'f055'], | ||||
|             ['name'=>'minus-circle','code'=>'f056'], | ||||
|             ['name'=>'times-circle','code'=>'f057'], | ||||
|             ['name'=>'check-circle','code'=>'f058'], | ||||
|             ['name'=>'question-circle','code'=>'f059'], | ||||
|             ['name'=>'info-circle','code'=>'f05a'], | ||||
|             ['name'=>'crosshairs','code'=>'f05b'], | ||||
|             ['name'=>'times-circle-o','code'=>'f05c'], | ||||
|             ['name'=>'check-circle-o','code'=>'f05d'], | ||||
|             ['name'=>'ban','code'=>'f05e'], | ||||
|             ['name'=>'arrow-left','code'=>'f060'], | ||||
|             ['name'=>'arrow-right','code'=>'f061'], | ||||
|             ['name'=>'arrow-up','code'=>'f062'], | ||||
|             ['name'=>'arrow-down','code'=>'f063'], | ||||
|             ['name'=>'share','code'=>'f064'], | ||||
|             ['name'=>'expand','code'=>'f065'], | ||||
|             ['name'=>'compress','code'=>'f066'], | ||||
|             ['name'=>'plus','code'=>'f067'], | ||||
|             ['name'=>'minus','code'=>'f068'], | ||||
|             ['name'=>'asterisk','code'=>'f069'], | ||||
|             ['name'=>'exclamation-circle','code'=>'f06a'], | ||||
|             ['name'=>'gift','code'=>'f06b'], | ||||
|             ['name'=>'leaf','code'=>'f06c'], | ||||
|             ['name'=>'fire','code'=>'f06d'], | ||||
|             ['name'=>'eye','code'=>'f06e'], | ||||
|             ['name'=>'eye-slash','code'=>'f070'], | ||||
|             ['name'=>'exclamation-triangle','code'=>'f071'], | ||||
|             ['name'=>'plane','code'=>'f072'], | ||||
|             ['name'=>'calendar','code'=>'f073'], | ||||
|             ['name'=>'random','code'=>'f074'], | ||||
|             ['name'=>'comment','code'=>'f075'], | ||||
|             ['name'=>'magnet','code'=>'f076'], | ||||
|             ['name'=>'chevron-up','code'=>'f077'], | ||||
|             ['name'=>'chevron-down','code'=>'f078'], | ||||
|             ['name'=>'retweet','code'=>'f079'], | ||||
|             ['name'=>'shopping-cart','code'=>'f07a'], | ||||
|             ['name'=>'folder','code'=>'f07b'], | ||||
|             ['name'=>'folder-open','code'=>'f07c'], | ||||
|             ['name'=>'arrows-v','code'=>'f07d'], | ||||
|             ['name'=>'arrows-h','code'=>'f07e'], | ||||
|             ['name'=>'bar-chart','code'=>'f080'], | ||||
|             ['name'=>'twitter-square','code'=>'f081'], | ||||
|             ['name'=>'facebook-square','code'=>'f082'], | ||||
|             ['name'=>'camera-retro','code'=>'f083'], | ||||
|             ['name'=>'key','code'=>'f084'], | ||||
|             ['name'=>'cogs','code'=>'f085'], | ||||
|             ['name'=>'comments','code'=>'f086'], | ||||
|             ['name'=>'thumbs-o-up','code'=>'f087'], | ||||
|             ['name'=>'thumbs-o-down','code'=>'f088'], | ||||
|             ['name'=>'star-half','code'=>'f089'], | ||||
|             ['name'=>'heart-o','code'=>'f08a'], | ||||
|             ['name'=>'sign-out','code'=>'f08b'], | ||||
|             ['name'=>'linkedin-square','code'=>'f08c'], | ||||
|             ['name'=>'thumb-tack','code'=>'f08d'], | ||||
|             ['name'=>'external-link','code'=>'f08e'], | ||||
|             ['name'=>'sign-in','code'=>'f090'], | ||||
|             ['name'=>'trophy','code'=>'f091'], | ||||
|             ['name'=>'github-square','code'=>'f092'], | ||||
|             ['name'=>'upload','code'=>'f093'], | ||||
|             ['name'=>'lemon-o','code'=>'f094'], | ||||
|             ['name'=>'phone','code'=>'f095'], | ||||
|             ['name'=>'square-o','code'=>'f096'], | ||||
|             ['name'=>'bookmark-o','code'=>'f097'], | ||||
|             ['name'=>'phone-square','code'=>'f098'], | ||||
|             ['name'=>'twitter','code'=>'f099'], | ||||
|             ['name'=>'facebook','code'=>'f09a'], | ||||
|             ['name'=>'github','code'=>'f09b'], | ||||
|             ['name'=>'unlock','code'=>'f09c'], | ||||
|             ['name'=>'credit-card','code'=>'f09d'], | ||||
|             ['name'=>'rss','code'=>'f09e'], | ||||
|             ['name'=>'hdd-o','code'=>'f0a0'], | ||||
|             ['name'=>'bullhorn','code'=>'f0a1'], | ||||
|             ['name'=>'bell','code'=>'f0f3'], | ||||
|             ['name'=>'certificate','code'=>'f0a3'], | ||||
|             ['name'=>'hand-o-right','code'=>'f0a4'], | ||||
|             ['name'=>'hand-o-left','code'=>'f0a5'], | ||||
|             ['name'=>'hand-o-up','code'=>'f0a6'], | ||||
|             ['name'=>'hand-o-down','code'=>'f0a7'], | ||||
|             ['name'=>'arrow-circle-left','code'=>'f0a8'], | ||||
|             ['name'=>'arrow-circle-right','code'=>'f0a9'], | ||||
|             ['name'=>'arrow-circle-up','code'=>'f0aa'], | ||||
|             ['name'=>'arrow-circle-down','code'=>'f0ab'], | ||||
|             ['name'=>'globe','code'=>'f0ac'], | ||||
|             ['name'=>'wrench','code'=>'f0ad'], | ||||
|             ['name'=>'tasks','code'=>'f0ae'], | ||||
|             ['name'=>'filter','code'=>'f0b0'], | ||||
|             ['name'=>'briefcase','code'=>'f0b1'], | ||||
|             ['name'=>'arrows-alt','code'=>'f0b2'], | ||||
|             ['name'=>'users','code'=>'f0c0'], | ||||
|             ['name'=>'link','code'=>'f0c1'], | ||||
|             ['name'=>'cloud','code'=>'f0c2'], | ||||
|             ['name'=>'flask','code'=>'f0c3'], | ||||
|             ['name'=>'scissors','code'=>'f0c4'], | ||||
|             ['name'=>'files-o','code'=>'f0c5'], | ||||
|             ['name'=>'paperclip','code'=>'f0c6'], | ||||
|             ['name'=>'floppy-o','code'=>'f0c7'], | ||||
|             ['name'=>'square','code'=>'f0c8'], | ||||
|             ['name'=>'bars','code'=>'f0c9'], | ||||
|             ['name'=>'list-ul','code'=>'f0ca'], | ||||
|             ['name'=>'list-ol','code'=>'f0cb'], | ||||
|             ['name'=>'strikethrough','code'=>'f0cc'], | ||||
|             ['name'=>'underline','code'=>'f0cd'], | ||||
|             ['name'=>'table','code'=>'f0ce'], | ||||
|             ['name'=>'magic','code'=>'f0d0'], | ||||
|             ['name'=>'truck','code'=>'f0d1'], | ||||
|             ['name'=>'pinterest','code'=>'f0d2'], | ||||
|             ['name'=>'pinterest-square','code'=>'f0d3'], | ||||
|             ['name'=>'google-plus-square','code'=>'f0d4'], | ||||
|             ['name'=>'google-plus','code'=>'f0d5'], | ||||
|             ['name'=>'money','code'=>'f0d6'], | ||||
|             ['name'=>'caret-down','code'=>'f0d7'], | ||||
|             ['name'=>'caret-up','code'=>'f0d8'], | ||||
|             ['name'=>'caret-left','code'=>'f0d9'], | ||||
|             ['name'=>'caret-right','code'=>'f0da'], | ||||
|             ['name'=>'columns','code'=>'f0db'], | ||||
|             ['name'=>'sort','code'=>'f0dc'], | ||||
|             ['name'=>'sort-desc','code'=>'f0dd'], | ||||
|             ['name'=>'sort-asc','code'=>'f0de'], | ||||
|             ['name'=>'envelope','code'=>'f0e0'], | ||||
|             ['name'=>'linkedin','code'=>'f0e1'], | ||||
|             ['name'=>'undo','code'=>'f0e2'], | ||||
|             ['name'=>'gavel','code'=>'f0e3'], | ||||
|             ['name'=>'tachometer','code'=>'f0e4'], | ||||
|             ['name'=>'comment-o','code'=>'f0e5'], | ||||
|             ['name'=>'comments-o','code'=>'f0e6'], | ||||
|             ['name'=>'bolt','code'=>'f0e7'], | ||||
|             ['name'=>'sitemap','code'=>'f0e8'], | ||||
|             ['name'=>'umbrella','code'=>'f0e9'], | ||||
|             ['name'=>'clipboard','code'=>'f0ea'], | ||||
|             ['name'=>'lightbulb-o','code'=>'f0eb'], | ||||
|             ['name'=>'exchange','code'=>'f0ec'], | ||||
|             ['name'=>'cloud-download','code'=>'f0ed'], | ||||
|             ['name'=>'cloud-upload','code'=>'f0ee'], | ||||
|             ['name'=>'user-md','code'=>'f0f0'], | ||||
|             ['name'=>'stethoscope','code'=>'f0f1'], | ||||
|             ['name'=>'suitcase','code'=>'f0f2'], | ||||
|             ['name'=>'bell-o','code'=>'f0a2'], | ||||
|             ['name'=>'coffee','code'=>'f0f4'], | ||||
|             ['name'=>'cutlery','code'=>'f0f5'], | ||||
|             ['name'=>'file-text-o','code'=>'f0f6'], | ||||
|             ['name'=>'building-o','code'=>'f0f7'], | ||||
|             ['name'=>'hospital-o','code'=>'f0f8'], | ||||
|             ['name'=>'ambulance','code'=>'f0f9'], | ||||
|             ['name'=>'medkit','code'=>'f0fa'], | ||||
|             ['name'=>'fighter-jet','code'=>'f0fb'], | ||||
|             ['name'=>'beer','code'=>'f0fc'], | ||||
|             ['name'=>'h-square','code'=>'f0fd'], | ||||
|             ['name'=>'plus-square','code'=>'f0fe'], | ||||
|             ['name'=>'angle-double-left','code'=>'f100'], | ||||
|             ['name'=>'angle-double-right','code'=>'f101'], | ||||
|             ['name'=>'angle-double-up','code'=>'f102'], | ||||
|             ['name'=>'angle-double-down','code'=>'f103'], | ||||
|             ['name'=>'angle-left','code'=>'f104'], | ||||
|             ['name'=>'angle-right','code'=>'f105'], | ||||
|             ['name'=>'angle-up','code'=>'f106'], | ||||
|             ['name'=>'angle-down','code'=>'f107'], | ||||
|             ['name'=>'desktop','code'=>'f108'], | ||||
|             ['name'=>'laptop','code'=>'f109'], | ||||
|             ['name'=>'tablet','code'=>'f10a'], | ||||
|             ['name'=>'mobile','code'=>'f10b'], | ||||
|             ['name'=>'circle-o','code'=>'f10c'], | ||||
|             ['name'=>'quote-left','code'=>'f10d'], | ||||
|             ['name'=>'quote-right','code'=>'f10e'], | ||||
|             ['name'=>'spinner','code'=>'f110'], | ||||
|             ['name'=>'circle','code'=>'f111'], | ||||
|             ['name'=>'reply','code'=>'f112'], | ||||
|             ['name'=>'github-alt','code'=>'f113'], | ||||
|             ['name'=>'folder-o','code'=>'f114'], | ||||
|             ['name'=>'folder-open-o','code'=>'f115'], | ||||
|             ['name'=>'smile-o','code'=>'f118'], | ||||
|             ['name'=>'frown-o','code'=>'f119'], | ||||
|             ['name'=>'meh-o','code'=>'f11a'], | ||||
|             ['name'=>'gamepad','code'=>'f11b'], | ||||
|             ['name'=>'keyboard-o','code'=>'f11c'], | ||||
|             ['name'=>'flag-o','code'=>'f11d'], | ||||
|             ['name'=>'flag-checkered','code'=>'f11e'], | ||||
|             ['name'=>'terminal','code'=>'f120'], | ||||
|             ['name'=>'code','code'=>'f121'], | ||||
|             ['name'=>'reply-all','code'=>'f122'], | ||||
|             ['name'=>'star-half-o','code'=>'f123'], | ||||
|             ['name'=>'location-arrow','code'=>'f124'], | ||||
|             ['name'=>'crop','code'=>'f125'], | ||||
|             ['name'=>'code-fork','code'=>'f126'], | ||||
|             ['name'=>'chain-broken','code'=>'f127'], | ||||
|             ['name'=>'question','code'=>'f128'], | ||||
|             ['name'=>'info','code'=>'f129'], | ||||
|             ['name'=>'exclamation','code'=>'f12a'], | ||||
|             ['name'=>'superscript','code'=>'f12b'], | ||||
|             ['name'=>'subscript','code'=>'f12c'], | ||||
|             ['name'=>'eraser','code'=>'f12d'], | ||||
|             ['name'=>'puzzle-piece','code'=>'f12e'], | ||||
|             ['name'=>'microphone','code'=>'f130'], | ||||
|             ['name'=>'microphone-slash','code'=>'f131'], | ||||
|             ['name'=>'shield','code'=>'f132'], | ||||
|             ['name'=>'calendar-o','code'=>'f133'], | ||||
|             ['name'=>'fire-extinguisher','code'=>'f134'], | ||||
|             ['name'=>'rocket','code'=>'f135'], | ||||
|             ['name'=>'maxcdn','code'=>'f136'], | ||||
|             ['name'=>'chevron-circle-left','code'=>'f137'], | ||||
|             ['name'=>'chevron-circle-right','code'=>'f138'], | ||||
|             ['name'=>'chevron-circle-up','code'=>'f139'], | ||||
|             ['name'=>'chevron-circle-down','code'=>'f13a'], | ||||
|             ['name'=>'html5','code'=>'f13b'], | ||||
|             ['name'=>'css3','code'=>'f13c'], | ||||
|             ['name'=>'anchor','code'=>'f13d'], | ||||
|             ['name'=>'unlock-alt','code'=>'f13e'], | ||||
|             ['name'=>'bullseye','code'=>'f140'], | ||||
|             ['name'=>'ellipsis-h','code'=>'f141'], | ||||
|             ['name'=>'ellipsis-v','code'=>'f142'], | ||||
|             ['name'=>'rss-square','code'=>'f143'], | ||||
|             ['name'=>'play-circle','code'=>'f144'], | ||||
|             ['name'=>'ticket','code'=>'f145'], | ||||
|             ['name'=>'minus-square','code'=>'f146'], | ||||
|             ['name'=>'minus-square-o','code'=>'f147'], | ||||
|             ['name'=>'level-up','code'=>'f148'], | ||||
|             ['name'=>'level-down','code'=>'f149'], | ||||
|             ['name'=>'check-square','code'=>'f14a'], | ||||
|             ['name'=>'pencil-square','code'=>'f14b'], | ||||
|             ['name'=>'external-link-square','code'=>'f14c'], | ||||
|             ['name'=>'share-square','code'=>'f14d'], | ||||
|             ['name'=>'compass','code'=>'f14e'], | ||||
|             ['name'=>'caret-square-o-down','code'=>'f150'], | ||||
|             ['name'=>'caret-square-o-up','code'=>'f151'], | ||||
|             ['name'=>'caret-square-o-right','code'=>'f152'], | ||||
|             ['name'=>'eur','code'=>'f153'], | ||||
|             ['name'=>'gbp','code'=>'f154'], | ||||
|             ['name'=>'usd','code'=>'f155'], | ||||
|             ['name'=>'inr','code'=>'f156'], | ||||
|             ['name'=>'jpy','code'=>'f157'], | ||||
|             ['name'=>'rub','code'=>'f158'], | ||||
|             ['name'=>'krw','code'=>'f159'], | ||||
|             ['name'=>'btc','code'=>'f15a'], | ||||
|             ['name'=>'file','code'=>'f15b'], | ||||
|             ['name'=>'file-text','code'=>'f15c'], | ||||
|             ['name'=>'sort-alpha-asc','code'=>'f15d'], | ||||
|             ['name'=>'sort-alpha-desc','code'=>'f15e'], | ||||
|             ['name'=>'sort-amount-asc','code'=>'f160'], | ||||
|             ['name'=>'sort-amount-desc','code'=>'f161'], | ||||
|             ['name'=>'sort-numeric-asc','code'=>'f162'], | ||||
|             ['name'=>'sort-numeric-desc','code'=>'f163'], | ||||
|             ['name'=>'thumbs-up','code'=>'f164'], | ||||
|             ['name'=>'thumbs-down','code'=>'f165'], | ||||
|             ['name'=>'youtube-square','code'=>'f166'], | ||||
|             ['name'=>'youtube','code'=>'f167'], | ||||
|             ['name'=>'xing','code'=>'f168'], | ||||
|             ['name'=>'xing-square','code'=>'f169'], | ||||
|             ['name'=>'youtube-play','code'=>'f16a'], | ||||
|             ['name'=>'dropbox','code'=>'f16b'], | ||||
|             ['name'=>'stack-overflow','code'=>'f16c'], | ||||
|             ['name'=>'instagram','code'=>'f16d'], | ||||
|             ['name'=>'flickr','code'=>'f16e'], | ||||
|             ['name'=>'adn','code'=>'f170'], | ||||
|             ['name'=>'bitbucket','code'=>'f171'], | ||||
|             ['name'=>'bitbucket-square','code'=>'f172'], | ||||
|             ['name'=>'tumblr','code'=>'f173'], | ||||
|             ['name'=>'tumblr-square','code'=>'f174'], | ||||
|             ['name'=>'long-arrow-down','code'=>'f175'], | ||||
|             ['name'=>'long-arrow-up','code'=>'f176'], | ||||
|             ['name'=>'long-arrow-left','code'=>'f177'], | ||||
|             ['name'=>'long-arrow-right','code'=>'f178'], | ||||
|             ['name'=>'apple','code'=>'f179'], | ||||
|             ['name'=>'windows','code'=>'f17a'], | ||||
|             ['name'=>'android','code'=>'f17b'], | ||||
|             ['name'=>'linux','code'=>'f17c'], | ||||
|             ['name'=>'dribbble','code'=>'f17d'], | ||||
|             ['name'=>'skype','code'=>'f17e'], | ||||
|             ['name'=>'foursquare','code'=>'f180'], | ||||
|             ['name'=>'trello','code'=>'f181'], | ||||
|             ['name'=>'female','code'=>'f182'], | ||||
|             ['name'=>'male','code'=>'f183'], | ||||
|             ['name'=>'gratipay','code'=>'f184'], | ||||
|             ['name'=>'sun-o','code'=>'f185'], | ||||
|             ['name'=>'moon-o','code'=>'f186'], | ||||
|             ['name'=>'archive','code'=>'f187'], | ||||
|             ['name'=>'bug','code'=>'f188'], | ||||
|             ['name'=>'vk','code'=>'f189'], | ||||
|             ['name'=>'weibo','code'=>'f18a'], | ||||
|             ['name'=>'renren','code'=>'f18b'], | ||||
|             ['name'=>'pagelines','code'=>'f18c'], | ||||
|             ['name'=>'stack-exchange','code'=>'f18d'], | ||||
|             ['name'=>'arrow-circle-o-right','code'=>'f18e'], | ||||
|             ['name'=>'arrow-circle-o-left','code'=>'f190'], | ||||
|             ['name'=>'caret-square-o-left','code'=>'f191'], | ||||
|             ['name'=>'dot-circle-o','code'=>'f192'], | ||||
|             ['name'=>'wheelchair','code'=>'f193'], | ||||
|             ['name'=>'vimeo-square','code'=>'f194'], | ||||
|             ['name'=>'try','code'=>'f195'], | ||||
|             ['name'=>'plus-square-o','code'=>'f196'], | ||||
|             ['name'=>'space-shuttle','code'=>'f197'], | ||||
|             ['name'=>'slack','code'=>'f198'], | ||||
|             ['name'=>'envelope-square','code'=>'f199'], | ||||
|             ['name'=>'wordpress','code'=>'f19a'], | ||||
|             ['name'=>'openid','code'=>'f19b'], | ||||
|             ['name'=>'university','code'=>'f19c'], | ||||
|             ['name'=>'graduation-cap','code'=>'f19d'], | ||||
|             ['name'=>'yahoo','code'=>'f19e'], | ||||
|             ['name'=>'google','code'=>'f1a0'], | ||||
|             ['name'=>'reddit','code'=>'f1a1'], | ||||
|             ['name'=>'reddit-square','code'=>'f1a2'], | ||||
|             ['name'=>'stumbleupon-circle','code'=>'f1a3'], | ||||
|             ['name'=>'stumbleupon','code'=>'f1a4'], | ||||
|             ['name'=>'delicious','code'=>'f1a5'], | ||||
|             ['name'=>'digg','code'=>'f1a6'], | ||||
|             ['name'=>'pied-piper','code'=>'f1a7'], | ||||
|             ['name'=>'pied-piper-alt','code'=>'f1a8'], | ||||
|             ['name'=>'drupal','code'=>'f1a9'], | ||||
|             ['name'=>'joomla','code'=>'f1aa'], | ||||
|             ['name'=>'language','code'=>'f1ab'], | ||||
|             ['name'=>'fax','code'=>'f1ac'], | ||||
|             ['name'=>'building','code'=>'f1ad'], | ||||
|             ['name'=>'child','code'=>'f1ae'], | ||||
|             ['name'=>'paw','code'=>'f1b0'], | ||||
|             ['name'=>'spoon','code'=>'f1b1'], | ||||
|             ['name'=>'cube','code'=>'f1b2'], | ||||
|             ['name'=>'cubes','code'=>'f1b3'], | ||||
|             ['name'=>'behance','code'=>'f1b4'], | ||||
|             ['name'=>'behance-square','code'=>'f1b5'], | ||||
|             ['name'=>'steam','code'=>'f1b6'], | ||||
|             ['name'=>'steam-square','code'=>'f1b7'], | ||||
|             ['name'=>'recycle','code'=>'f1b8'], | ||||
|             ['name'=>'car','code'=>'f1b9'], | ||||
|             ['name'=>'taxi','code'=>'f1ba'], | ||||
|             ['name'=>'tree','code'=>'f1bb'], | ||||
|             ['name'=>'spotify','code'=>'f1bc'], | ||||
|             ['name'=>'deviantart','code'=>'f1bd'], | ||||
|             ['name'=>'soundcloud','code'=>'f1be'], | ||||
|             ['name'=>'database','code'=>'f1c0'], | ||||
|             ['name'=>'file-pdf-o','code'=>'f1c1'], | ||||
|             ['name'=>'file-word-o','code'=>'f1c2'], | ||||
|             ['name'=>'file-excel-o','code'=>'f1c3'], | ||||
|             ['name'=>'file-powerpoint-o','code'=>'f1c4'], | ||||
|             ['name'=>'file-image-o','code'=>'f1c5'], | ||||
|             ['name'=>'file-archive-o','code'=>'f1c6'], | ||||
|             ['name'=>'file-audio-o','code'=>'f1c7'], | ||||
|             ['name'=>'file-video-o','code'=>'f1c8'], | ||||
|             ['name'=>'file-code-o','code'=>'f1c9'], | ||||
|             ['name'=>'vine','code'=>'f1ca'], | ||||
|             ['name'=>'codepen','code'=>'f1cb'], | ||||
|             ['name'=>'jsfiddle','code'=>'f1cc'], | ||||
|             ['name'=>'life-ring','code'=>'f1cd'], | ||||
|             ['name'=>'circle-o-notch','code'=>'f1ce'], | ||||
|             ['name'=>'rebel','code'=>'f1d0'], | ||||
|             ['name'=>'empire','code'=>'f1d1'], | ||||
|             ['name'=>'git-square','code'=>'f1d2'], | ||||
|             ['name'=>'git','code'=>'f1d3'], | ||||
|             ['name'=>'hacker-news','code'=>'f1d4'], | ||||
|             ['name'=>'tencent-weibo','code'=>'f1d5'], | ||||
|             ['name'=>'qq','code'=>'f1d6'], | ||||
|             ['name'=>'weixin','code'=>'f1d7'], | ||||
|             ['name'=>'paper-plane','code'=>'f1d8'], | ||||
|             ['name'=>'paper-plane-o','code'=>'f1d9'], | ||||
|             ['name'=>'history','code'=>'f1da'], | ||||
|             ['name'=>'circle-thin','code'=>'f1db'], | ||||
|             ['name'=>'header','code'=>'f1dc'], | ||||
|             ['name'=>'paragraph','code'=>'f1dd'], | ||||
|             ['name'=>'sliders','code'=>'f1de'], | ||||
|             ['name'=>'share-alt','code'=>'f1e0'], | ||||
|             ['name'=>'share-alt-square','code'=>'f1e1'], | ||||
|             ['name'=>'bomb','code'=>'f1e2'], | ||||
|             ['name'=>'futbol-o','code'=>'f1e3'], | ||||
|             ['name'=>'tty','code'=>'f1e4'], | ||||
|             ['name'=>'binoculars','code'=>'f1e5'], | ||||
|             ['name'=>'plug','code'=>'f1e6'], | ||||
|             ['name'=>'slideshare','code'=>'f1e7'], | ||||
|             ['name'=>'twitch','code'=>'f1e8'], | ||||
|             ['name'=>'yelp','code'=>'f1e9'], | ||||
|             ['name'=>'newspaper-o','code'=>'f1ea'], | ||||
|             ['name'=>'wifi','code'=>'f1eb'], | ||||
|             ['name'=>'calculator','code'=>'f1ec'], | ||||
|             ['name'=>'paypal','code'=>'f1ed'], | ||||
|             ['name'=>'google-wallet','code'=>'f1ee'], | ||||
|             ['name'=>'cc-visa','code'=>'f1f0'], | ||||
|             ['name'=>'cc-mastercard','code'=>'f1f1'], | ||||
|             ['name'=>'cc-discover','code'=>'f1f2'], | ||||
|             ['name'=>'cc-amex','code'=>'f1f3'], | ||||
|             ['name'=>'cc-paypal','code'=>'f1f4'], | ||||
|             ['name'=>'cc-stripe','code'=>'f1f5'], | ||||
|             ['name'=>'bell-slash','code'=>'f1f6'], | ||||
|             ['name'=>'bell-slash-o','code'=>'f1f7'], | ||||
|             ['name'=>'trash','code'=>'f1f8'], | ||||
|             ['name'=>'copyright','code'=>'f1f9'], | ||||
|             ['name'=>'at','code'=>'f1fa'], | ||||
|             ['name'=>'eyedropper','code'=>'f1fb'], | ||||
|             ['name'=>'paint-brush','code'=>'f1fc'], | ||||
|             ['name'=>'birthday-cake','code'=>'f1fd'], | ||||
|             ['name'=>'area-chart','code'=>'f1fe'], | ||||
|             ['name'=>'pie-chart','code'=>'f200'], | ||||
|             ['name'=>'line-chart','code'=>'f201'], | ||||
|             ['name'=>'lastfm','code'=>'f202'], | ||||
|             ['name'=>'lastfm-square','code'=>'f203'], | ||||
|             ['name'=>'toggle-off','code'=>'f204'], | ||||
|             ['name'=>'toggle-on','code'=>'f205'], | ||||
|             ['name'=>'bicycle','code'=>'f206'], | ||||
|             ['name'=>'bus','code'=>'f207'], | ||||
|             ['name'=>'ioxhost','code'=>'f208'], | ||||
|             ['name'=>'angellist','code'=>'f209'], | ||||
|             ['name'=>'cc','code'=>'f20a'], | ||||
|             ['name'=>'ils','code'=>'f20b'], | ||||
|             ['name'=>'meanpath','code'=>'f20c'], | ||||
|             ['name'=>'buysellads','code'=>'f20d'], | ||||
|             ['name'=>'connectdevelop','code'=>'f20e'], | ||||
|             ['name'=>'dashcube','code'=>'f210'], | ||||
|             ['name'=>'forumbee','code'=>'f211'], | ||||
|             ['name'=>'leanpub','code'=>'f212'], | ||||
|             ['name'=>'sellsy','code'=>'f213'], | ||||
|             ['name'=>'shirtsinbulk','code'=>'f214'], | ||||
|             ['name'=>'simplybuilt','code'=>'f215'], | ||||
|             ['name'=>'skyatlas','code'=>'f216'], | ||||
|             ['name'=>'cart-plus','code'=>'f217'], | ||||
|             ['name'=>'cart-arrow-down','code'=>'f218'], | ||||
|             ['name'=>'diamond','code'=>'f219'], | ||||
|             ['name'=>'ship','code'=>'f21a'], | ||||
|             ['name'=>'user-secret','code'=>'f21b'], | ||||
|             ['name'=>'motorcycle','code'=>'f21c'], | ||||
|             ['name'=>'street-view','code'=>'f21d'], | ||||
|             ['name'=>'heartbeat','code'=>'f21e'], | ||||
|             ['name'=>'venus','code'=>'f221'], | ||||
|             ['name'=>'mars','code'=>'f222'], | ||||
|             ['name'=>'mercury','code'=>'f223'], | ||||
|             ['name'=>'transgender','code'=>'f224'], | ||||
|             ['name'=>'transgender-alt','code'=>'f225'], | ||||
|             ['name'=>'venus-double','code'=>'f226'], | ||||
|             ['name'=>'mars-double','code'=>'f227'], | ||||
|             ['name'=>'venus-mars','code'=>'f228'], | ||||
|             ['name'=>'mars-stroke','code'=>'f229'], | ||||
|             ['name'=>'mars-stroke-v','code'=>'f22a'], | ||||
|             ['name'=>'mars-stroke-h','code'=>'f22b'], | ||||
|             ['name'=>'neuter','code'=>'f22c'], | ||||
|             ['name'=>'genderless','code'=>'f22d'], | ||||
|             ['name'=>'facebook-official','code'=>'f230'], | ||||
|             ['name'=>'pinterest-p','code'=>'f231'], | ||||
|             ['name'=>'whatsapp','code'=>'f232'], | ||||
|             ['name'=>'server','code'=>'f233'], | ||||
|             ['name'=>'user-plus','code'=>'f234'], | ||||
|             ['name'=>'user-times','code'=>'f235'], | ||||
|             ['name'=>'bed','code'=>'f236'], | ||||
|             ['name'=>'viacoin','code'=>'f237'], | ||||
|             ['name'=>'train','code'=>'f238'], | ||||
|             ['name'=>'subway','code'=>'f239'], | ||||
|             ['name'=>'medium','code'=>'f23a'], | ||||
|             ['name'=>'y-combinator','code'=>'f23b'], | ||||
|             ['name'=>'optin-monster','code'=>'f23c'], | ||||
|             ['name'=>'opencart','code'=>'f23d'], | ||||
|             ['name'=>'expeditedssl','code'=>'f23e'], | ||||
|             ['name'=>'battery-full','code'=>'f240'], | ||||
|             ['name'=>'battery-three-quarters','code'=>'f241'], | ||||
|             ['name'=>'battery-half','code'=>'f242'], | ||||
|             ['name'=>'battery-quarter','code'=>'f243'], | ||||
|             ['name'=>'battery-empty','code'=>'f244'], | ||||
|             ['name'=>'mouse-pointer','code'=>'f245'], | ||||
|             ['name'=>'i-cursor','code'=>'f246'], | ||||
|             ['name'=>'object-group','code'=>'f247'], | ||||
|             ['name'=>'object-ungroup','code'=>'f248'], | ||||
|             ['name'=>'sticky-note','code'=>'f249'], | ||||
|             ['name'=>'sticky-note-o','code'=>'f24a'], | ||||
|             ['name'=>'cc-jcb','code'=>'f24b'], | ||||
|             ['name'=>'cc-diners-club','code'=>'f24c'], | ||||
|             ['name'=>'clone','code'=>'f24d'], | ||||
|             ['name'=>'balance-scale','code'=>'f24e'], | ||||
|             ['name'=>'hourglass-o','code'=>'f250'], | ||||
|             ['name'=>'hourglass-start','code'=>'f251'], | ||||
|             ['name'=>'hourglass-half','code'=>'f252'], | ||||
|             ['name'=>'hourglass-end','code'=>'f253'], | ||||
|             ['name'=>'hourglass','code'=>'f254'], | ||||
|             ['name'=>'hand-rock-o','code'=>'f255'], | ||||
|             ['name'=>'hand-paper-o','code'=>'f256'], | ||||
|             ['name'=>'hand-scissors-o','code'=>'f257'], | ||||
|             ['name'=>'hand-lizard-o','code'=>'f258'], | ||||
|             ['name'=>'hand-spock-o','code'=>'f259'], | ||||
|             ['name'=>'hand-pointer-o','code'=>'f25a'], | ||||
|             ['name'=>'hand-peace-o','code'=>'f25b'], | ||||
|             ['name'=>'trademark','code'=>'f25c'], | ||||
|             ['name'=>'registered','code'=>'f25d'], | ||||
|             ['name'=>'creative-commons','code'=>'f25e'], | ||||
|             ['name'=>'gg','code'=>'f260'], | ||||
|             ['name'=>'gg-circle','code'=>'f261'], | ||||
|             ['name'=>'tripadvisor','code'=>'f262'], | ||||
|             ['name'=>'odnoklassniki','code'=>'f263'], | ||||
|             ['name'=>'odnoklassniki-square','code'=>'f264'], | ||||
|             ['name'=>'get-pocket','code'=>'f265'], | ||||
|             ['name'=>'wikipedia-w','code'=>'f266'], | ||||
|             ['name'=>'safari','code'=>'f267'], | ||||
|             ['name'=>'chrome','code'=>'f268'], | ||||
|             ['name'=>'firefox','code'=>'f269'], | ||||
|             ['name'=>'opera','code'=>'f26a'], | ||||
|             ['name'=>'internet-explorer','code'=>'f26b'], | ||||
|             ['name'=>'television','code'=>'f26c'], | ||||
|             ['name'=>'contao','code'=>'f26d'], | ||||
|             ['name'=>'500px','code'=>'f26e'], | ||||
|             ['name'=>'amazon','code'=>'f270'], | ||||
|             ['name'=>'calendar-plus-o','code'=>'f271'], | ||||
|             ['name'=>'calendar-minus-o','code'=>'f272'], | ||||
|             ['name'=>'calendar-times-o','code'=>'f273'], | ||||
|             ['name'=>'calendar-check-o','code'=>'f274'], | ||||
|             ['name'=>'industry','code'=>'f275'], | ||||
|             ['name'=>'map-pin','code'=>'f276'], | ||||
|             ['name'=>'map-signs','code'=>'f277'], | ||||
|             ['name'=>'map-o','code'=>'f278'], | ||||
|             ['name'=>'map','code'=>'f279'], | ||||
|             ['name'=>'commenting','code'=>'f27a'], | ||||
|             ['name'=>'commenting-o','code'=>'f27b'], | ||||
|             ['name'=>'houzz','code'=>'f27c'], | ||||
|             ['name'=>'vimeo','code'=>'f27d'], | ||||
|             ['name'=>'black-tie','code'=>'f27e'], | ||||
|             ['name'=>'fonticons','code'=>'f280'], | ||||
|         ]; | ||||
| 
 | ||||
|         foreach ($icons as $icon) { | ||||
|             $data[$icon['name']] = '&#x' . $icon['code'] . ' ' . ucwords(str_replace('-', ' ', $icon['name'])); | ||||
|         } | ||||
| 
 | ||||
|         ksort($data); | ||||
| 
 | ||||
|         return $data; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										173
									
								
								app/Http/Controllers/ProposalTemplateController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								app/Http/Controllers/ProposalTemplateController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,173 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Controllers; | ||||
| 
 | ||||
| use App\Http\Requests\CreateProposalTemplateRequest; | ||||
| use App\Http\Requests\ProposalTemplateRequest; | ||||
| use App\Http\Requests\UpdateProposalTemplateRequest; | ||||
| use App\Models\Invoice; | ||||
| use App\Models\ProposalTemplate; | ||||
| use App\Ninja\Datatables\ProposalTemplateDatatable; | ||||
| use App\Ninja\Repositories\ProposalTemplateRepository; | ||||
| use App\Services\ProposalTemplateService; | ||||
| use Auth; | ||||
| use Input; | ||||
| use Session; | ||||
| use View; | ||||
| 
 | ||||
| class ProposalTemplateController extends BaseController | ||||
| { | ||||
|     protected $proposalTemplateRepo; | ||||
|     protected $proposalTemplateService; | ||||
|     protected $entityType = ENTITY_PROPOSAL_TEMPLATE; | ||||
| 
 | ||||
|     public function __construct(ProposalTemplateRepository $proposalTemplateRepo, ProposalTemplateService $proposalTemplateService) | ||||
|     { | ||||
|         $this->proposalTemplateRepo = $proposalTemplateRepo; | ||||
|         $this->proposalTemplateService = $proposalTemplateService; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Display a listing of the resource. | ||||
|      * | ||||
|      * @return Response | ||||
|      */ | ||||
|     public function index() | ||||
|     { | ||||
|         return View::make('list_wrapper', [ | ||||
|             'entityType' => ENTITY_PROPOSAL_TEMPLATE, | ||||
|             'datatable' => new ProposalTemplateDatatable(), | ||||
|             'title' => trans('texts.proposal_templates'), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public function getDatatable($expensePublicId = null) | ||||
|     { | ||||
|         $search = Input::get('sSearch'); | ||||
|         $userId = Auth::user()->filterId(); | ||||
| 
 | ||||
|         return $this->proposalTemplateService->getDatatable($search, $userId); | ||||
|     } | ||||
| 
 | ||||
|     public function create(ProposalTemplateRequest $request) | ||||
|     { | ||||
|         $data = array_merge($this->getViewmodel(), [ | ||||
|             'template' => null, | ||||
|             'method' => 'POST', | ||||
|             'url' => 'proposals/templates', | ||||
|             'title' => trans('texts.new_proposal_template'), | ||||
|         ]); | ||||
| 
 | ||||
|         return View::make('proposals/templates/edit', $data); | ||||
|     } | ||||
| 
 | ||||
|     private function getViewmodel() | ||||
|     { | ||||
|         $customTemplates = ProposalTemplate::scope()->orderBy('name')->get(); | ||||
|         $defaultTemplates = ProposalTemplate::whereNull('account_id')->orderBy('public_id')->get(); | ||||
| 
 | ||||
|         $options = []; | ||||
|         $customLabel = trans('texts.custom'); | ||||
|         $defaultLabel = trans('texts.default'); | ||||
| 
 | ||||
|         foreach ($customTemplates as $template) { | ||||
|             if (! isset($options[$customLabel])) { | ||||
|                 $options[$customLabel] = []; | ||||
|             } | ||||
|             $options[trans('texts.custom')][$template->public_id] = $template->name; | ||||
|         } | ||||
|         foreach ($defaultTemplates as $template) { | ||||
|             if (! isset($options[$defaultLabel])) { | ||||
|                 $options[$defaultLabel] = []; | ||||
|             } | ||||
|             $options[trans('texts.default')][$template->public_id] = $template->name; | ||||
|         } | ||||
| 
 | ||||
|         $data = [ | ||||
|             'account' => auth()->user()->account, | ||||
|             'customTemplates' => $customTemplates, | ||||
|             'defaultTemplates' => $defaultTemplates, | ||||
|             'templateOptions' => $options, | ||||
|         ]; | ||||
| 
 | ||||
|         return $data; | ||||
|     } | ||||
| 
 | ||||
|     public function show($publicId) | ||||
|     { | ||||
|         Session::reflash(); | ||||
| 
 | ||||
|         return redirect("proposals/templates/$publicId/edit"); | ||||
|     } | ||||
| 
 | ||||
|     public function edit(ProposalTemplateRequest $request, $publicId = false, $clone = false) | ||||
|     { | ||||
|         $template = $request->entity(); | ||||
| 
 | ||||
|         if ($clone) { | ||||
|             $template->id = null; | ||||
|             $template->public_id = null; | ||||
|             $template->name = ''; | ||||
|             $template->private_notes = ''; | ||||
|             $method = 'POST'; | ||||
|             $url = 'proposals/templates'; | ||||
|         } else { | ||||
|             $method = 'PUT'; | ||||
|             $url = 'proposals/templates/' . $template->public_id; | ||||
|         } | ||||
| 
 | ||||
|         $data = array_merge($this->getViewmodel(), [ | ||||
|             'template' => $template, | ||||
|             'entity' => $clone ? false : $template, | ||||
|             'method' => $method, | ||||
|             'url' => $url, | ||||
|             'title' => trans('texts.edit_proposal_template'), | ||||
|         ]); | ||||
| 
 | ||||
|         return View::make('proposals/templates/edit', $data); | ||||
|     } | ||||
| 
 | ||||
|     public function cloneProposal(ProposalTemplateRequest $request, $publicId) | ||||
|     { | ||||
|         return self::edit($request, $publicId, true); | ||||
|     } | ||||
| 
 | ||||
|     public function store(CreateProposalTemplateRequest $request) | ||||
|     { | ||||
|         $proposalTemplate = $this->proposalTemplateService->save($request->input()); | ||||
| 
 | ||||
|         Session::flash('message', trans('texts.created_proposal_template')); | ||||
| 
 | ||||
|         return redirect()->to($proposalTemplate->getRoute()); | ||||
|     } | ||||
| 
 | ||||
|     public function update(UpdateProposalTemplateRequest $request) | ||||
|     { | ||||
|         $proposalTemplate = $this->proposalTemplateService->save($request->input(), $request->entity()); | ||||
| 
 | ||||
|         Session::flash('message', trans('texts.updated_proposal_template')); | ||||
| 
 | ||||
|         $action = Input::get('action'); | ||||
|         if (in_array($action, ['archive', 'delete', 'restore'])) { | ||||
|             return self::bulk(); | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->to($proposalTemplate->getRoute()); | ||||
|     } | ||||
| 
 | ||||
|     public function bulk() | ||||
|     { | ||||
|         $action = Input::get('action'); | ||||
|         $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids'); | ||||
| 
 | ||||
|         $count = $this->proposalTemplateService->bulk($ids, $action); | ||||
| 
 | ||||
|         if ($count > 0) { | ||||
|             $field = $count == 1 ? "{$action}d_proposal_template" : "{$action}d_proposal_templates"; | ||||
|             $message = trans("texts.$field", ['count' => $count]); | ||||
|             Session::flash('message', $message); | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->to('/proposals/templates'); | ||||
|     } | ||||
| } | ||||
| @ -97,7 +97,7 @@ class QuoteController extends BaseController | ||||
| 
 | ||||
|         return [ | ||||
|           'entityType' => ENTITY_QUOTE, | ||||
|           'account' => $account, | ||||
|           'account' => Auth::user()->account->load('country'), | ||||
|           'products' => Product::scope()->orderBy('product_key')->get(), | ||||
|           'taxRateOptions' => $account->present()->taxRateOptions, | ||||
|           'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(), | ||||
| @ -148,6 +148,11 @@ class QuoteController extends BaseController | ||||
|     { | ||||
|         $invitation = Invitation::with('invoice.invoice_items', 'invoice.invitations')->where('invitation_key', '=', $invitationKey)->firstOrFail(); | ||||
|         $invoice = $invitation->invoice; | ||||
|         $account = $invoice->account; | ||||
| 
 | ||||
|         if ($account->requiresAuthorization($invoice) && ! session('authorized:' . $invitation->invitation_key)) { | ||||
|             return redirect()->to('view/' . $invitation->invitation_key); | ||||
|         } | ||||
| 
 | ||||
|         if ($invoice->due_date) { | ||||
|             $carbonDueDate = \Carbon::parse($invoice->due_date); | ||||
|  | ||||
| @ -75,6 +75,7 @@ class ReportController extends BaseController | ||||
|             'activity', | ||||
|             'aging', | ||||
|             'client', | ||||
|             'credit', | ||||
|             'document', | ||||
|             'expense', | ||||
|             'invoice', | ||||
|  | ||||
| @ -134,6 +134,7 @@ class SubscriptionController extends BaseController | ||||
|                 $subscription = Subscription::scope($subscriptionPublicId)->firstOrFail(); | ||||
|             } else { | ||||
|                 $subscription = Subscription::createNew(); | ||||
|                 $subscriptionPublicId = $subscription->public_id; | ||||
|             } | ||||
| 
 | ||||
|             $validator = Validator::make(Input::all(), $rules); | ||||
| @ -154,6 +155,14 @@ class SubscriptionController extends BaseController | ||||
|             Session::flash('message', $message); | ||||
|         } | ||||
| 
 | ||||
|         return Redirect::to('settings/' . ACCOUNT_API_TOKENS); | ||||
|         return redirect('/settings/api_tokens'); | ||||
| 
 | ||||
|         /* | ||||
|         if ($subscriptionPublicId) { | ||||
|             return Redirect::to('subscriptions/' . $subscriptionPublicId . '/edit'); | ||||
|         } else { | ||||
|             return redirect('/settings/api_tokens'); | ||||
|         } | ||||
|         */ | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -55,8 +55,16 @@ class TaskKanbanController extends BaseController | ||||
|                 $task->task_status_sort_order = $i++; | ||||
|                 $task->save(); | ||||
|             } | ||||
|         // otherwise, check that the tasks orders are correct
 | ||||
|         // otherwise, check that the orders are correct
 | ||||
|         } else { | ||||
|             for ($i=0; $i<$statuses->count(); $i++) { | ||||
|                 $status = $statuses[$i]; | ||||
|                 if ($status->sort_order != $i) { | ||||
|                     $status->sort_order = $i; | ||||
|                     $status->save(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             $firstStatus = $statuses[0]; | ||||
|             $counts = []; | ||||
|             foreach ($tasks as $task) { | ||||
|  | ||||
| @ -118,7 +118,7 @@ class UserController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         if (! Auth::user()->confirmed) { | ||||
|             Session::flash('error', trans('texts.confirmation_required')); | ||||
|             Session::flash('error', trans('texts.confirmation_required', ['link' => link_to('/resend_confirmation', trans('texts.click_here'))])); | ||||
| 
 | ||||
|             return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT); | ||||
|         } | ||||
|  | ||||
| @ -100,8 +100,8 @@ class ApiCheck | ||||
|                 return Response::json("Please wait {$wait} second(s)", 403, $headers); | ||||
|             } | ||||
| 
 | ||||
|             Cache::put("hour_throttle:{$key}", $new_hour_throttle, 10); | ||||
|             Cache::put("last_api_request:{$key}", time(), 10); | ||||
|             Cache::put("hour_throttle:{$key}", $new_hour_throttle, 60); | ||||
|             Cache::put("last_api_request:{$key}", time(), 60); | ||||
|         } | ||||
| 
 | ||||
|         return $next($request); | ||||
|  | ||||
| @ -4,6 +4,7 @@ namespace App\Http\Middleware; | ||||
| 
 | ||||
| use App\Models\Contact; | ||||
| use App\Models\Invitation; | ||||
| use App\Models\ProposalInvitation; | ||||
| use Auth; | ||||
| use Closure; | ||||
| use Session; | ||||
| @ -25,13 +26,14 @@ class Authenticate | ||||
|     public function handle($request, Closure $next, $guard = 'user') | ||||
|     { | ||||
|         $authenticated = Auth::guard($guard)->check(); | ||||
|         $invitationKey = $request->invitation_key ?: $request->proposal_invitation_key; | ||||
| 
 | ||||
|         if ($guard == 'client') { | ||||
|             if (! empty($request->invitation_key)) { | ||||
|             if (! empty($request->invitation_key) || ! empty($request->proposal_invitation_key)) { | ||||
|                 $contact_key = session('contact_key'); | ||||
|                 if ($contact_key) { | ||||
|                     $contact = $this->getContact($contact_key); | ||||
|                     $invitation = $this->getInvitation($request->invitation_key); | ||||
|                     $invitation = $this->getInvitation($invitationKey, ! empty($request->proposal_invitation_key)); | ||||
| 
 | ||||
|                     if (! $invitation) { | ||||
|                         return response()->view('error', [ | ||||
| @ -59,7 +61,7 @@ class Authenticate | ||||
|             $contact = false; | ||||
|             if ($contact_key) { | ||||
|                 $contact = $this->getContact($contact_key); | ||||
|             } elseif ($invitation = $this->getInvitation($request->invitation_key)) { | ||||
|             } elseif ($invitation = $this->getInvitation($invitationKey, ! empty($request->proposal_invitation_key))) { | ||||
|                 $contact = $invitation->contact; | ||||
|                 Session::put('contact_key', $contact->contact_key); | ||||
|             } | ||||
| @ -89,6 +91,7 @@ class Authenticate | ||||
| 
 | ||||
|             if ($authenticated) { | ||||
|                 $request->merge(['contact' => $contact]); | ||||
|                 $account->loadLocalizationSettings($contact->client); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -108,7 +111,7 @@ class Authenticate | ||||
|      * | ||||
|      * @return \Illuminate\Database\Eloquent\Model|null|static | ||||
|      */ | ||||
|     protected function getInvitation($key) | ||||
|     protected function getInvitation($key, $isProposal = false) | ||||
|     { | ||||
|         if (! $key) { | ||||
|             return false; | ||||
| @ -118,7 +121,12 @@ class Authenticate | ||||
|         list($key) = explode('&', $key); | ||||
|         $key = substr($key, 0, RANDOM_KEY_LENGTH); | ||||
| 
 | ||||
|         $invitation = Invitation::withTrashed()->where('invitation_key', '=', $key)->first(); | ||||
|         if ($isProposal) { | ||||
|             $invitation = ProposalInvitation::withTrashed()->where('invitation_key', '=', $key)->first(); | ||||
|         } else { | ||||
|             $invitation = Invitation::withTrashed()->where('invitation_key', '=', $key)->first(); | ||||
|         } | ||||
| 
 | ||||
|         if ($invitation && ! $invitation->is_deleted) { | ||||
|             return $invitation; | ||||
|         } else { | ||||
|  | ||||
| @ -7,6 +7,7 @@ use Closure; | ||||
| use App\Models\LookupAccount; | ||||
| use App\Models\LookupContact; | ||||
| use App\Models\LookupInvitation; | ||||
| use App\Models\LookupProposalInvitation; | ||||
| use App\Models\LookupAccountToken; | ||||
| use App\Models\LookupUser; | ||||
| use Auth; | ||||
| @ -43,6 +44,8 @@ class DatabaseLookup | ||||
|         } elseif ($guard == 'contact') { | ||||
|             if ($key = request()->invitation_key) { | ||||
|                 LookupInvitation::setServerByField('invitation_key', $key); | ||||
|             } elseif ($key = request()->proposal_invitation_key) { | ||||
|                 LookupProposalInvitation::setServerByField('invitation_key', $key); | ||||
|             } elseif ($key = request()->contact_key ?: session('contact_key')) { | ||||
|                 LookupContact::setServerByField('contact_key', $key); | ||||
|             } elseif ($key = request()->account_key) { | ||||
|  | ||||
| @ -36,8 +36,13 @@ class StartupCheck | ||||
|         // Set up trusted X-Forwarded-Proto proxies
 | ||||
|         // TRUSTED_PROXIES accepts a comma delimited list of subnets
 | ||||
|         // ie, TRUSTED_PROXIES='10.0.0.0/8,172.16.0.0/12,192.168.0.0/16'
 | ||||
|         // set TRUSTED_PROXIES=* if you want to trust every proxy.
 | ||||
|         if (isset($_ENV['TRUSTED_PROXIES'])) { | ||||
|             $request->setTrustedProxies(array_map('trim', explode(',', env('TRUSTED_PROXIES')))); | ||||
|             if (env('TRUSTED_PROXIES') == '*') { | ||||
|                 $request->setTrustedProxies(['127.0.0.1', $request->server->get('REMOTE_ADDR')]); | ||||
|             } else{ | ||||
|                 $request->setTrustedProxies(array_map('trim', explode(',', env('TRUSTED_PROXIES')))); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Ensure all request are over HTTPS in production
 | ||||
| @ -218,7 +223,7 @@ class StartupCheck | ||||
| 
 | ||||
|         // Show message to IE 8 and before users
 | ||||
|         if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(?i)msie [2-8]/', $_SERVER['HTTP_USER_AGENT'])) { | ||||
|             Session::flash('error', trans('texts.old_browser', ['link' => OUTDATE_BROWSER_URL])); | ||||
|             Session::flash('error', trans('texts.old_browser', ['link' => link_to(OUTDATE_BROWSER_URL, trans('texts.newer_browser'), ['target' => '_blank'])])); | ||||
|         } | ||||
| 
 | ||||
|         $response = $next($request); | ||||
|  | ||||
							
								
								
									
										36
									
								
								app/Http/Requests/CreatePaymentTermAPIRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/Http/Requests/CreatePaymentTermAPIRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| use App\Models\Invoice; | ||||
| 
 | ||||
| class CreatePaymentTermAPIRequest extends Request | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
| 
 | ||||
|     public function authorize() | ||||
|     { | ||||
|         return $this->user()->can('create', ENTITY_PAYMENT_TERM); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation rules that apply to the request. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
| 
 | ||||
|     public function rules() | ||||
|     { | ||||
| 
 | ||||
|         $rules = [ | ||||
|             'num_days' => 'required|numeric|unique:payment_terms', | ||||
|         ]; | ||||
| 
 | ||||
| 
 | ||||
|         return $rules; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										28
									
								
								app/Http/Requests/CreateProposalCategoryRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/Http/Requests/CreateProposalCategoryRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class CreateProposalCategoryRequest extends ProposalCategoryRequest | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() | ||||
|     { | ||||
|         return $this->user()->can('create', ENTITY_PROPOSAL_CATEGORY); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation rules that apply to the request. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function rules() | ||||
|     { | ||||
|         return [ | ||||
|             'name' => sprintf('required|unique:proposal_categories,name,,id,account_id,%s', $this->user()->account_id), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										28
									
								
								app/Http/Requests/CreateProposalRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/Http/Requests/CreateProposalRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class CreateProposalRequest extends ProposalRequest | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() | ||||
|     { | ||||
|         return $this->user()->can('create', ENTITY_PROPOSAL); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation rules that apply to the request. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function rules() | ||||
|     { | ||||
|         return [ | ||||
|             'invoice_id' => 'required', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										28
									
								
								app/Http/Requests/CreateProposalSnippetRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/Http/Requests/CreateProposalSnippetRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class CreateProposalSnippetRequest extends ProposalSnippetRequest | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() | ||||
|     { | ||||
|         return $this->user()->can('create', ENTITY_PROPOSAL_SNIPPET); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation rules that apply to the request. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function rules() | ||||
|     { | ||||
|         return [ | ||||
|             'name' => sprintf('required|unique:proposal_snippets,name,,id,account_id,%s', $this->user()->account_id), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										28
									
								
								app/Http/Requests/CreateProposalTemplateRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/Http/Requests/CreateProposalTemplateRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class CreateProposalTemplateRequest extends ProposalTemplateRequest | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() | ||||
|     { | ||||
|         return $this->user()->can('create', ENTITY_PROPOSAL_TEMPLATE); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation rules that apply to the request. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function rules() | ||||
|     { | ||||
|         return [ | ||||
|             'name' => sprintf('required|unique:proposal_templates,name,,id,account_id,%s', $this->user()->account_id), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @ -35,6 +35,7 @@ class EntityRequest extends Request | ||||
|         if (! $publicId) { | ||||
|             $publicId = Input::get('public_id') ?: Input::get('id'); | ||||
|         } | ||||
| 
 | ||||
|         if (! $publicId) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
							
								
								
									
										8
									
								
								app/Http/Requests/PaymentTermRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/Http/Requests/PaymentTermRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class PaymentTermRequest extends EntityRequest | ||||
| { | ||||
|     protected $entityType = ENTITY_PAYMENT_TERM; | ||||
| } | ||||
							
								
								
									
										8
									
								
								app/Http/Requests/ProposalCategoryRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/Http/Requests/ProposalCategoryRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class ProposalCategoryRequest extends EntityRequest | ||||
| { | ||||
|     protected $entityType = ENTITY_PROPOSAL_CATEGORY; | ||||
| } | ||||
							
								
								
									
										8
									
								
								app/Http/Requests/ProposalRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/Http/Requests/ProposalRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class ProposalRequest extends EntityRequest | ||||
| { | ||||
|     protected $entityType = ENTITY_PROPOSAL; | ||||
| } | ||||
							
								
								
									
										34
									
								
								app/Http/Requests/ProposalSnippetRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								app/Http/Requests/ProposalSnippetRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| use App\Models\ProposalCategory; | ||||
| 
 | ||||
| class ProposalSnippetRequest extends EntityRequest | ||||
| { | ||||
|     protected $entityType = ENTITY_PROPOSAL_SNIPPET; | ||||
| 
 | ||||
|     public function sanitize() | ||||
|     { | ||||
|         $input = $this->all(); | ||||
| 
 | ||||
|         // check if we're creating a new proposal category
 | ||||
|         if ($this->proposal_category_id == '-1') { | ||||
|             $data = [ | ||||
|                 'name' => trim($this->proposal_category_name) | ||||
|             ]; | ||||
|             if (ProposalCategory::validate($data) === true) { | ||||
|                 $category = app('App\Ninja\Repositories\ProposalCategoryRepository')->save($data); | ||||
|                 $input['proposal_category_id'] = $category->id; | ||||
|             } else { | ||||
|                 $input['proposal_category_id'] = null; | ||||
|             } | ||||
|         } elseif ($this->proposal_category_id) { | ||||
|             $input['proposal_category_id'] = ProposalCategory::getPrivateId($this->proposal_category_id); | ||||
|         } | ||||
| 
 | ||||
|         $this->replace($input); | ||||
| 
 | ||||
|         return $this->all(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										8
									
								
								app/Http/Requests/ProposalTemplateRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/Http/Requests/ProposalTemplateRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class ProposalTemplateRequest extends EntityRequest | ||||
| { | ||||
|     protected $entityType = ENTITY_PROPOSAL_TEMPLATE; | ||||
| } | ||||
| @ -34,7 +34,7 @@ class UpdateInvoiceAPIRequest extends InvoiceRequest | ||||
|         $invoiceId = $this->entity()->id; | ||||
| 
 | ||||
|         $rules = [ | ||||
|             'invoice_items' => 'valid_invoice_items', | ||||
|             'invoice_items' => 'required|valid_invoice_items', | ||||
|             'invoice_number' => 'unique:invoices,invoice_number,' . $invoiceId . ',id,account_id,' . $this->user()->account_id, | ||||
|             'discount' => 'positive', | ||||
|             //'invoice_date' => 'date',
 | ||||
|  | ||||
| @ -31,7 +31,7 @@ class UpdateInvoiceRequest extends InvoiceRequest | ||||
| 
 | ||||
|         $rules = [ | ||||
|             'client' => 'required', | ||||
|             'invoice_items' => 'valid_invoice_items', | ||||
|             'invoice_items' => 'required|valid_invoice_items', | ||||
|             'invoice_number' => 'required|unique:invoices,invoice_number,' . $invoiceId . ',id,account_id,' . $this->user()->account_id, | ||||
|             'discount' => 'positive', | ||||
|             'invoice_date' => 'required', | ||||
|  | ||||
							
								
								
									
										17
									
								
								app/Http/Requests/UpdatePaymentTermRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/Http/Requests/UpdatePaymentTermRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class UpdatePaymentTermRequest extends EntityRequest | ||||
| { | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation rules that apply to the request. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function rules() | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										32
									
								
								app/Http/Requests/UpdateProposalCategoryRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/Http/Requests/UpdateProposalCategoryRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class UpdateProposalCategoryRequest extends ProposalCategoryRequest | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() | ||||
|     { | ||||
|         return $this->entity() && $this->user()->can('edit', $this->entity()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation rules that apply to the request. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function rules() | ||||
|     { | ||||
|         if (! $this->entity()) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             'name' => sprintf('required|unique:proposal_categories,name,,id,account_id,%s', $this->user()->account_id), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										32
									
								
								app/Http/Requests/UpdateProposalRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/Http/Requests/UpdateProposalRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class UpdateProposalRequest extends ProposalRequest | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() | ||||
|     { | ||||
|         return $this->entity() && $this->user()->can('edit', $this->entity()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation rules that apply to the request. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function rules() | ||||
|     { | ||||
|         if (! $this->entity()) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             'invoice_id' => 'required', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										32
									
								
								app/Http/Requests/UpdateProposalSnippetRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/Http/Requests/UpdateProposalSnippetRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class UpdateProposalSnippetRequest extends ProposalSnippetRequest | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() | ||||
|     { | ||||
|         return $this->entity() && $this->user()->can('edit', $this->entity()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation rules that apply to the request. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function rules() | ||||
|     { | ||||
|         if (! $this->entity()) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             'name' => sprintf('required|unique:proposal_snippets,name,%s,id,account_id,%s', $this->entity()->id, $this->user()->account_id), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										32
									
								
								app/Http/Requests/UpdateProposalTemplateRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/Http/Requests/UpdateProposalTemplateRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\Requests; | ||||
| 
 | ||||
| class UpdateProposalTemplateRequest extends ProposalTemplateRequest | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() | ||||
|     { | ||||
|         return $this->entity() && $this->user()->can('edit', $this->entity()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation rules that apply to the request. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function rules() | ||||
|     { | ||||
|         if (! $this->entity()) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             'name' => sprintf('required|unique:proposal_templates,name,%s,id,account_id,%s', $this->entity()->id, $this->user()->account_id), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										48
									
								
								app/Http/ViewComposers/ProposalComposer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								app/Http/ViewComposers/ProposalComposer.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Http\ViewComposers; | ||||
| 
 | ||||
| use Illuminate\View\View; | ||||
| use App\Models\ProposalSnippet; | ||||
| use App\Models\Document; | ||||
| 
 | ||||
| /** | ||||
|  * ClientPortalHeaderComposer.php. | ||||
|  * | ||||
|  * @copyright See LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
| class ProposalComposer | ||||
| { | ||||
|     /** | ||||
|      * Bind data to the view. | ||||
|      * | ||||
|      * @param View $view | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function compose(View $view) | ||||
|     { | ||||
|         $snippets = ProposalSnippet::scope() | ||||
|             ->with('proposal_category') | ||||
|             ->orderBy('name') | ||||
|             ->get(); | ||||
| 
 | ||||
|         $view->with('snippets', $snippets); | ||||
| 
 | ||||
| 
 | ||||
|         $documents = Document::scope() | ||||
|             ->whereNull('invoice_id') | ||||
|             ->whereNull('expense_id') | ||||
|             ->get(); | ||||
| 
 | ||||
|         $data = []; | ||||
|         foreach ($documents as $document) { | ||||
|             $data[] = [ | ||||
|                 'src' => $document->getProposalUrl(), | ||||
|                 'public_id' => $document->public_id, | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|         $view->with('documents', $data); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										157
									
								
								app/Jobs/ConvertInvoiceToUbl.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								app/Jobs/ConvertInvoiceToUbl.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,157 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Jobs; | ||||
| 
 | ||||
| use App\Jobs\Job; | ||||
| use CleverIt\UBL\Invoice\Generator; | ||||
| use CleverIt\UBL\Invoice\Invoice; | ||||
| use CleverIt\UBL\Invoice\Party; | ||||
| use CleverIt\UBL\Invoice\Address; | ||||
| use CleverIt\UBL\Invoice\Country; | ||||
| use CleverIt\UBL\Invoice\Contact; | ||||
| use CleverIt\UBL\Invoice\TaxTotal; | ||||
| use CleverIt\UBL\Invoice\TaxSubTotal; | ||||
| use CleverIt\UBL\Invoice\TaxCategory; | ||||
| use CleverIt\UBL\Invoice\TaxScheme; | ||||
| use CleverIt\UBL\Invoice\InvoiceLine; | ||||
| use CleverIt\UBL\Invoice\Item; | ||||
| use CleverIt\UBL\Invoice\LegalMonetaryTotal; | ||||
| 
 | ||||
| class ConvertInvoiceToUbl extends Job | ||||
| { | ||||
|     const INVOICE_TYPE_STANDARD = 380; | ||||
|     const INVOICE_TYPE_CREDIT = 381; | ||||
| 
 | ||||
|     public function __construct($invoice) | ||||
|     { | ||||
|         $this->invoice = $invoice; | ||||
|     } | ||||
| 
 | ||||
|     public function handle() | ||||
|     { | ||||
|         $invoice = $this->invoice; | ||||
|         $account = $invoice->account; | ||||
|         $client = $invoice->client; | ||||
|         $ublInvoice = new Invoice(); | ||||
| 
 | ||||
|         // invoice
 | ||||
|         $ublInvoice->setId($invoice->invoice_number); | ||||
|         $ublInvoice->setIssueDate(date_create($invoice->invoice_date)); | ||||
|         $ublInvoice->setInvoiceTypeCode($invoice->amount < 0 ? self::INVOICE_TYPE_CREDIT : self::INVOICE_TYPE_STANDARD); | ||||
| 
 | ||||
|         $supplierParty = $this->createParty($account, $invoice->user); | ||||
|         $ublInvoice->setAccountingSupplierParty($supplierParty); | ||||
| 
 | ||||
|         $customerParty = $this->createParty($client, $client->contacts[0]); | ||||
|         $ublInvoice->setAccountingCustomerParty($customerParty); | ||||
| 
 | ||||
|         // line items
 | ||||
|         $invoiceLine = []; | ||||
|         $taxable = $invoice->getTaxable(); | ||||
| 
 | ||||
|         foreach ($invoice->invoice_items as $index => $item) { | ||||
|             $itemTaxable = $invoice->getItemTaxable($item, $taxable); | ||||
|             $item->setRelation('invoice', $invoice); | ||||
|             $invoiceLines[] = $this->createInvoiceLine($index, $item, $itemTaxable); | ||||
|         } | ||||
| 
 | ||||
|         $ublInvoice->setInvoiceLines($invoiceLines); | ||||
| 
 | ||||
|         if ($invoice->hasTaxes()) { | ||||
|             $taxtotal = new TaxTotal(); | ||||
|             $taxAmount1 = $taxAmount2 = 0; | ||||
| 
 | ||||
|             if ($invoice->tax_name1 || floatval($invoice->tax_rate1)) { | ||||
|                 $taxAmount1 = $this->createTaxRate($taxtotal, $taxable, $invoice->tax_rate1, $invoice->tax_name1); | ||||
|             } | ||||
| 
 | ||||
|             if ($invoice->tax_name2 || floatval($invoice->tax_rate2)) { | ||||
|                 $taxAmount2 = $this->createTaxRate($taxtotal, $taxable, $invoice->tax_rate2, $invoice->tax_name2); | ||||
|             } | ||||
| 
 | ||||
|             $taxtotal->setTaxAmount($taxAmount1 + $taxAmount2); | ||||
|             $ublInvoice->setTaxTotal($taxtotal); | ||||
|         } | ||||
| 
 | ||||
|         $ublInvoice->setLegalMonetaryTotal((new LegalMonetaryTotal()) | ||||
|             //->setLineExtensionAmount()
 | ||||
|             ->setTaxExclusiveAmount($taxable) | ||||
|             ->setPayableAmount($invoice->balance)); | ||||
| 
 | ||||
|         return Generator::invoice($ublInvoice, $invoice->client->getCurrencyCode()); | ||||
|     } | ||||
| 
 | ||||
|     private function createParty($company, $user) | ||||
|     { | ||||
|         $party = new Party(); | ||||
|         $party->setName($company->name); | ||||
|         $address = (new Address()) | ||||
|             ->setCityName($company->city) | ||||
|             ->setStreetName($company->address1) | ||||
|             ->setBuildingNumber($company->address2) | ||||
|             ->setPostalZone($company->postal_code); | ||||
| 
 | ||||
|         if ($company->country_id) { | ||||
|             $country = new Country(); | ||||
|             $country->setIdentificationCode($company->country->iso_3166_2); | ||||
|             $address->setCountry($country); | ||||
|         } | ||||
| 
 | ||||
|         $party->setPostalAddress($address); | ||||
|         $party->setPhysicalLocation($address); | ||||
| 
 | ||||
|         $contact = new Contact(); | ||||
|         $contact->setElectronicMail($user->email); | ||||
|         $party->setContact($contact); | ||||
| 
 | ||||
|         return $party; | ||||
|     } | ||||
| 
 | ||||
|     private function createInvoiceLine($index, $item, $taxable) | ||||
|     { | ||||
|         $invoiceLine = (new InvoiceLine()) | ||||
|             ->setId($index + 1) | ||||
|             ->setInvoicedQuantity($item->qty) | ||||
|             ->setLineExtensionAmount($item->costWithDiscount()) | ||||
|             ->setItem((new Item()) | ||||
|                 ->setName($item->product_key) | ||||
|                 ->setDescription($item->description)); | ||||
|                 //->setSellersItemIdentification("1ABCD"));
 | ||||
| 
 | ||||
|         if ($item->hasTaxes()) { | ||||
|             $taxtotal = new TaxTotal(); | ||||
|             $itemTaxAmount1 = $itemTaxAmount2 = 0; | ||||
| 
 | ||||
|             if ($item->tax_name1 || floatval($item->tax_rate1)) { | ||||
|                 $itemTaxAmount1 = $this->createTaxRate($taxtotal, $taxable, $item->tax_rate1, $item->tax_name1); | ||||
|             } | ||||
| 
 | ||||
|             if ($item->tax_name2 || floatval($item->tax_rate2)) { | ||||
|                 $itemTaxAmount2 = $this->createTaxRate($taxtotal, $taxable, $item->tax_rate2, $item->tax_name2); | ||||
|             } | ||||
| 
 | ||||
|             $taxtotal->setTaxAmount($itemTaxAmount1 + $itemTaxAmount2); | ||||
|             $invoiceLine->setTaxTotal($taxtotal); | ||||
|         } | ||||
| 
 | ||||
|         return $invoiceLine; | ||||
|     } | ||||
| 
 | ||||
|     private function createTaxRate(&$taxtotal, $taxable, $taxRate, $taxName) | ||||
|     { | ||||
|         $invoice = $this->invoice; | ||||
|         $taxAmount = $invoice->taxAmount($taxable, $taxRate); | ||||
|         $taxScheme = ((new TaxScheme()))->setId($taxName); | ||||
| 
 | ||||
|         $taxtotal->addTaxSubTotal((new TaxSubTotal()) | ||||
|                 ->setTaxAmount($taxAmount) | ||||
|                 ->setTaxableAmount($taxable) | ||||
|                 ->setTaxCategory((new TaxCategory()) | ||||
|                     ->setId($taxName) | ||||
|                     ->setName($taxName) | ||||
|                     ->setTaxScheme($taxScheme) | ||||
|                     ->setPercent($taxRate))); | ||||
| 
 | ||||
|         return $taxAmount; | ||||
|     } | ||||
| } | ||||
| @ -46,6 +46,10 @@ class DownloadInvoices extends Job | ||||
|      */ | ||||
|     public function handle(UserMailer $userMailer) | ||||
|     { | ||||
|         if (! extension_loaded('GMP')) { | ||||
|             die(trans('texts.gmp_required')); | ||||
|         } | ||||
| 
 | ||||
|         $zip = Archive::instance_by_useragent(date('Y-m-d') . '_' . str_replace(' ', '_', trans('texts.invoice_pdfs'))); | ||||
| 
 | ||||
|         foreach ($this->invoices as $invoice) { | ||||
| @ -54,34 +58,5 @@ class DownloadInvoices extends Job | ||||
| 
 | ||||
|         $zip->finish(); | ||||
|         exit; | ||||
| 
 | ||||
|         /* | ||||
|         // if queues are disabled download a zip file
 | ||||
|         if (config('queue.default') === 'sync' || count($this->invoices) <= 10) { | ||||
|             $zip = Archive::instance_by_useragent(date('Y-m-d') . '-Invoice_PDFs'); | ||||
|             foreach ($this->invoices as $invoice) { | ||||
|                 $zip->add_file($invoice->getFileName(), $invoice->getPDFString()); | ||||
|             } | ||||
|             $zip->finish(); | ||||
|             exit; | ||||
| 
 | ||||
|         // otherwise sends the PDFs in an email
 | ||||
|         } else { | ||||
|             $data = []; | ||||
|             foreach ($this->invoices as $invoice) { | ||||
|                 $data[] = [ | ||||
|                     'name' => $invoice->getFileName(), | ||||
|                     'data' => $invoice->getPDFString(), | ||||
|                 ]; | ||||
|             } | ||||
| 
 | ||||
|             $subject = trans('texts.invoices_are_attached'); | ||||
|             $data = [ | ||||
|                 'documents' => $data | ||||
|             ]; | ||||
| 
 | ||||
|             $userMailer->sendMessage($this->user, $subject, false, $data); | ||||
|         } | ||||
|         */ | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -50,6 +50,11 @@ class PurgeAccountData extends Job | ||||
|             'vendors', | ||||
|             'contacts', | ||||
|             'clients', | ||||
|             'proposals', | ||||
|             'proposal_templates', | ||||
|             'proposal_snippets', | ||||
|             'proposal_categories', | ||||
|             'proposal_invitations', | ||||
|         ]; | ||||
| 
 | ||||
|         foreach ($tables as $table) { | ||||
| @ -71,6 +76,7 @@ class PurgeAccountData extends Job | ||||
|             $lookupAccount = LookupAccount::whereAccountKey($account->account_key)->firstOrFail(); | ||||
|             DB::table('lookup_contacts')->where('lookup_account_id', '=', $lookupAccount->id)->delete(); | ||||
|             DB::table('lookup_invitations')->where('lookup_account_id', '=', $lookupAccount->id)->delete(); | ||||
|             DB::table('lookup_proposal_invitations')->where('lookup_account_id', '=', $lookupAccount->id)->delete(); | ||||
| 
 | ||||
|             config(['database.default' => $current]); | ||||
|         } | ||||
|  | ||||
| @ -43,6 +43,11 @@ class SendInvoiceEmail extends Job implements ShouldQueue | ||||
|      */ | ||||
|     protected $server; | ||||
| 
 | ||||
|     /** | ||||
|      * @var Proposal | ||||
|      */ | ||||
|     protected $proposal; | ||||
| 
 | ||||
|     /** | ||||
|      * Create a new job instance. | ||||
|      * | ||||
| @ -51,12 +56,13 @@ class SendInvoiceEmail extends Job implements ShouldQueue | ||||
|      * @param bool    $reminder | ||||
|      * @param mixed   $pdfString | ||||
|      */ | ||||
|     public function __construct(Invoice $invoice, $userId = false, $reminder = false, $template = false) | ||||
|     public function __construct(Invoice $invoice, $userId = false, $reminder = false, $template = false, $proposal = false) | ||||
|     { | ||||
|         $this->invoice = $invoice; | ||||
|         $this->userId = $userId; | ||||
|         $this->reminder = $reminder; | ||||
|         $this->template = $template; | ||||
|         $this->proposal = $proposal; | ||||
|         $this->server = config('database.default'); | ||||
|     } | ||||
| 
 | ||||
| @ -72,7 +78,7 @@ class SendInvoiceEmail extends Job implements ShouldQueue | ||||
|             Auth::onceUsingId($this->userId); | ||||
|         } | ||||
| 
 | ||||
|         $mailer->sendInvoice($this->invoice, $this->reminder, $this->template); | ||||
|         $mailer->sendInvoice($this->invoice, $this->reminder, $this->template, $this->proposal); | ||||
| 
 | ||||
|         if (App::runningInConsole() && $this->userId) { | ||||
|             Auth::logout(); | ||||
|  | ||||
| @ -61,4 +61,17 @@ class HTMLUtils | ||||
|             return $previous; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static function getEnvForAccount($field, $default = '') | ||||
|     { | ||||
|         $key = ''; | ||||
| 
 | ||||
|         if ($user = auth()->user()) { | ||||
|             $key .= $user->account->id . '_'; | ||||
|         } | ||||
| 
 | ||||
|         $key .= $field; | ||||
| 
 | ||||
|         return env($key, env($field, $default)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -12,6 +12,7 @@ class HistoryUtils | ||||
|     public static function loadHistory($users) | ||||
|     { | ||||
|         $userIds = []; | ||||
|         session([RECENTLY_VIEWED => false]); | ||||
| 
 | ||||
|         if (is_array($users)) { | ||||
|             foreach ($users as $user) { | ||||
| @ -37,7 +38,7 @@ class HistoryUtils | ||||
|             ACTIVITY_TYPE_VIEW_QUOTE, | ||||
|         ]; | ||||
| 
 | ||||
|         $activities = Activity::with(['client.contacts', 'invoice', 'task', 'expense']) | ||||
|         $activities = Activity::with(['client.contacts', 'invoice', 'task.project', 'expense']) | ||||
|             ->whereIn('user_id', $userIds) | ||||
|             ->whereIn('activity_type_id', $activityTypes) | ||||
|             ->orderBy('id', 'desc') | ||||
| @ -53,6 +54,12 @@ class HistoryUtils | ||||
|                     continue; | ||||
|                 } | ||||
|                 $entity->setRelation('client', $activity->client); | ||||
| 
 | ||||
|                 if ($entity->project) { | ||||
|                     $project = $entity->project; | ||||
|                     $project->setRelation('client', $activity->client); | ||||
|                     static::trackViewed($project); | ||||
|                 } | ||||
|             } elseif ($activity->activity_type_id == ACTIVITY_TYPE_CREATE_EXPENSE || $activity->activity_type_id == ACTIVITY_TYPE_UPDATE_EXPENSE) { | ||||
|                 $entity = $activity->expense; | ||||
|                 if (! $entity) { | ||||
| @ -80,6 +87,8 @@ class HistoryUtils | ||||
|             ENTITY_QUOTE, | ||||
|             ENTITY_TASK, | ||||
|             ENTITY_EXPENSE, | ||||
|             ENTITY_PROJECT, | ||||
|             ENTITY_PROPOSAL, | ||||
|             //ENTITY_RECURRING_EXPENSE,
 | ||||
|         ]; | ||||
| 
 | ||||
| @ -87,6 +96,10 @@ class HistoryUtils | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if ($entity->is_deleted) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         $object = static::convertToObject($entity); | ||||
|         $history = Session::get(RECENTLY_VIEWED) ?: []; | ||||
|         $accountHistory = isset($history[$entity->account_id]) ? $history[$entity->account_id] : []; | ||||
| @ -135,6 +148,9 @@ class HistoryUtils | ||||
|         } elseif (method_exists($entity, 'client') && $entity->client) { | ||||
|             $object->client_id = $entity->client->public_id; | ||||
|             $object->client_name = $entity->client->getDisplayName(); | ||||
|         } elseif (method_exists($entity, 'invoice') && $entity->invoice) { | ||||
|             $object->client_id = $entity->invoice->client->public_id; | ||||
|             $object->client_name = $entity->invoice->client->getDisplayName(); | ||||
|         } else { | ||||
|             $object->client_id = 0; | ||||
|             $object->client_name = 0; | ||||
| @ -175,7 +191,8 @@ class HistoryUtils | ||||
|                     $button = ''; | ||||
|                 } | ||||
| 
 | ||||
|                 $str .= sprintf('<li>%s<a href="%s"><div>%s %s</div></a></li>', $button, $link, $icon, $name); | ||||
|                 $padding = $str ? 16 : 0; | ||||
|                 $str .= sprintf('<li style="margin-top: %spx">%s<a href="%s"><div>%s %s</div></a></li>', $padding, $button, $link, $icon, $name); | ||||
|                 $lastClientId = $item->client_id; | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -364,7 +364,9 @@ class Utils | ||||
|             if ($field == 'checkbox') { | ||||
|                 $data[] = $field; | ||||
|             } elseif ($field) { | ||||
|                 if ($module) { | ||||
|                 if (substr($field, 0, 1) == '-') { | ||||
|                     $data[] = substr($field, 1); | ||||
|                 } elseif ($module) { | ||||
|                     $data[] = mtrans($module, $field); | ||||
|                 } else { | ||||
|                     $data[] = trans("texts.$field"); | ||||
| @ -564,6 +566,10 @@ class Utils | ||||
| 
 | ||||
|         if ($type === ENTITY_EXPENSE_CATEGORY) { | ||||
|             return 'expense_categories'; | ||||
|         } elseif ($type === ENTITY_PROPOSAL_CATEGORY) { | ||||
|             return 'proposal_categories'; | ||||
|         } elseif ($type === ENTITY_TASK_STATUS) { | ||||
|             return 'task_statuses'; | ||||
|         } else { | ||||
|             return $type . 's'; | ||||
|         } | ||||
| @ -1087,6 +1093,25 @@ class Utils | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static function getCustomLabel($value) | ||||
|     { | ||||
|         if (strpos($value, '|') !== false) { | ||||
|             return explode('|', $value)[0]; | ||||
|         } else { | ||||
|             return $value; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static function getCustomValues($value) | ||||
|     { | ||||
|         if (strpos($value, '|') !== false) { | ||||
|             $values = explode(',', explode('|', $value)[1]); | ||||
|             return array_combine($values, $values); | ||||
|         } else { | ||||
|             return $value; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static function formatWebsite($link) | ||||
|     { | ||||
|         if (! $link) { | ||||
| @ -1260,7 +1285,7 @@ class Utils | ||||
|         $tax1 = round($amount * $taxRate1 / 100, 2); | ||||
|         $tax2 = round($amount * $taxRate2 / 100, 2); | ||||
| 
 | ||||
|         return round($amount + $tax1 + $tax2, 2); | ||||
|         return round($tax1 + $tax2, 2); | ||||
|     } | ||||
| 
 | ||||
|     public static function roundSignificant($value, $precision = 2) { | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Listeners; | ||||
| 
 | ||||
| use App\Events\SubdomainWasRemoved; | ||||
| use App\Events\SubdomainWasUpdated; | ||||
| use App\Ninja\DNS\Cloudflare; | ||||
| 
 | ||||
| @ -19,4 +20,11 @@ class DNSListener | ||||
|         if(env("CLOUDFLARE_DNS_ENABLED")) | ||||
|             Cloudflare::addDNSRecord($event->account); | ||||
|     } | ||||
| 
 | ||||
|     public function removeDNSRecord(SubdomainWasRemoved $event) | ||||
|     { | ||||
|         if(env("CLOUDFLARE_DNS_ENABLED")) | ||||
|             Cloudflare::removeDNSRecord($event->account); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -102,7 +102,7 @@ class HandleUserLoggedIn | ||||
|             if (in_array(config('app.key'), ['SomeRandomString', 'SomeRandomStringSomeRandomString', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'])) { | ||||
|                 Session::flash('error', trans('texts.error_app_key_set_to_default')); | ||||
|             } elseif (in_array($appCipher, ['MCRYPT_RIJNDAEL_256', 'MCRYPT_RIJNDAEL_128'])) { | ||||
|                 Session::flash('error', trans('texts.mcrypt_warning')); | ||||
|                 Session::flash('error', trans('texts.mcrypt_warning', ['command' => '<code>php artisan ninja:update-key --legacy=true</code>'])); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -46,6 +46,8 @@ class HandleUserSettingsChanged | ||||
| 
 | ||||
|         if ($event->user && $event->user->isEmailBeingChanged()) { | ||||
|             $this->userMailer->sendConfirmation($event->user); | ||||
|             $this->userMailer->sendEmailChanged($event->user); | ||||
|              | ||||
|             Session::flash('warning', trans('texts.verify_email')); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -254,19 +254,30 @@ class SubscriptionListener | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // generate JSON data
 | ||||
|         $manager = new Manager(); | ||||
|         $manager->setSerializer(new ArraySerializer()); | ||||
|         $manager->parseIncludes($include); | ||||
| 
 | ||||
|         $resource = new Item($entity, $transformer, $entity->getEntityType()); | ||||
|         $data = $manager->createData($resource)->toArray(); | ||||
|         $jsonData = $manager->createData($resource)->toArray(); | ||||
| 
 | ||||
|         // For legacy Zapier support
 | ||||
|         if (isset($data['client_id'])) { | ||||
|             $data['client_name'] = $entity->client->getDisplayName(); | ||||
|         if (isset($jsonData['client_id'])) { | ||||
|             $jsonData['client_name'] = $entity->client->getDisplayName(); | ||||
|         } | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
|         foreach ($subscriptions as $subscription) { | ||||
|             switch ($subscription->format) { | ||||
|                 case SUBSCRIPTION_FORMAT_JSON: | ||||
|                     $data = $jsonData; | ||||
|                     break; | ||||
|                 case SUBSCRIPTION_FORMAT_UBL: | ||||
|                     $data = $ublData; | ||||
|                     break; | ||||
|             } | ||||
|             self::notifySubscription($subscription, $data); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -146,6 +146,7 @@ class Account extends Eloquent | ||||
|         'invoice_fields', | ||||
|         'invoice_embed_documents', | ||||
|         'document_email_attachment', | ||||
|         'ubl_email_attachment', | ||||
|         'enable_client_portal_dashboard', | ||||
|         'page_size', | ||||
|         'live_preview', | ||||
| @ -237,6 +238,8 @@ class Account extends Eloquent | ||||
|         'hours', | ||||
|         'id_number', | ||||
|         'invoice', | ||||
|         'invoice_date', | ||||
|         'invoice_number', | ||||
|         'item', | ||||
|         'line_total', | ||||
|         'outstanding', | ||||
| @ -245,6 +248,8 @@ class Account extends Eloquent | ||||
|         'po_number', | ||||
|         'quantity', | ||||
|         'quote', | ||||
|         'quote_date', | ||||
|         'quote_number', | ||||
|         'rate', | ||||
|         'service', | ||||
|         'subtotal', | ||||
| @ -500,7 +505,7 @@ class Account extends Eloquent | ||||
|         if ($gatewayId) { | ||||
|             return $this->getGatewayConfig($gatewayId) != false; | ||||
|         } else { | ||||
|             return count($this->account_gateways) > 0; | ||||
|             return $this->account_gateways->count() > 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -1484,6 +1489,14 @@ class Account extends Eloquent | ||||
|         return $this->hasFeature(FEATURE_PDF_ATTACHMENT) && $this->pdf_email_attachment; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function attachUBL() | ||||
|     { | ||||
|         return $this->hasFeature(FEATURE_PDF_ATTACHMENT) && $this->ubl_email_attachment; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
| @ -1643,6 +1656,7 @@ class Account extends Eloquent | ||||
|             ENTITY_EXPENSE, | ||||
|             ENTITY_VENDOR, | ||||
|             ENTITY_PROJECT, | ||||
|             ENTITY_PROPOSAL, | ||||
|         ])) { | ||||
|             return true; | ||||
|         } | ||||
| @ -1651,6 +1665,8 @@ class Account extends Eloquent | ||||
|             $entityType = ENTITY_EXPENSE; | ||||
|         } elseif ($entityType == ENTITY_PROJECT) { | ||||
|             $entityType = ENTITY_TASK; | ||||
|         } elseif ($entityType == ENTITY_PROPOSAL) { | ||||
|             $entityType = ENTITY_QUOTE; | ||||
|         } | ||||
| 
 | ||||
|         // note: single & checks bitmask match
 | ||||
|  | ||||
| @ -38,6 +38,7 @@ class AccountEmailSettings extends Eloquent | ||||
|     public static $templates = [ | ||||
|         TEMPLATE_INVOICE, | ||||
|         TEMPLATE_QUOTE, | ||||
|         TEMPLATE_PROPOSAL, | ||||
|         //TEMPLATE_PARTIAL,
 | ||||
|         TEMPLATE_PAYMENT, | ||||
|         TEMPLATE_REMINDER1, | ||||
|  | ||||
| @ -268,4 +268,13 @@ class AccountGateway extends EntityModel | ||||
| 
 | ||||
|         return \URL::to(env('WEBHOOK_PREFIX', '').'payment_hook/'.$account->account_key.'/'.$this->gateway_id.env('WEBHOOK_SUFFIX', '')); | ||||
|     } | ||||
| 
 | ||||
|     public function isTestMode() | ||||
|     { | ||||
|         if ($this->isGateway(GATEWAY_STRIPE)) { | ||||
|             return strpos($this->getPublishableStripeKey(), 'test') !== false; | ||||
|         } else { | ||||
|             return $this->getConfigField('testMode'); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -351,7 +351,7 @@ class Client extends EntityModel | ||||
|             return $this->name; | ||||
|         } | ||||
| 
 | ||||
|         if (! count($this->contacts)) { | ||||
|         if (! $this->contacts->count()) { | ||||
|             return ''; | ||||
|         } | ||||
| 
 | ||||
| @ -386,6 +386,29 @@ class Client extends EntityModel | ||||
|         return $this->hasAddress() && env('GOOGLE_MAPS_ENABLED') !== false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function addressesMatch() | ||||
|     { | ||||
|         $fields = [ | ||||
|             'address1', | ||||
|             'address2', | ||||
|             'city', | ||||
|             'state', | ||||
|             'postal_code', | ||||
|             'country_id', | ||||
|         ]; | ||||
| 
 | ||||
|         foreach ($fields as $field) { | ||||
|             if ($this->$field != $this->{'shipping_' . $field}) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
|  | ||||
| @ -131,6 +131,21 @@ class Contact extends EntityModel implements AuthenticatableContract, CanResetPa | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed|string | ||||
|      */ | ||||
|     public function getSearchName() | ||||
|     { | ||||
|         $name = $this->getFullName(); | ||||
|         $email = $this->email; | ||||
| 
 | ||||
|         if ($name && $email) { | ||||
|             return sprintf('%s <%s>', $name, $email); | ||||
|         } else { | ||||
|             return $name ?: $email; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param $contact_key | ||||
|      * | ||||
|  | ||||
| @ -41,6 +41,6 @@ class Country extends Eloquent | ||||
|      */ | ||||
|     public function getName() | ||||
|     { | ||||
|         return $this->name; | ||||
|         return trans('texts.country_' . $this->name); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -272,6 +272,15 @@ class Document extends EntityModel | ||||
|         return url('client/documents/'.$invitation->invitation_key.'/'.$this->public_id.'/'.$this->name); | ||||
|     } | ||||
| 
 | ||||
|     public function getProposalUrl() | ||||
|     { | ||||
|         if (! $this->is_proposal || ! $this->document_key) { | ||||
|             return ''; | ||||
|         } | ||||
| 
 | ||||
|         return url('proposal/image/'. $this->account->account_key . '/' . $this->document_key . '/' . $this->name); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
|  | ||||
| @ -321,6 +321,7 @@ class EntityModel extends Eloquent | ||||
|             'recurring_expenses' => 'files-o', | ||||
|             'credits' => 'credit-card', | ||||
|             'quotes' => 'file-text-o', | ||||
|             'proposals' => 'th-large', | ||||
|             'tasks' => 'clock-o', | ||||
|             'expenses' => 'file-image-o', | ||||
|             'vendors' => 'building', | ||||
| @ -354,6 +355,15 @@ class EntityModel extends Eloquent | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     public static function getFormUrl($entityType) | ||||
|     { | ||||
|         if (in_array($entityType, [ENTITY_PROPOSAL_CATEGORY, ENTITY_PROPOSAL_SNIPPET, ENTITY_PROPOSAL_TEMPLATE])) { | ||||
|             return str_replace('_', 's/', Utils::pluralizeEntityType($entityType)); | ||||
|         } else { | ||||
|             return Utils::pluralizeEntityType($entityType); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static function getStates($entityType = false) | ||||
|     { | ||||
|         $data = []; | ||||
|  | ||||
| @ -61,6 +61,7 @@ class Expense extends EntityModel | ||||
|             'vendor', | ||||
|             'amount', | ||||
|             'public_notes', | ||||
|             'private_notes', | ||||
|             'expense_category', | ||||
|             'expense_date', | ||||
|         ]; | ||||
| @ -73,7 +74,8 @@ class Expense extends EntityModel | ||||
|             'category' => 'expense_category', | ||||
|             'client' => 'client', | ||||
|             'vendor' => 'vendor', | ||||
|             'notes|details' => 'public_notes', | ||||
|             'notes|details^private' => 'public_notes', | ||||
|             'notes|details^public' => 'private_notes', | ||||
|             'date' => 'expense_date', | ||||
|         ]; | ||||
|     } | ||||
| @ -253,6 +255,11 @@ class Expense extends EntityModel | ||||
|     } | ||||
| 
 | ||||
|     public function amountWithTax() | ||||
|     { | ||||
|         return $this->amount + $this->taxAmount(); | ||||
|     } | ||||
| 
 | ||||
|     public function taxAmount() | ||||
|     { | ||||
|         return Utils::calculateTaxes($this->amount, $this->tax_rate1, $this->tax_rate2); | ||||
|     } | ||||
|  | ||||
| @ -2,10 +2,9 @@ | ||||
| 
 | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Carbon; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| use Utils; | ||||
| use App\Models\LookupInvitation; | ||||
| use App\Models\Traits\Inviteable; | ||||
| 
 | ||||
| /** | ||||
|  * Class Invitation. | ||||
| @ -13,6 +12,8 @@ use App\Models\LookupInvitation; | ||||
| class Invitation extends EntityModel | ||||
| { | ||||
|     use SoftDeletes; | ||||
|     use Inviteable; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
| @ -58,102 +59,6 @@ class Invitation extends EntityModel | ||||
|         return $this->belongsTo('App\Models\Account'); | ||||
|     } | ||||
| 
 | ||||
|     // If we're getting the link for PhantomJS to generate the PDF
 | ||||
|     // we need to make sure it's served from our site
 | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $type | ||||
|      * @param bool   $forceOnsite | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getLink($type = 'view', $forceOnsite = false, $forcePlain = false) | ||||
|     { | ||||
|         if (! $this->account) { | ||||
|             $this->load('account'); | ||||
|         } | ||||
| 
 | ||||
|         $account = $this->account; | ||||
|         $iframe_url = $account->iframe_url; | ||||
|         $url = trim(SITE_URL, '/'); | ||||
| 
 | ||||
|         if (env('REQUIRE_HTTPS')) { | ||||
|             $url = str_replace('http://', 'https://', $url); | ||||
|         } | ||||
| 
 | ||||
|         if ($account->hasFeature(FEATURE_CUSTOM_URL)) { | ||||
|             if (Utils::isNinjaProd() && ! Utils::isReseller()) { | ||||
|                 $url = $account->present()->clientPortalLink(); | ||||
|             } | ||||
| 
 | ||||
|             if ($iframe_url && ! $forceOnsite) { | ||||
|                 return "{$iframe_url}?{$this->invitation_key}"; | ||||
|             } elseif ($this->account->subdomain && ! $forcePlain) { | ||||
|                 $url = Utils::replaceSubdomain($url, $account->subdomain); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return "{$url}/{$type}/{$this->invitation_key}"; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool|string | ||||
|      */ | ||||
|     public function getStatus() | ||||
|     { | ||||
|         $hasValue = false; | ||||
|         $parts = []; | ||||
|         $statuses = $this->message_id ? ['sent', 'opened', 'viewed'] : ['sent', 'viewed']; | ||||
| 
 | ||||
|         foreach ($statuses as $status) { | ||||
|             $field = "{$status}_date"; | ||||
|             $date = ''; | ||||
|             if ($this->$field && $this->field != '0000-00-00 00:00:00') { | ||||
|                 $date = Utils::dateToString($this->$field); | ||||
|                 $hasValue = true; | ||||
|                 $parts[] = trans('texts.invitation_status_' . $status) . ': ' . $date; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $hasValue ? implode($parts, '<br/>') : false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function getName() | ||||
|     { | ||||
|         return $this->invitation_key; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param null $messageId | ||||
|      */ | ||||
|     public function markSent($messageId = null) | ||||
|     { | ||||
|         $this->message_id = $messageId; | ||||
|         $this->email_error = null; | ||||
|         $this->sent_date = Carbon::now()->toDateTimeString(); | ||||
|         $this->save(); | ||||
|     } | ||||
| 
 | ||||
|     public function isSent() | ||||
|     { | ||||
|         return $this->sent_date && $this->sent_date != '0000-00-00 00:00:00'; | ||||
|     } | ||||
| 
 | ||||
|     public function markViewed() | ||||
|     { | ||||
|         $invoice = $this->invoice; | ||||
|         $client = $invoice->client; | ||||
| 
 | ||||
|         $this->viewed_date = Carbon::now()->toDateTimeString(); | ||||
|         $this->save(); | ||||
| 
 | ||||
|         $invoice->markViewed(); | ||||
|         $client->markLoggedIn(); | ||||
|     } | ||||
| 
 | ||||
|     public function signatureDiv() | ||||
|     { | ||||
|         if (! $this->signature_base64) { | ||||
|  | ||||
| @ -451,6 +451,23 @@ class Invoice extends EntityModel implements BalanceAffecting | ||||
|                      ->where('is_recurring', '=', false); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param $query | ||||
|      * | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function scopeUnapprovedQuotes($query, $includeInvoiceId = false) | ||||
|     { | ||||
|         return $query->quotes() | ||||
|                     ->where(function ($query) use ($includeInvoiceId) { | ||||
|                         $query->whereId($includeInvoiceId) | ||||
|                             ->orWhere(function ($query) { | ||||
|                                   $query->where('invoice_status_id', '<', INVOICE_STATUS_APPROVED) | ||||
|                                     ->whereNull('quote_invoice_id'); | ||||
|                               }); | ||||
|                     }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param $query | ||||
|      * @param $typeId | ||||
| @ -710,11 +727,11 @@ class Invoice extends EntityModel implements BalanceAffecting | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getFileName() | ||||
|     public function getFileName($extension = 'pdf') | ||||
|     { | ||||
|         $entityType = $this->getEntityType(); | ||||
| 
 | ||||
|         return trans("texts.$entityType") . '_' . $this->invoice_number . '.pdf'; | ||||
|         return trans("texts.$entityType") . '_' . $this->invoice_number . '.' . $extension; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -841,6 +858,14 @@ class Invoice extends EntityModel implements BalanceAffecting | ||||
|         return $this->invoice_status_id >= INVOICE_STATUS_VIEWED; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isApproved() | ||||
|     { | ||||
|         return $this->invoice_status_id >= INVOICE_STATUS_APPROVED || $this->quote_invoice_id; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
| @ -1403,21 +1428,12 @@ class Invoice extends EntityModel implements BalanceAffecting | ||||
|         $paidAmount = $this->getAmountPaid($calculatePaid); | ||||
| 
 | ||||
|         if ($this->tax_name1) { | ||||
|             if ($account->inclusive_taxes) { | ||||
|                 $invoiceTaxAmount = round($taxable - ($taxable / (1 + ($this->tax_rate1 / 100))), 2); | ||||
|             } else { | ||||
|                 $invoiceTaxAmount = round($taxable * ($this->tax_rate1 / 100), 2); | ||||
|             } | ||||
|             $invoiceTaxAmount = $this->taxAmount($taxable, $this->tax_rate1); | ||||
|             $invoicePaidAmount = floatval($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0; | ||||
|             $this->calculateTax($taxes, $this->tax_name1, $this->tax_rate1, $invoiceTaxAmount, $invoicePaidAmount); | ||||
|         } | ||||
| 
 | ||||
|         if ($this->tax_name2) { | ||||
|             if ($account->inclusive_taxes) { | ||||
|                 $invoiceTaxAmount = round($taxable - ($taxable / (1 + ($this->tax_rate2 / 100))), 2); | ||||
|             } else { | ||||
|                 $invoiceTaxAmount = round($taxable * ($this->tax_rate2 / 100), 2); | ||||
|             } | ||||
|             $invoiceTaxAmount = $this->taxAmount($taxable, $this->tax_rate2); | ||||
|             $invoicePaidAmount = floatval($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0; | ||||
|             $this->calculateTax($taxes, $this->tax_name2, $this->tax_rate2, $invoiceTaxAmount, $invoicePaidAmount); | ||||
|         } | ||||
| @ -1426,21 +1442,12 @@ class Invoice extends EntityModel implements BalanceAffecting | ||||
|             $itemTaxable = $this->getItemTaxable($invoiceItem, $taxable); | ||||
| 
 | ||||
|             if ($invoiceItem->tax_name1) { | ||||
|                 if ($account->inclusive_taxes) { | ||||
|                     $itemTaxAmount = round($taxable - ($taxable / (1 + ($invoiceItem->tax_rate1 / 100))), 2); | ||||
|                 } else { | ||||
|                     $itemTaxAmount = round($itemTaxable * ($invoiceItem->tax_rate1 / 100), 2); | ||||
|                 } | ||||
|                 $itemTaxAmount = $this->taxAmount($itemTaxable, $invoiceItem->tax_rate1); | ||||
|                 $itemPaidAmount = floatval($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0; | ||||
|                 $this->calculateTax($taxes, $invoiceItem->tax_name1, $invoiceItem->tax_rate1, $itemTaxAmount, $itemPaidAmount); | ||||
|             } | ||||
| 
 | ||||
|             if ($invoiceItem->tax_name2) { | ||||
|                 if ($account->inclusive_taxes) { | ||||
|                     $itemTaxAmount = round($taxable - ($taxable / (1 + ($invoiceItem->tax_rate2 / 100))), 2); | ||||
|                 } else { | ||||
|                     $itemTaxAmount = round($itemTaxable * ($invoiceItem->tax_rate2 / 100), 2); | ||||
|                 } | ||||
|                 $itemTaxAmount = $this->taxAmount($itemTaxable, $invoiceItem->tax_rate2); | ||||
|                 $itemPaidAmount = floatval($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0; | ||||
|                 $this->calculateTax($taxes, $invoiceItem->tax_name2, $invoiceItem->tax_rate2, $itemTaxAmount, $itemPaidAmount); | ||||
|             } | ||||
| @ -1449,6 +1456,28 @@ class Invoice extends EntityModel implements BalanceAffecting | ||||
|         return $taxes; | ||||
|     } | ||||
| 
 | ||||
|     public function getTaxTotal() | ||||
|     { | ||||
|         $total = 0; | ||||
| 
 | ||||
|         foreach ($this->getTaxes() as $tax) { | ||||
|             $total += $tax['amount']; | ||||
|         } | ||||
| 
 | ||||
|         return $total; | ||||
|     } | ||||
| 
 | ||||
|     public function taxAmount($taxable, $rate) | ||||
|     { | ||||
|         $account = $this->account; | ||||
| 
 | ||||
|         if ($account->inclusive_taxes) { | ||||
|             return round($taxable - ($taxable / (1 + ($rate / 100))), 2); | ||||
|         } else { | ||||
|             return round($taxable * ($rate / 100), 2); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param $taxes | ||||
|      * @param $name | ||||
| @ -1484,18 +1513,18 @@ class Invoice extends EntityModel implements BalanceAffecting | ||||
|      */ | ||||
|     public function countDocuments($expenses = false) | ||||
|     { | ||||
|         $count = count($this->documents); | ||||
|         $count = $this->documents->count(); | ||||
| 
 | ||||
|         foreach ($this->expenses as $expense) { | ||||
|             if ($expense->invoice_documents) { | ||||
|                 $count += count($expense->documents); | ||||
|                 $count += $expense->documents->count(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if ($expenses) { | ||||
|             foreach ($expenses as $expense) { | ||||
|                 if ($expense->invoice_documents) { | ||||
|                     $count += count($expense->documents); | ||||
|                     $count += $expense->documents->count(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @ -1525,7 +1554,7 @@ class Invoice extends EntityModel implements BalanceAffecting | ||||
|     public function hasExpenseDocuments() | ||||
|     { | ||||
|         foreach ($this->expenses as $expense) { | ||||
|             if ($expense->invoice_documents && count($expense->documents)) { | ||||
|             if ($expense->invoice_documents && $expense->documents->count()) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
| @ -1606,6 +1635,28 @@ class Invoice extends EntityModel implements BalanceAffecting | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     public function hasTaxes() | ||||
|     { | ||||
|         if ($this->tax_name1 || $this->tax_rate1) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if ($this->tax_name2 || $this->tax_rate2) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     public function isLocked() | ||||
|     { | ||||
|         if (! config('ninja.lock_sent_invoices')) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return $this->isSent() && ! $this->is_recurring; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Invoice::creating(function ($invoice) { | ||||
|  | ||||
| @ -107,4 +107,33 @@ class InvoiceItem extends EntityModel | ||||
|             $this->save(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function hasTaxes() | ||||
|     { | ||||
|         if ($this->tax_name1 || $this->tax_rate1) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if ($this->tax_name2 || $this->tax_rate2) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     public function costWithDiscount() | ||||
|     { | ||||
|         $cost = $this->cost; | ||||
| 
 | ||||
|         if ($this->discount != 0) { | ||||
|             if ($this->invoice->is_amount_discount) { | ||||
|                 $cost -= $discount / $this->qty; | ||||
|             } else { | ||||
|                 $cost -= $cost * $discount / 100; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $cost; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
							
								
								
									
										47
									
								
								app/Models/LookupProposalInvitation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								app/Models/LookupProposalInvitation.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Eloquent; | ||||
| 
 | ||||
| /** | ||||
|  * Class ExpenseCategory. | ||||
|  */ | ||||
| class LookupProposalInvitation extends LookupModel | ||||
| { | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $fillable = [ | ||||
|         'lookup_account_id', | ||||
|         'invitation_key', | ||||
|         'message_id', | ||||
|     ]; | ||||
| 
 | ||||
|     public static function updateInvitation($accountKey, $invitation) | ||||
|     { | ||||
|         if (! env('MULTI_DB_ENABLED')) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (! $invitation->message_id) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         $current = config('database.default'); | ||||
|         config(['database.default' => DB_NINJA_LOOKUP]); | ||||
| 
 | ||||
|         $lookupAccount = LookupAccount::whereAccountKey($accountKey) | ||||
|                             ->firstOrFail(); | ||||
| 
 | ||||
|         $lookupInvitation = LookupProposalInvitation::whereLookupAccountId($lookupAccount->id) | ||||
|                                 ->whereInvitationKey($invitation->invitation_key) | ||||
|                                 ->firstOrFail(); | ||||
| 
 | ||||
|         $lookupInvitation->message_id = $invitation->message_id; | ||||
|         $lookupInvitation->save(); | ||||
| 
 | ||||
|         config(['database.default' => $current]); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -47,6 +47,8 @@ class Product extends EntityModel | ||||
|             'product_key', | ||||
|             'notes', | ||||
|             'cost', | ||||
|             'custom_value1', | ||||
|             'custom_value2', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
| @ -59,6 +61,8 @@ class Product extends EntityModel | ||||
|             'product|item' => 'product_key', | ||||
|             'notes|description|details' => 'notes', | ||||
|             'cost|amount|price' => 'cost', | ||||
|             'custom_value1' => 'custom_value1', | ||||
|             'custom_value2' => 'custom_value2', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										107
									
								
								app/Models/Proposal.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								app/Models/Proposal.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| use Laracasts\Presenter\PresentableTrait; | ||||
| 
 | ||||
| /** | ||||
|  * Class ExpenseCategory. | ||||
|  */ | ||||
| class Proposal extends EntityModel | ||||
| { | ||||
|     use SoftDeletes; | ||||
|     use PresentableTrait; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $dates = ['deleted_at']; | ||||
|     /** | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $presenter = 'App\Ninja\Presenters\ProposalPresenter'; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $fillable = [ | ||||
|         'private_notes', | ||||
|         'html', | ||||
|         'css', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * @var string | ||||
|      */ | ||||
|     //protected $presenter = 'App\Ninja\Presenters\ProjectPresenter';
 | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function getEntityType() | ||||
|     { | ||||
|         return ENTITY_PROPOSAL; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getRoute() | ||||
|     { | ||||
|         return "/proposals/{$this->public_id}"; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||||
|      */ | ||||
|     public function account() | ||||
|     { | ||||
|         return $this->belongsTo('App\Models\Account'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function invoice() | ||||
|     { | ||||
|         return $this->belongsTo('App\Models\Invoice')->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function invitations() | ||||
|     { | ||||
|         return $this->hasMany('App\Models\ProposalInvitation')->orderBy('proposal_invitations.contact_id'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function proposal_invitations() | ||||
|     { | ||||
|         return $this->hasMany('App\Models\ProposalInvitation')->orderBy('proposal_invitations.contact_id'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||||
|      */ | ||||
|     public function proposal_template() | ||||
|     { | ||||
|         return $this->belongsTo('App\Models\ProposalTemplate')->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function getDisplayName() | ||||
|     { | ||||
|         return $this->invoice->invoice_number; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Proposal::creating(function ($project) { | ||||
|     $project->setNullValues(); | ||||
| }); | ||||
| 
 | ||||
| Proposal::updating(function ($project) { | ||||
|     $project->setNullValues(); | ||||
| }); | ||||
							
								
								
									
										71
									
								
								app/Models/ProposalCategory.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								app/Models/ProposalCategory.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| use Laracasts\Presenter\PresentableTrait; | ||||
| 
 | ||||
| /** | ||||
|  * Class ExpenseCategory. | ||||
|  */ | ||||
| class ProposalCategory extends EntityModel | ||||
| { | ||||
|     use SoftDeletes; | ||||
|     use PresentableTrait; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $dates = ['deleted_at']; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $fillable = [ | ||||
|         'name', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * @var string | ||||
|      */ | ||||
|     //protected $presenter = 'App\Ninja\Presenters\ProjectPresenter';
 | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function getEntityType() | ||||
|     { | ||||
|         return ENTITY_PROPOSAL_CATEGORY; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getRoute() | ||||
|     { | ||||
|         return "/proposals/categories/{$this->public_id}"; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||||
|      */ | ||||
|     public function account() | ||||
|     { | ||||
|         return $this->belongsTo('App\Models\Account'); | ||||
|     } | ||||
| 
 | ||||
|     public function getDisplayName() | ||||
|     { | ||||
|         return $this->name; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| Proposal::creating(function ($project) { | ||||
|     $project->setNullValues(); | ||||
| }); | ||||
| 
 | ||||
| Proposal::updating(function ($project) { | ||||
|     $project->setNullValues(); | ||||
| }); | ||||
| */ | ||||
							
								
								
									
										85
									
								
								app/Models/ProposalInvitation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								app/Models/ProposalInvitation.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| use App\Models\LookupProposalInvitation; | ||||
| use App\Models\Traits\Inviteable; | ||||
| 
 | ||||
| /** | ||||
|  * Class Invitation. | ||||
|  */ | ||||
| class ProposalInvitation extends EntityModel | ||||
| { | ||||
|     use SoftDeletes; | ||||
|     use Inviteable; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $dates = ['deleted_at']; | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function getEntityType() | ||||
|     { | ||||
|         return ENTITY_PROPOSAL_INVITATION; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function proposal() | ||||
|     { | ||||
|         return $this->belongsTo('App\Models\Proposal')->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function contact() | ||||
|     { | ||||
|         return $this->belongsTo('App\Models\Contact')->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function user() | ||||
|     { | ||||
|         return $this->belongsTo('App\Models\User')->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||||
|      */ | ||||
|     public function account() | ||||
|     { | ||||
|         return $this->belongsTo('App\Models\Account'); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ProposalInvitation::creating(function ($invitation) | ||||
| { | ||||
|     LookupProposalInvitation::createNew($invitation->account->account_key, [ | ||||
|         'invitation_key' => $invitation->invitation_key, | ||||
|     ]); | ||||
| }); | ||||
| 
 | ||||
| ProposalInvitation::updating(function ($invitation) | ||||
| { | ||||
|     $dirty = $invitation->getDirty(); | ||||
|     if (array_key_exists('message_id', $dirty)) { | ||||
|         LookupProposalInvitation::updateInvitation($invitation->account->account_key, $invitation); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| ProposalInvitation::deleted(function ($invitation) | ||||
| { | ||||
|     if ($invitation->forceDeleting) { | ||||
|         LookupProposalInvitation::deleteWhere([ | ||||
|             'invitation_key' => $invitation->invitation_key, | ||||
|         ]); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										84
									
								
								app/Models/ProposalSnippet.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								app/Models/ProposalSnippet.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| use Laracasts\Presenter\PresentableTrait; | ||||
| 
 | ||||
| /** | ||||
|  * Class ExpenseCategory. | ||||
|  */ | ||||
| class ProposalSnippet extends EntityModel | ||||
| { | ||||
|     use SoftDeletes; | ||||
|     use PresentableTrait; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $dates = ['deleted_at']; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $fillable = [ | ||||
|         'name', | ||||
|         'icon', | ||||
|         'private_notes', | ||||
|         'proposal_category_id', | ||||
|         'html', | ||||
|         'css', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $presenter = 'App\Ninja\Presenters\ProposalSnippetPresenter'; | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function getEntityType() | ||||
|     { | ||||
|         return ENTITY_PROPOSAL_SNIPPET; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getRoute() | ||||
|     { | ||||
|         return "/proposals/snippets/{$this->public_id}"; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||||
|      */ | ||||
|     public function account() | ||||
|     { | ||||
|         return $this->belongsTo('App\Models\Account'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||||
|      */ | ||||
|     public function proposal_category() | ||||
|     { | ||||
|         return $this->belongsTo('App\Models\ProposalCategory')->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function getDisplayName() | ||||
|     { | ||||
|         return $this->name; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| Proposal::creating(function ($project) { | ||||
|     $project->setNullValues(); | ||||
| }); | ||||
| 
 | ||||
| Proposal::updating(function ($project) { | ||||
|     $project->setNullValues(); | ||||
| }); | ||||
| */ | ||||
							
								
								
									
										74
									
								
								app/Models/ProposalTemplate.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								app/Models/ProposalTemplate.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| use Laracasts\Presenter\PresentableTrait; | ||||
| 
 | ||||
| /** | ||||
|  * Class ExpenseCategory. | ||||
|  */ | ||||
| class ProposalTemplate extends EntityModel | ||||
| { | ||||
|     use SoftDeletes; | ||||
|     use PresentableTrait; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $dates = ['deleted_at']; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $fillable = [ | ||||
|         'name', | ||||
|         'private_notes', | ||||
|         'html', | ||||
|         'css', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $presenter = 'App\Ninja\Presenters\ProposalTemplatePresenter'; | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function getEntityType() | ||||
|     { | ||||
|         return ENTITY_PROPOSAL_TEMPLATE; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getRoute() | ||||
|     { | ||||
|         return "/proposals/templates/{$this->public_id}"; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||||
|      */ | ||||
|     public function account() | ||||
|     { | ||||
|         return $this->belongsTo('App\Models\Account'); | ||||
|     } | ||||
| 
 | ||||
|     public function getDisplayName() | ||||
|     { | ||||
|         return $this->name; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| Proposal::creating(function ($project) { | ||||
|     $project->setNullValues(); | ||||
| }); | ||||
| 
 | ||||
| Proposal::updating(function ($project) { | ||||
|     $project->setNullValues(); | ||||
| }); | ||||
| */ | ||||
| @ -129,7 +129,7 @@ class RecurringExpense extends EntityModel | ||||
| 
 | ||||
|     public function amountWithTax() | ||||
|     { | ||||
|         return Utils::calculateTaxes($this->amount, $this->tax_rate1, $this->tax_rate2); | ||||
|         return $this->amount + Utils::calculateTaxes($this->amount, $this->tax_rate1, $this->tax_rate2); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -28,6 +28,7 @@ class Subscription extends EntityModel | ||||
|     protected $fillable = [ | ||||
|         'event_id', | ||||
|         'target_url', | ||||
|         'format', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -129,17 +129,27 @@ class Task extends EntityModel | ||||
|      * | ||||
|      * @return int | ||||
|      */ | ||||
|     public static function calcDuration($task) | ||||
|     public static function calcDuration($task, $startTimeCutoff = 0, $endTimeCutoff = 0) | ||||
|     { | ||||
|         $duration = 0; | ||||
|         $parts = json_decode($task->time_log) ?: []; | ||||
| 
 | ||||
|         foreach ($parts as $part) { | ||||
|             $startTime = $part[0]; | ||||
|             if (count($part) == 1 || ! $part[1]) { | ||||
|                 $duration += time() - $part[0]; | ||||
|                 $endTime = time(); | ||||
|             } else { | ||||
|                 $duration += $part[1] - $part[0]; | ||||
|                 $endTime = $part[1]; | ||||
|             } | ||||
| 
 | ||||
|             if ($startTimeCutoff) { | ||||
|                 $startTime = max($startTime, $startTimeCutoff); | ||||
|             } | ||||
|             if ($endTimeCutoff) { | ||||
|                 $endTime = min($endTime, $endTimeCutoff); | ||||
|             } | ||||
| 
 | ||||
|             $duration += $endTime - $startTime; | ||||
|         } | ||||
| 
 | ||||
|         return $duration; | ||||
| @ -148,9 +158,9 @@ class Task extends EntityModel | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     public function getDuration() | ||||
|     public function getDuration($startTimeCutoff = 0, $endTimeCutoff = 0) | ||||
|     { | ||||
|         return self::calcDuration($this); | ||||
|         return self::calcDuration($this, $startTimeCutoff, $endTimeCutoff); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -230,8 +240,11 @@ class Task extends EntityModel | ||||
| 
 | ||||
|     public function scopeDateRange($query, $startDate, $endDate) | ||||
|     { | ||||
|         $query->whereRaw('cast(substring(time_log, 3, 10) as unsigned) >= ' . $startDate->format('U')); | ||||
|         $query->whereRaw('cast(substring(time_log, 3, 10) as unsigned) <= ' . $endDate->modify('+1 day')->format('U')); | ||||
|         $query->whereRaw('cast(substring(time_log, 3, 10) as unsigned) <= ' . $endDate->modify('+1 day')->format('U')) | ||||
|             ->whereRaw('case | ||||
|                 when is_running then unix_timestamp() | ||||
|                 else cast(substring(time_log, length(time_log) - 11, 10) as unsigned) | ||||
|             end >= ' . $startDate->format('U')); | ||||
| 
 | ||||
|         return $query; | ||||
|     } | ||||
|  | ||||
							
								
								
									
										113
									
								
								app/Models/Traits/Inviteable.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								app/Models/Traits/Inviteable.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,113 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Models\Traits; | ||||
| 
 | ||||
| use Carbon; | ||||
| use Utils; | ||||
| 
 | ||||
| /** | ||||
|  * Class SendsEmails. | ||||
|  */ | ||||
| trait Inviteable | ||||
| { | ||||
|     // If we're getting the link for PhantomJS to generate the PDF
 | ||||
|     // we need to make sure it's served from our site
 | ||||
|     /** | ||||
|      * @param string $type | ||||
|      * @param bool   $forceOnsite | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getLink($type = 'view', $forceOnsite = false, $forcePlain = false) | ||||
|     { | ||||
|         if (! $this->account) { | ||||
|             $this->load('account'); | ||||
|         } | ||||
| 
 | ||||
|         if ($this->proposal_id) { | ||||
|             $type = 'proposal'; | ||||
|         } | ||||
| 
 | ||||
|         $account = $this->account; | ||||
|         $iframe_url = $account->iframe_url; | ||||
|         $url = trim(SITE_URL, '/'); | ||||
| 
 | ||||
|         if (env('REQUIRE_HTTPS')) { | ||||
|             $url = str_replace('http://', 'https://', $url); | ||||
|         } | ||||
| 
 | ||||
|         if ($account->hasFeature(FEATURE_CUSTOM_URL)) { | ||||
|             if (Utils::isNinjaProd() && ! Utils::isReseller()) { | ||||
|                 $url = $account->present()->clientPortalLink(); | ||||
|             } | ||||
| 
 | ||||
|             if ($iframe_url && ! $forceOnsite) { | ||||
|                 return "{$iframe_url}?{$this->invitation_key}"; | ||||
|             } elseif ($this->account->subdomain && ! $forcePlain) { | ||||
|                 $url = Utils::replaceSubdomain($url, $account->subdomain); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return "{$url}/{$type}/{$this->invitation_key}"; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool|string | ||||
|      */ | ||||
|     public function getStatus() | ||||
|     { | ||||
|         $hasValue = false; | ||||
|         $parts = []; | ||||
|         $statuses = $this->message_id ? ['sent', 'opened', 'viewed'] : ['sent', 'viewed']; | ||||
| 
 | ||||
|         foreach ($statuses as $status) { | ||||
|             $field = "{$status}_date"; | ||||
|             $date = ''; | ||||
|             if ($this->$field && $this->field != '0000-00-00 00:00:00') { | ||||
|                 $date = Utils::dateToString($this->$field); | ||||
|                 $hasValue = true; | ||||
|                 $parts[] = trans('texts.invitation_status_' . $status) . ': ' . $date; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $hasValue ? implode($parts, '<br/>') : false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function getName() | ||||
|     { | ||||
|         return $this->invitation_key; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param null $messageId | ||||
|      */ | ||||
|     public function markSent($messageId = null) | ||||
|     { | ||||
|         $this->message_id = $messageId; | ||||
|         $this->email_error = null; | ||||
|         $this->sent_date = Carbon::now()->toDateTimeString(); | ||||
|         $this->save(); | ||||
|     } | ||||
| 
 | ||||
|     public function isSent() | ||||
|     { | ||||
|         return $this->sent_date && $this->sent_date != '0000-00-00 00:00:00'; | ||||
|     } | ||||
| 
 | ||||
|     public function markViewed() | ||||
|     { | ||||
|         $this->viewed_date = Carbon::now()->toDateTimeString(); | ||||
|         $this->save(); | ||||
| 
 | ||||
|         if ($this->invoice) { | ||||
|             $invoice = $this->invoice; | ||||
|             $client = $invoice->client; | ||||
| 
 | ||||
|             $invoice->markViewed(); | ||||
|             $client->markLoggedIn(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user