mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-06-23 20:00:33 -04:00
Merge branch 'release-4.0.0'
This commit is contained in:
commit
730b378b5b
@ -114,6 +114,8 @@ after_script:
|
|||||||
- mysql -u root -e 'select * from credits;' ninja
|
- mysql -u root -e 'select * from credits;' ninja
|
||||||
- mysql -u root -e 'select * from expenses;' ninja
|
- mysql -u root -e 'select * from expenses;' ninja
|
||||||
- mysql -u root -e 'select * from accounts;' ninja
|
- mysql -u root -e 'select * from accounts;' ninja
|
||||||
|
- mysql -u root -e 'select * from fonts;' ninja
|
||||||
|
- mysql -u root -e 'select * from banks;' ninja
|
||||||
- cat storage/logs/laravel-error.log
|
- cat storage/logs/laravel-error.log
|
||||||
- cat storage/logs/laravel-info.log
|
- cat storage/logs/laravel-info.log
|
||||||
- FILES=$(find tests/_output -type f -name '*.png' | sort -nr)
|
- FILES=$(find tests/_output -type f -name '*.png' | sort -nr)
|
||||||
|
@ -21,7 +21,7 @@ class InitLookup extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'ninja:init-lookup {--truncate=} {--validate=} {--update=} {--company_id=} {--page_size=100} {--database=db-ninja-1}';
|
protected $signature = 'ninja:init-lookup {--truncate=} {--subdomain} {--validate=} {--update=} {--company_id=} {--page_size=100} {--database=db-ninja-1}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@ -57,9 +57,12 @@ class InitLookup extends Command
|
|||||||
$database = $this->option('database');
|
$database = $this->option('database');
|
||||||
$dbServer = DbServer::whereName($database)->first();
|
$dbServer = DbServer::whereName($database)->first();
|
||||||
|
|
||||||
if ($this->option('truncate')) {
|
if ($this->option('subdomain')) {
|
||||||
|
$this->logMessage('Updating subdomains...');
|
||||||
|
$this->popuplateSubdomains();
|
||||||
|
} else if ($this->option('truncate')) {
|
||||||
|
$this->logMessage('Truncating data...');
|
||||||
$this->truncateTables();
|
$this->truncateTables();
|
||||||
$this->logMessage('Truncated');
|
|
||||||
} else {
|
} else {
|
||||||
config(['database.default' => $this->option('database')]);
|
config(['database.default' => $this->option('database')]);
|
||||||
|
|
||||||
@ -87,6 +90,30 @@ class InitLookup extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function popuplateSubdomains()
|
||||||
|
{
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
config(['database.default' => $this->option('database')]);
|
||||||
|
|
||||||
|
$accounts = DB::table('accounts')
|
||||||
|
->orderBy('id')
|
||||||
|
->where('subdomain', '!=', '')
|
||||||
|
->get(['account_key', 'subdomain']);
|
||||||
|
foreach ($accounts as $account) {
|
||||||
|
$data[$account->account_key] = $account->subdomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
config(['database.default' => DB_NINJA_LOOKUP]);
|
||||||
|
|
||||||
|
$validate = $this->option('validate');
|
||||||
|
$update = $this->option('update');
|
||||||
|
|
||||||
|
foreach ($data as $accountKey => $subdomain) {
|
||||||
|
LookupAccount::whereAccountKey($accountKey)->update(['subdomain' => $subdomain]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function initCompanies($dbServerId, $offset = 0)
|
private function initCompanies($dbServerId, $offset = 0)
|
||||||
{
|
{
|
||||||
$data = [];
|
$data = [];
|
||||||
@ -340,6 +367,7 @@ class InitLookup extends Command
|
|||||||
protected function getOptions()
|
protected function getOptions()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
['subdomain', null, InputOption::VALUE_OPTIONAL, 'Subdomain', null],
|
||||||
['truncate', null, InputOption::VALUE_OPTIONAL, 'Truncate', null],
|
['truncate', null, InputOption::VALUE_OPTIONAL, 'Truncate', null],
|
||||||
['company_id', null, InputOption::VALUE_OPTIONAL, 'Company Id', null],
|
['company_id', null, InputOption::VALUE_OPTIONAL, 'Company Id', null],
|
||||||
['page_size', null, InputOption::VALUE_OPTIONAL, 'Page Size', null],
|
['page_size', null, InputOption::VALUE_OPTIONAL, 'Page Size', null],
|
||||||
|
@ -116,8 +116,10 @@ class SendRecurringInvoices extends Command
|
|||||||
try {
|
try {
|
||||||
$invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice);
|
$invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice);
|
||||||
if ($invoice && ! $invoice->isPaid()) {
|
if ($invoice && ! $invoice->isPaid()) {
|
||||||
$this->info('Sending Invoice');
|
$this->info('Not billed - Sending Invoice');
|
||||||
$this->mailer->sendInvoice($invoice);
|
$this->mailer->sendInvoice($invoice);
|
||||||
|
} elseif ($invoice) {
|
||||||
|
$this->info('Successfully billed invoice');
|
||||||
}
|
}
|
||||||
} catch (Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
$this->info('Error: ' . $exception->getMessage());
|
$this->info('Error: ' . $exception->getMessage());
|
||||||
|
@ -2,12 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Carbon;
|
||||||
|
use Str;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Ninja\Mailers\ContactMailer as Mailer;
|
use App\Ninja\Mailers\ContactMailer as Mailer;
|
||||||
|
use App\Ninja\Mailers\UserMailer;
|
||||||
use App\Ninja\Repositories\AccountRepository;
|
use App\Ninja\Repositories\AccountRepository;
|
||||||
use App\Ninja\Repositories\InvoiceRepository;
|
use App\Ninja\Repositories\InvoiceRepository;
|
||||||
|
use App\Models\ScheduledReport;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use App\Jobs\ExportReportResults;
|
||||||
|
use App\Jobs\RunReport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SendReminders.
|
* Class SendReminders.
|
||||||
@ -46,13 +52,14 @@ class SendReminders extends Command
|
|||||||
* @param InvoiceRepository $invoiceRepo
|
* @param InvoiceRepository $invoiceRepo
|
||||||
* @param accountRepository $accountRepo
|
* @param accountRepository $accountRepo
|
||||||
*/
|
*/
|
||||||
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo)
|
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, UserMailer $userMailer)
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->mailer = $mailer;
|
$this->mailer = $mailer;
|
||||||
$this->invoiceRepo = $invoiceRepo;
|
$this->invoiceRepo = $invoiceRepo;
|
||||||
$this->accountRepo = $accountRepo;
|
$this->accountRepo = $accountRepo;
|
||||||
|
$this->userMailer = $userMailer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fire()
|
public function fire()
|
||||||
@ -63,6 +70,23 @@ class SendReminders extends Command
|
|||||||
config(['database.default' => $database]);
|
config(['database.default' => $database]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->chargeLateFees();
|
||||||
|
$this->setReminderEmails();
|
||||||
|
$this->sendScheduledReports();
|
||||||
|
|
||||||
|
$this->info('Done');
|
||||||
|
|
||||||
|
if ($errorEmail = env('ERROR_EMAIL')) {
|
||||||
|
\Mail::raw('EOM', function ($message) use ($errorEmail, $database) {
|
||||||
|
$message->to($errorEmail)
|
||||||
|
->from(CONTACT_EMAIL)
|
||||||
|
->subject("SendReminders [{$database}]: Finished successfully");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function chargeLateFees()
|
||||||
|
{
|
||||||
$accounts = $this->accountRepo->findWithFees();
|
$accounts = $this->accountRepo->findWithFees();
|
||||||
$this->info(count($accounts) . ' accounts found with fees');
|
$this->info(count($accounts) . ' accounts found with fees');
|
||||||
|
|
||||||
@ -79,17 +103,20 @@ class SendReminders extends Command
|
|||||||
$this->info('Charge fee: ' . $invoice->id);
|
$this->info('Charge fee: ' . $invoice->id);
|
||||||
$account->loadLocalizationSettings($invoice->client); // support trans to add fee line item
|
$account->loadLocalizationSettings($invoice->client); // support trans to add fee line item
|
||||||
$number = preg_replace('/[^0-9]/', '', $reminder);
|
$number = preg_replace('/[^0-9]/', '', $reminder);
|
||||||
|
|
||||||
$amount = $account->account_email_settings->{"late_fee{$number}_amount"};
|
$amount = $account->account_email_settings->{"late_fee{$number}_amount"};
|
||||||
$percent = $account->account_email_settings->{"late_fee{$number}_percent"};
|
$percent = $account->account_email_settings->{"late_fee{$number}_percent"};
|
||||||
$this->invoiceRepo->setLateFee($invoice, $amount, $percent);
|
$this->invoiceRepo->setLateFee($invoice, $amount, $percent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setReminderEmails()
|
||||||
|
{
|
||||||
$accounts = $this->accountRepo->findWithReminders();
|
$accounts = $this->accountRepo->findWithReminders();
|
||||||
$this->info(count($accounts) . ' accounts found with reminders');
|
$this->info(count($accounts) . ' accounts found with reminders');
|
||||||
|
|
||||||
/** @var \App\Models\Account $account */
|
|
||||||
foreach ($accounts as $account) {
|
foreach ($accounts as $account) {
|
||||||
if (! $account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
|
if (! $account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
|
||||||
continue;
|
continue;
|
||||||
@ -98,7 +125,6 @@ class SendReminders extends Command
|
|||||||
$invoices = $this->invoiceRepo->findNeedingReminding($account);
|
$invoices = $this->invoiceRepo->findNeedingReminding($account);
|
||||||
$this->info($account->name . ': ' . count($invoices) . ' invoices found');
|
$this->info($account->name . ': ' . count($invoices) . ' invoices found');
|
||||||
|
|
||||||
/** @var Invoice $invoice */
|
|
||||||
foreach ($invoices as $invoice) {
|
foreach ($invoices as $invoice) {
|
||||||
if ($reminder = $account->getInvoiceReminder($invoice)) {
|
if ($reminder = $account->getInvoiceReminder($invoice)) {
|
||||||
$this->info('Send email: ' . $invoice->id);
|
$this->info('Send email: ' . $invoice->id);
|
||||||
@ -106,15 +132,34 @@ class SendReminders extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->info('Done');
|
private function sendScheduledReports()
|
||||||
|
{
|
||||||
|
$scheduledReports = ScheduledReport::where('send_date', '<=', date('Y-m-d'))
|
||||||
|
->with('user', 'account.company')
|
||||||
|
->get();
|
||||||
|
$this->info(count($scheduledReports) . ' scheduled reports');
|
||||||
|
|
||||||
if ($errorEmail = env('ERROR_EMAIL')) {
|
foreach ($scheduledReports as $scheduledReport) {
|
||||||
\Mail::raw('EOM', function ($message) use ($errorEmail, $database) {
|
$user = $scheduledReport->user;
|
||||||
$message->to($errorEmail)
|
$account = $scheduledReport->account;
|
||||||
->from(CONTACT_EMAIL)
|
|
||||||
->subject("SendReminders [{$database}]: Finished successfully");
|
if (! $account->hasFeature(FEATURE_REPORTS)) {
|
||||||
});
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = (array) json_decode($scheduledReport->config);
|
||||||
|
$reportType = $config['report_type'];
|
||||||
|
|
||||||
|
$report = dispatch(new RunReport($scheduledReport->user, $reportType, $config, true));
|
||||||
|
$file = dispatch(new ExportReportResults($scheduledReport->user, $config['export_format'], $reportType, $report->exportParams));
|
||||||
|
|
||||||
|
if ($file) {
|
||||||
|
$this->userMailer->sendScheduledReport($scheduledReport, $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledReport->updateSendDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,11 +7,11 @@ use Symfony\Component\Console\Input\InputOption;
|
|||||||
use App\Models\AccountGateway;
|
use App\Models\AccountGateway;
|
||||||
use App\Models\BankAccount;
|
use App\Models\BankAccount;
|
||||||
use Artisan;
|
use Artisan;
|
||||||
use Crypt;
|
|
||||||
use Illuminate\Encryption\Encrypter;
|
use Illuminate\Encryption\Encrypter;
|
||||||
|
use Laravel\LegacyEncrypter\McryptEncrypter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class PruneData.
|
* Class UpdateKey
|
||||||
*/
|
*/
|
||||||
class UpdateKey extends Command
|
class UpdateKey extends Command
|
||||||
{
|
{
|
||||||
@ -34,16 +34,29 @@ class UpdateKey extends Command
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$legacy = false;
|
||||||
|
if ($this->option('legacy') == 'true') {
|
||||||
|
$legacy = new McryptEncrypter(env('APP_KEY'));
|
||||||
|
}
|
||||||
|
|
||||||
// load the current values
|
// load the current values
|
||||||
$gatewayConfigs = [];
|
$gatewayConfigs = [];
|
||||||
$bankUsernames = [];
|
$bankUsernames = [];
|
||||||
|
|
||||||
foreach (AccountGateway::all() as $gateway) {
|
foreach (AccountGateway::all() as $gateway) {
|
||||||
$gatewayConfigs[$gateway->id] = $gateway->getConfig();
|
if ($legacy) {
|
||||||
|
$gatewayConfigs[$gateway->id] = json_decode($legacy->decrypt($gateway->config));
|
||||||
|
} else {
|
||||||
|
$gatewayConfigs[$gateway->id] = $gateway->getConfig();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (BankAccount::all() as $bank) {
|
foreach (BankAccount::all() as $bank) {
|
||||||
$bankUsernames[$bank->id] = $bank->getUsername();
|
if ($legacy) {
|
||||||
|
$bankUsernames[$bank->id] = $legacy->decrypt($bank->username);
|
||||||
|
} else {
|
||||||
|
$bankUsernames[$bank->id] = $bank->getUsername();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we can write to the .env file
|
// check if we can write to the .env file
|
||||||
@ -57,7 +70,8 @@ class UpdateKey extends Command
|
|||||||
$key = str_random(32);
|
$key = str_random(32);
|
||||||
}
|
}
|
||||||
|
|
||||||
$crypt = new Encrypter($key, config('app.cipher'));
|
$cipher = $legacy ? 'AES-256-CBC' : config('app.cipher');
|
||||||
|
$crypt = new Encrypter($key, $cipher);
|
||||||
|
|
||||||
// update values using the new key/encrypter
|
// update values using the new key/encrypter
|
||||||
foreach (AccountGateway::all() as $gateway) {
|
foreach (AccountGateway::all() as $gateway) {
|
||||||
@ -72,11 +86,21 @@ class UpdateKey extends Command
|
|||||||
$bank->save();
|
$bank->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$message = date('r') . ' Successfully updated ';
|
||||||
if ($envWriteable) {
|
if ($envWriteable) {
|
||||||
$this->info(date('r') . ' Successfully update the key');
|
if ($legacy) {
|
||||||
|
$message .= 'the key, set the cipher in the .env file to AES-256-CBC';
|
||||||
|
} else {
|
||||||
|
$message .= 'the key';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->info(date('r') . ' Successfully update data, make sure to set the new app key: ' . $key);
|
if ($legacy) {
|
||||||
|
$message .= 'the data, make sure to set the new cipher/key: AES-256-CBC/' . $key;
|
||||||
|
} else {
|
||||||
|
$message .= 'the data, make sure to set the new key: ' . $key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
$this->info($message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,6 +116,8 @@ class UpdateKey extends Command
|
|||||||
*/
|
*/
|
||||||
protected function getOptions()
|
protected function getOptions()
|
||||||
{
|
{
|
||||||
return [];
|
return [
|
||||||
|
['legacy', null, InputOption::VALUE_OPTIONAL, 'Legacy', null],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
if (! defined('APP_NAME')) {
|
if (! defined('APP_NAME')) {
|
||||||
define('APP_NAME', env('APP_NAME', 'Invoice Ninja'));
|
define('APP_NAME', env('APP_NAME', 'Invoice Ninja'));
|
||||||
|
define('APP_DOMAIN', env('APP_DOMAIN', 'invoiceninja.com'));
|
||||||
define('CONTACT_EMAIL', env('MAIL_FROM_ADDRESS', env('MAIL_USERNAME')));
|
define('CONTACT_EMAIL', env('MAIL_FROM_ADDRESS', env('MAIL_USERNAME')));
|
||||||
define('CONTACT_NAME', env('MAIL_FROM_NAME'));
|
define('CONTACT_NAME', env('MAIL_FROM_NAME'));
|
||||||
define('SITE_URL', env('APP_URL'));
|
define('SITE_URL', env('APP_URL'));
|
||||||
@ -39,6 +40,7 @@ if (! defined('APP_NAME')) {
|
|||||||
define('ENTITY_PROJECT', 'project');
|
define('ENTITY_PROJECT', 'project');
|
||||||
define('ENTITY_RECURRING_EXPENSE', 'recurring_expense');
|
define('ENTITY_RECURRING_EXPENSE', 'recurring_expense');
|
||||||
define('ENTITY_CUSTOMER', 'customer');
|
define('ENTITY_CUSTOMER', 'customer');
|
||||||
|
define('ENTITY_SUBSCRIPTION', 'subscription');
|
||||||
|
|
||||||
define('INVOICE_TYPE_STANDARD', 1);
|
define('INVOICE_TYPE_STANDARD', 1);
|
||||||
define('INVOICE_TYPE_QUOTE', 2);
|
define('INVOICE_TYPE_QUOTE', 2);
|
||||||
@ -228,6 +230,11 @@ if (! defined('APP_NAME')) {
|
|||||||
define('FREQUENCY_SIX_MONTHS', 8);
|
define('FREQUENCY_SIX_MONTHS', 8);
|
||||||
define('FREQUENCY_ANNUALLY', 9);
|
define('FREQUENCY_ANNUALLY', 9);
|
||||||
|
|
||||||
|
define('REPORT_FREQUENCY_DAILY', 'daily');
|
||||||
|
define('REPORT_FREQUENCY_WEEKLY', 'weekly');
|
||||||
|
define('REPORT_FREQUENCY_BIWEEKLY', 'biweekly');
|
||||||
|
define('REPORT_FREQUENCY_MONTHLY', 'monthly');
|
||||||
|
|
||||||
define('SESSION_TIMEZONE', 'timezone');
|
define('SESSION_TIMEZONE', 'timezone');
|
||||||
define('SESSION_CURRENCY', 'currency');
|
define('SESSION_CURRENCY', 'currency');
|
||||||
define('SESSION_CURRENCY_DECORATOR', 'currency_decorator');
|
define('SESSION_CURRENCY_DECORATOR', 'currency_decorator');
|
||||||
@ -310,7 +317,7 @@ if (! defined('APP_NAME')) {
|
|||||||
define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com'));
|
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_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest'));
|
||||||
define('NINJA_DATE', '2000-01-01');
|
define('NINJA_DATE', '2000-01-01');
|
||||||
define('NINJA_VERSION', '3.9.2' . env('NINJA_VERSION_SUFFIX'));
|
define('NINJA_VERSION', '4.0.0' . env('NINJA_VERSION_SUFFIX'));
|
||||||
|
|
||||||
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
|
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'));
|
define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
|
||||||
@ -426,6 +433,7 @@ if (! defined('APP_NAME')) {
|
|||||||
define('GATEWAY_TYPE_SOFORT', 8);
|
define('GATEWAY_TYPE_SOFORT', 8);
|
||||||
define('GATEWAY_TYPE_SEPA', 9);
|
define('GATEWAY_TYPE_SEPA', 9);
|
||||||
define('GATEWAY_TYPE_GOCARDLESS', 10);
|
define('GATEWAY_TYPE_GOCARDLESS', 10);
|
||||||
|
define('GATEWAY_TYPE_APPLE_PAY', 11);
|
||||||
define('GATEWAY_TYPE_TOKEN', 'token');
|
define('GATEWAY_TYPE_TOKEN', 'token');
|
||||||
|
|
||||||
define('TEMPLATE_INVOICE', 'invoice');
|
define('TEMPLATE_INVOICE', 'invoice');
|
||||||
@ -451,6 +459,9 @@ if (! defined('APP_NAME')) {
|
|||||||
define('FILTER_INVOICE_DATE', 'invoice_date');
|
define('FILTER_INVOICE_DATE', 'invoice_date');
|
||||||
define('FILTER_PAYMENT_DATE', 'payment_date');
|
define('FILTER_PAYMENT_DATE', 'payment_date');
|
||||||
|
|
||||||
|
define('ADDRESS_BILLING', 'billing_address');
|
||||||
|
define('ADDRESS_SHIPPING', 'shipping_address');
|
||||||
|
|
||||||
define('SOCIAL_GOOGLE', 'Google');
|
define('SOCIAL_GOOGLE', 'Google');
|
||||||
define('SOCIAL_FACEBOOK', 'Facebook');
|
define('SOCIAL_FACEBOOK', 'Facebook');
|
||||||
define('SOCIAL_GITHUB', 'GitHub');
|
define('SOCIAL_GITHUB', 'GitHub');
|
||||||
|
@ -4,6 +4,7 @@ namespace App\Exceptions;
|
|||||||
|
|
||||||
use Crawler;
|
use Crawler;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Illuminate\Auth\AuthenticationException;
|
||||||
use Illuminate\Auth\Access\AuthorizationException;
|
use Illuminate\Auth\Access\AuthorizationException;
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
@ -28,7 +29,7 @@ class Handler extends ExceptionHandler
|
|||||||
*/
|
*/
|
||||||
protected $dontReport = [
|
protected $dontReport = [
|
||||||
TokenMismatchException::class,
|
TokenMismatchException::class,
|
||||||
//ModelNotFoundException::class,
|
ModelNotFoundException::class,
|
||||||
//AuthorizationException::class,
|
//AuthorizationException::class,
|
||||||
//HttpException::class,
|
//HttpException::class,
|
||||||
//ValidationException::class,
|
//ValidationException::class,
|
||||||
@ -150,4 +151,31 @@ class Handler extends ExceptionHandler
|
|||||||
return parent::render($request, $e);
|
return parent::render($request, $e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an authentication exception into an unauthenticated response.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Illuminate\Auth\AuthenticationException $exception
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
protected function unauthenticated($request, AuthenticationException $exception)
|
||||||
|
{
|
||||||
|
if ($request->expectsJson()) {
|
||||||
|
return response()->json(['error' => 'Unauthenticated.'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$guard = array_get($exception->guards(), 0);
|
||||||
|
|
||||||
|
switch ($guard) {
|
||||||
|
case 'client':
|
||||||
|
$url = '/client/login';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$url = '/login';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->guest($url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ use Auth;
|
|||||||
use Cache;
|
use Cache;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Response;
|
use Response;
|
||||||
use Socialite;
|
use Socialite;
|
||||||
use Utils;
|
use Utils;
|
||||||
@ -91,7 +92,7 @@ class AccountApiController extends BaseAPIController
|
|||||||
|
|
||||||
return $this->response($data);
|
return $this->response($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Request $request)
|
public function show(Request $request)
|
||||||
{
|
{
|
||||||
$account = Auth::user()->account;
|
$account = Auth::user()->account;
|
||||||
@ -118,7 +119,13 @@ class AccountApiController extends BaseAPIController
|
|||||||
|
|
||||||
public function getUserAccounts(Request $request)
|
public function getUserAccounts(Request $request)
|
||||||
{
|
{
|
||||||
return $this->processLogin($request);
|
$user = Auth::user();
|
||||||
|
|
||||||
|
$users = $this->accountRepo->findUsers($user, 'account.account_tokens');
|
||||||
|
$transformer = new UserAccountTransformer($user->account, $request->serializer, $request->token_name);
|
||||||
|
$data = $this->createCollection($users, $transformer, 'user_account');
|
||||||
|
|
||||||
|
return $this->response($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(UpdateAccountRequest $request)
|
public function update(UpdateAccountRequest $request)
|
||||||
@ -140,7 +147,7 @@ class AccountApiController extends BaseAPIController
|
|||||||
$devices = json_decode($account->devices, true);
|
$devices = json_decode($account->devices, true);
|
||||||
|
|
||||||
for ($x = 0; $x < count($devices); $x++) {
|
for ($x = 0; $x < count($devices); $x++) {
|
||||||
if ($devices[$x]['email'] == Auth::user()->username) {
|
if ($devices[$x]['email'] == $request->email) {
|
||||||
$devices[$x]['token'] = $request->token; //update
|
$devices[$x]['token'] = $request->token; //update
|
||||||
$devices[$x]['device'] = $request->device;
|
$devices[$x]['device'] = $request->device;
|
||||||
$account->devices = json_encode($devices);
|
$account->devices = json_encode($devices);
|
||||||
@ -171,6 +178,26 @@ class AccountApiController extends BaseAPIController
|
|||||||
return $this->response($newDevice);
|
return $this->response($newDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function removeDeviceToken(Request $request) {
|
||||||
|
|
||||||
|
$account = Auth::user()->account;
|
||||||
|
|
||||||
|
$devices = json_decode($account->devices, true);
|
||||||
|
|
||||||
|
foreach($devices as $key => $value)
|
||||||
|
{
|
||||||
|
|
||||||
|
if($request->token == $value['token'])
|
||||||
|
unset($devices[$key]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$account->devices = json_encode(array_values($devices));
|
||||||
|
$account->save();
|
||||||
|
|
||||||
|
return $this->response(['success']);
|
||||||
|
}
|
||||||
|
|
||||||
public function updatePushNotifications(Request $request)
|
public function updatePushNotifications(Request $request)
|
||||||
{
|
{
|
||||||
$account = Auth::user()->account;
|
$account = Auth::user()->account;
|
||||||
@ -220,4 +247,11 @@ class AccountApiController extends BaseAPIController
|
|||||||
return $this->errorResponse(['message' => 'Invalid credentials'], 401);
|
return $this->errorResponse(['message' => 'Invalid credentials'], 401);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function iosSubscriptionStatus() {
|
||||||
|
|
||||||
|
//stubbed for iOS callbacks
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -494,6 +494,8 @@ class AccountController extends BaseController
|
|||||||
'account' => Auth::user()->account,
|
'account' => Auth::user()->account,
|
||||||
'title' => trans('texts.tax_rates'),
|
'title' => trans('texts.tax_rates'),
|
||||||
'taxRates' => TaxRate::scope()->whereIsInclusive(false)->get(),
|
'taxRates' => TaxRate::scope()->whereIsInclusive(false)->get(),
|
||||||
|
'countInvoices' => Invoice::scope()->withTrashed()->count(),
|
||||||
|
'hasInclusiveTaxRates' => TaxRate::scope()->whereIsInclusive(true)->count() ? true : false,
|
||||||
];
|
];
|
||||||
|
|
||||||
return View::make('accounts.tax_rates', $data);
|
return View::make('accounts.tax_rates', $data);
|
||||||
@ -769,11 +771,20 @@ class AccountController extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function saveClientPortalSettings(SaveClientPortalSettings $request)
|
public function saveClientPortalSettings(SaveClientPortalSettings $request)
|
||||||
{
|
{
|
||||||
|
|
||||||
$account = $request->user()->account;
|
$account = $request->user()->account;
|
||||||
|
|
||||||
if($account->subdomain !== $request->subdomain)
|
// check subdomain is unique in the lookup tables
|
||||||
|
if (request()->subdomain) {
|
||||||
|
if (! \App\Models\LookupAccount::validateField('subdomain', request()->subdomain, $account)) {
|
||||||
|
return Redirect::to('settings/' . ACCOUNT_CLIENT_PORTAL)
|
||||||
|
->withError(trans('texts.subdomain_taken'))
|
||||||
|
->withInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($account->subdomain !== $request->subdomain) {
|
||||||
event(new SubdomainWasUpdated($account));
|
event(new SubdomainWasUpdated($account));
|
||||||
|
}
|
||||||
|
|
||||||
$account->fill($request->all());
|
$account->fill($request->all());
|
||||||
$account->client_view_css = $request->client_view_css;
|
$account->client_view_css = $request->client_view_css;
|
||||||
|
@ -17,6 +17,7 @@ use Utils;
|
|||||||
use Validator;
|
use Validator;
|
||||||
use View;
|
use View;
|
||||||
use WePay;
|
use WePay;
|
||||||
|
use File;
|
||||||
|
|
||||||
class AccountGatewayController extends BaseController
|
class AccountGatewayController extends BaseController
|
||||||
{
|
{
|
||||||
@ -119,9 +120,9 @@ class AccountGatewayController extends BaseController
|
|||||||
$creditCards = [];
|
$creditCards = [];
|
||||||
foreach ($creditCardsArray as $card => $name) {
|
foreach ($creditCardsArray as $card => $name) {
|
||||||
if ($selectedCards > 0 && ($selectedCards & $card) == $card) {
|
if ($selectedCards > 0 && ($selectedCards & $card) == $card) {
|
||||||
$creditCards[$name['text']] = ['value' => $card, 'data-imageUrl' => asset($name['card']), 'checked' => 'checked'];
|
$creditCards['<div>' . $name['text'] . '</div>'] = ['value' => $card, 'data-imageUrl' => asset($name['card']), 'checked' => 'checked'];
|
||||||
} else {
|
} else {
|
||||||
$creditCards[$name['text']] = ['value' => $card, 'data-imageUrl' => asset($name['card'])];
|
$creditCards['<div>' . $name['text'] . '</div>'] = ['value' => $card, 'data-imageUrl' => asset($name['card'])];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,6 +298,13 @@ class AccountGatewayController extends BaseController
|
|||||||
$config->enableSofort = boolval(Input::get('enable_sofort'));
|
$config->enableSofort = boolval(Input::get('enable_sofort'));
|
||||||
$config->enableSepa = boolval(Input::get('enable_sepa'));
|
$config->enableSepa = boolval(Input::get('enable_sepa'));
|
||||||
$config->enableBitcoin = boolval(Input::get('enable_bitcoin'));
|
$config->enableBitcoin = boolval(Input::get('enable_bitcoin'));
|
||||||
|
$config->enableApplePay = boolval(Input::get('enable_apple_pay'));
|
||||||
|
|
||||||
|
if ($config->enableApplePay && $uploadedFile = request()->file('apple_merchant_id')) {
|
||||||
|
$config->appleMerchantId = File::get($uploadedFile);
|
||||||
|
} elseif ($oldConfig && ! empty($oldConfig->appleMerchantId)) {
|
||||||
|
$config->appleMerchantId = $oldConfig->appleMerchantId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($gatewayId == GATEWAY_STRIPE || $gatewayId == GATEWAY_WEPAY) {
|
if ($gatewayId == GATEWAY_STRIPE || $gatewayId == GATEWAY_WEPAY) {
|
||||||
@ -316,6 +324,7 @@ class AccountGatewayController extends BaseController
|
|||||||
|
|
||||||
$accountGateway->accepted_credit_cards = $cardCount;
|
$accountGateway->accepted_credit_cards = $cardCount;
|
||||||
$accountGateway->show_address = Input::get('show_address') ? true : false;
|
$accountGateway->show_address = Input::get('show_address') ? true : false;
|
||||||
|
$accountGateway->show_shipping_address = Input::get('show_shipping_address') ? true : false;
|
||||||
$accountGateway->update_address = Input::get('update_address') ? true : false;
|
$accountGateway->update_address = Input::get('update_address') ? true : false;
|
||||||
$accountGateway->setConfig($config);
|
$accountGateway->setConfig($config);
|
||||||
|
|
||||||
|
@ -269,9 +269,21 @@ class AppController extends BaseController
|
|||||||
public function update()
|
public function update()
|
||||||
{
|
{
|
||||||
if (! Utils::isNinjaProd()) {
|
if (! Utils::isNinjaProd()) {
|
||||||
|
if ($password = env('UPDATE_SECRET')) {
|
||||||
|
if (! hash_equals($password, request('secret') ?: '')) {
|
||||||
|
abort(400, 'Invalid secret: /update?secret=<value>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
set_time_limit(60 * 5);
|
set_time_limit(60 * 5);
|
||||||
$this->checkInnoDB();
|
$this->checkInnoDB();
|
||||||
|
|
||||||
|
$cacheCompiled = base_path('bootstrap/cache/compiled.php');
|
||||||
|
if (file_exists($cacheCompiled)) { unlink ($cacheCompiled); }
|
||||||
|
$cacheServices = base_path('bootstrap/cache/services.json');
|
||||||
|
if (file_exists($cacheServices)) { unlink ($cacheServices); }
|
||||||
|
|
||||||
Artisan::call('clear-compiled');
|
Artisan::call('clear-compiled');
|
||||||
Artisan::call('cache:clear');
|
Artisan::call('cache:clear');
|
||||||
Artisan::call('debugbar:clear');
|
Artisan::call('debugbar:clear');
|
||||||
|
@ -2,42 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Events\UserLoggedIn;
|
use Illuminate\Http\Request;
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Ninja\Repositories\AccountRepository;
|
use App\Ninja\Repositories\AccountRepository;
|
||||||
use App\Services\AuthService;
|
use App\Services\AuthService;
|
||||||
use Auth;
|
use App\Http\Controllers\Controller;
|
||||||
use Event;
|
|
||||||
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Lang;
|
|
||||||
use Session;
|
|
||||||
use Utils;
|
|
||||||
use Cache;
|
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
|
||||||
use App\Http\Requests\ValidateTwoFactorRequest;
|
|
||||||
|
|
||||||
class AuthController extends Controller
|
class AuthController extends Controller
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Registration & Login Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller handles the registration of new users, as well as the
|
|
||||||
| authentication of existing users. By default, this controller uses
|
|
||||||
| a simple trait to add these behaviors. Why don't you explore it?
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use AuthenticatesAndRegistersUsers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $redirectTo = '/dashboard';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var AuthService
|
* @var AuthService
|
||||||
*/
|
*/
|
||||||
@ -63,43 +34,13 @@ class AuthController extends Controller
|
|||||||
$this->authService = $authService;
|
$this->authService = $authService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $data
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function validator(array $data)
|
|
||||||
{
|
|
||||||
return Validator::make($data, [
|
|
||||||
'name' => 'required|max:255',
|
|
||||||
'email' => 'required|email|max:255|unique:users',
|
|
||||||
'password' => 'required|confirmed|min:6',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new user instance after a valid registration.
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
*
|
|
||||||
* @return User
|
|
||||||
*/
|
|
||||||
public function create(array $data)
|
|
||||||
{
|
|
||||||
return User::create([
|
|
||||||
'name' => $data['name'],
|
|
||||||
'email' => $data['email'],
|
|
||||||
'password' => bcrypt($data['password']),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $provider
|
* @param $provider
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function authLogin($provider, Request $request)
|
public function oauthLogin($provider, Request $request)
|
||||||
{
|
{
|
||||||
return $this->authService->execute($provider, $request->has('code'));
|
return $this->authService->execute($provider, $request->has('code'));
|
||||||
}
|
}
|
||||||
@ -107,161 +48,12 @@ class AuthController extends Controller
|
|||||||
/**
|
/**
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function authUnlink()
|
public function oauthUnlink()
|
||||||
{
|
{
|
||||||
$this->accountRepo->unlinkUserFromOauth(Auth::user());
|
$this->accountRepo->unlinkUserFromOauth(auth()->user());
|
||||||
|
|
||||||
Session::flash('message', trans('texts.updated_settings'));
|
session()->flash('message', trans('texts.updated_settings'));
|
||||||
|
|
||||||
return redirect()->to('/settings/' . ACCOUNT_USER_DETAILS);
|
return redirect()->to('/settings/' . ACCOUNT_USER_DETAILS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function getLoginWrapper()
|
|
||||||
{
|
|
||||||
if (auth()->check()) {
|
|
||||||
return redirect('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! Utils::isNinja() && ! User::count()) {
|
|
||||||
return redirect()->to('/setup');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Utils::isNinja() && ! Utils::isTravis()) {
|
|
||||||
// make sure the user is on SITE_URL/login to ensure OAuth works
|
|
||||||
$requestURL = request()->url();
|
|
||||||
$loginURL = SITE_URL . '/login';
|
|
||||||
$subdomain = Utils::getSubdomain(request()->url());
|
|
||||||
if ($requestURL != $loginURL && ! strstr($subdomain, 'webapp-')) {
|
|
||||||
return redirect()->to($loginURL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::getLogin();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Request $request
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function postLoginWrapper(Request $request)
|
|
||||||
{
|
|
||||||
$userId = Auth::check() ? Auth::user()->id : null;
|
|
||||||
$user = User::where('email', '=', $request->input('email'))->first();
|
|
||||||
|
|
||||||
if ($user && $user->failed_logins >= MAX_FAILED_LOGINS) {
|
|
||||||
Session::flash('error', trans('texts.invalid_credentials'));
|
|
||||||
return redirect()->to('login');
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = self::postLogin($request);
|
|
||||||
|
|
||||||
if (Auth::check()) {
|
|
||||||
/*
|
|
||||||
$users = false;
|
|
||||||
// we're linking a new account
|
|
||||||
if ($request->link_accounts && $userId && Auth::user()->id != $userId) {
|
|
||||||
$users = $this->accountRepo->associateAccounts($userId, Auth::user()->id);
|
|
||||||
Session::flash('message', trans('texts.associated_accounts'));
|
|
||||||
// check if other accounts are linked
|
|
||||||
} else {
|
|
||||||
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
} elseif ($user) {
|
|
||||||
error_log('login failed');
|
|
||||||
$user->failed_logins = $user->failed_logins + 1;
|
|
||||||
$user->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the post-authentication response.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
private function authenticated(Request $request, Authenticatable $user)
|
|
||||||
{
|
|
||||||
if ($user->google_2fa_secret) {
|
|
||||||
Auth::logout();
|
|
||||||
$request->session()->put('2fa:user:id', $user->id);
|
|
||||||
return redirect('/validate_two_factor/' . $user->account->account_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
Event::fire(new UserLoggedIn());
|
|
||||||
|
|
||||||
return redirect()->intended($this->redirectTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function getValidateToken()
|
|
||||||
{
|
|
||||||
if (session('2fa:user:id')) {
|
|
||||||
return view('auth.two_factor');
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect('login');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param App\Http\Requests\ValidateSecretRequest $request
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function postValidateToken(ValidateTwoFactorRequest $request)
|
|
||||||
{
|
|
||||||
//get user id and create cache key
|
|
||||||
$userId = $request->session()->pull('2fa:user:id');
|
|
||||||
$key = $userId . ':' . $request->totp;
|
|
||||||
|
|
||||||
//use cache to store token to blacklist
|
|
||||||
Cache::add($key, true, 4);
|
|
||||||
|
|
||||||
//login and redirect user
|
|
||||||
Auth::loginUsingId($userId);
|
|
||||||
Event::fire(new UserLoggedIn());
|
|
||||||
|
|
||||||
return redirect()->intended($this->redirectTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function getLogoutWrapper()
|
|
||||||
{
|
|
||||||
if (Auth::check() && ! Auth::user()->registered) {
|
|
||||||
if (request()->force_logout) {
|
|
||||||
$account = Auth::user()->account;
|
|
||||||
$this->accountRepo->unlinkAccount($account);
|
|
||||||
|
|
||||||
if (! $account->hasMultipleAccounts()) {
|
|
||||||
$account->company->forceDelete();
|
|
||||||
}
|
|
||||||
$account->forceDelete();
|
|
||||||
} else {
|
|
||||||
return redirect('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = self::getLogout();
|
|
||||||
|
|
||||||
Session::flush();
|
|
||||||
|
|
||||||
$reason = htmlentities(request()->reason);
|
|
||||||
if (!empty($reason) && Lang::has("texts.{$reason}_logout")) {
|
|
||||||
Session::flash('warning', trans("texts.{$reason}_logout"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
32
app/Http/Controllers/Auth/ForgotPasswordController.php
Normal file
32
app/Http/Controllers/Auth/ForgotPasswordController.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||||
|
|
||||||
|
class ForgotPasswordController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Password Reset Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller is responsible for handling password reset emails and
|
||||||
|
| includes a trait which assists in sending these notifications from
|
||||||
|
| your application to your users. Feel free to explore this trait.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use SendsPasswordResetEmails;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('guest');
|
||||||
|
}
|
||||||
|
}
|
214
app/Http/Controllers/Auth/LoginController.php
Normal file
214
app/Http/Controllers/Auth/LoginController.php
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use Utils;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
use Event;
|
||||||
|
use Cache;
|
||||||
|
use Lang;
|
||||||
|
use App\Events\UserLoggedIn;
|
||||||
|
use App\Http\Requests\ValidateTwoFactorRequest;
|
||||||
|
|
||||||
|
class LoginController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Login Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller handles authenticating users for the application and
|
||||||
|
| redirecting them to your home screen. The controller uses a trait
|
||||||
|
| to conveniently provide its functionality to your applications.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after login.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = '/dashboard';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('guest', ['except' => 'getLogoutWrapper']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function getLoginWrapper(Request $request)
|
||||||
|
{
|
||||||
|
if (auth()->check()) {
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! Utils::isNinja() && ! User::count()) {
|
||||||
|
return redirect()->to('/setup');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Utils::isNinja() && ! Utils::isTravis()) {
|
||||||
|
// make sure the user is on SITE_URL/login to ensure OAuth works
|
||||||
|
$requestURL = request()->url();
|
||||||
|
$loginURL = SITE_URL . '/login';
|
||||||
|
$subdomain = Utils::getSubdomain(request()->url());
|
||||||
|
if ($requestURL != $loginURL && ! strstr($subdomain, 'webapp-')) {
|
||||||
|
return redirect()->to($loginURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::showLoginForm($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function postLoginWrapper(Request $request)
|
||||||
|
{
|
||||||
|
$userId = auth()->check() ? auth()->user()->id : null;
|
||||||
|
$user = User::where('email', '=', $request->input('email'))->first();
|
||||||
|
|
||||||
|
if ($user && $user->failed_logins >= MAX_FAILED_LOGINS) {
|
||||||
|
session()->flash('error', trans('texts.invalid_credentials'));
|
||||||
|
return redirect()->to('login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = self::login($request);
|
||||||
|
|
||||||
|
if (auth()->check()) {
|
||||||
|
/*
|
||||||
|
$users = false;
|
||||||
|
// we're linking a new account
|
||||||
|
if ($request->link_accounts && $userId && Auth::user()->id != $userId) {
|
||||||
|
$users = $this->accountRepo->associateAccounts($userId, Auth::user()->id);
|
||||||
|
Session::flash('message', trans('texts.associated_accounts'));
|
||||||
|
// check if other accounts are linked
|
||||||
|
} else {
|
||||||
|
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
$stacktrace = sprintf("%s %s %s %s\n", date('Y-m-d h:i:s'), $request->input('email'), \Request::getClientIp(), array_get($_SERVER, 'HTTP_USER_AGENT'));
|
||||||
|
file_put_contents(storage_path('logs/failed-logins.log'), $stacktrace, FILE_APPEND);
|
||||||
|
error_log('login failed');
|
||||||
|
if ($user) {
|
||||||
|
$user->failed_logins = $user->failed_logins + 1;
|
||||||
|
$user->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the failed login response instance.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
protected function sendFailedLoginResponse(Request $request)
|
||||||
|
{
|
||||||
|
return redirect()->back()
|
||||||
|
->withInput($request->only($this->username(), 'remember'))
|
||||||
|
->withErrors([
|
||||||
|
$this->username() => trans('texts.invalid_credentials'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the post-authentication response.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
private function authenticated(Request $request, Authenticatable $user)
|
||||||
|
{
|
||||||
|
if ($user->google_2fa_secret) {
|
||||||
|
auth()->logout();
|
||||||
|
session()->put('2fa:user:id', $user->id);
|
||||||
|
return redirect('/validate_two_factor/' . $user->account->account_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::fire(new UserLoggedIn());
|
||||||
|
|
||||||
|
return redirect()->intended($this->redirectTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function getValidateToken()
|
||||||
|
{
|
||||||
|
if (session('2fa:user:id')) {
|
||||||
|
return view('auth.two_factor');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect('login');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param App\Http\Requests\ValidateSecretRequest $request
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function postValidateToken(ValidateTwoFactorRequest $request)
|
||||||
|
{
|
||||||
|
//get user id and create cache key
|
||||||
|
$userId = session()->pull('2fa:user:id');
|
||||||
|
$key = $userId . ':' . $request->totp;
|
||||||
|
|
||||||
|
//use cache to store token to blacklist
|
||||||
|
Cache::add($key, true, 4);
|
||||||
|
|
||||||
|
//login and redirect user
|
||||||
|
auth()->loginUsingId($userId);
|
||||||
|
Event::fire(new UserLoggedIn());
|
||||||
|
|
||||||
|
return redirect()->intended($this->redirectTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function getLogoutWrapper(Request $request)
|
||||||
|
{
|
||||||
|
if (auth()->check() && ! auth()->user()->registered) {
|
||||||
|
if (request()->force_logout) {
|
||||||
|
$account = auth()->user()->account;
|
||||||
|
app('App\Ninja\Repositories\AccountRepository')->unlinkAccount($account);
|
||||||
|
|
||||||
|
if (! $account->hasMultipleAccounts()) {
|
||||||
|
$account->company->forceDelete();
|
||||||
|
}
|
||||||
|
$account->forceDelete();
|
||||||
|
} else {
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = self::logout($request);
|
||||||
|
|
||||||
|
$reason = htmlentities(request()->reason);
|
||||||
|
if (!empty($reason) && Lang::has("texts.{$reason}_logout")) {
|
||||||
|
session()->flash('warning', trans("texts.{$reason}_logout"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
@ -3,11 +3,13 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use Event;
|
use Event;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\PasswordReset;
|
||||||
use App\Events\UserLoggedIn;
|
use App\Events\UserLoggedIn;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||||
|
|
||||||
class PasswordController extends Controller
|
class ResetPasswordController extends Controller
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -21,40 +23,27 @@ class PasswordController extends Controller
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use ResetsPasswords {
|
use ResetsPasswords {
|
||||||
getResetSuccessResponse as protected traitGetResetSuccessResponse;
|
sendResetResponse as protected traitSendResetResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Where to redirect users after resetting their password.
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $redirectTo = '/dashboard';
|
protected $redirectTo = '/dashboard';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new password controller instance.
|
* Create a new controller instance.
|
||||||
*
|
*
|
||||||
* @internal param \Illuminate\Contracts\Auth\Guard $auth
|
* @return void
|
||||||
* @internal param \Illuminate\Contracts\Auth\PasswordBroker $passwords
|
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('guest');
|
$this->middleware('guest');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function sendResetResponse($response)
|
||||||
* Display the form to request a password reset link.
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function getEmailWrapper()
|
|
||||||
{
|
|
||||||
if (auth()->check()) {
|
|
||||||
return redirect('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->getEmail();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getResetSuccessResponse($response)
|
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
|
|
||||||
@ -64,7 +53,20 @@ class PasswordController extends Controller
|
|||||||
return redirect('/validate_two_factor/' . $user->account->account_key);
|
return redirect('/validate_two_factor/' . $user->account->account_key);
|
||||||
} else {
|
} else {
|
||||||
Event::fire(new UserLoggedIn());
|
Event::fire(new UserLoggedIn());
|
||||||
return $this->traitGetResetSuccessResponse($response);
|
return $this->traitSendResetResponse($response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function showResetForm(Request $request, $token = null)
|
||||||
|
{
|
||||||
|
$passwordReset = PasswordReset::whereToken($token)->first();
|
||||||
|
|
||||||
|
if (! $passwordReset) {
|
||||||
|
return redirect('login')->withMessage(trans('texts.invalid_code'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('auth.passwords.reset')->with(
|
||||||
|
['token' => $token, 'email' => $passwordReset->email]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,82 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\ClientAuth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Contact;
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Session;
|
|
||||||
|
|
||||||
class AuthController extends Controller
|
|
||||||
{
|
|
||||||
use AuthenticatesUsers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $guard = 'client';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $redirectTo = '/client/dashboard';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function showLoginForm()
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'clientauth' => true,
|
|
||||||
];
|
|
||||||
|
|
||||||
return view('clientauth.login')->with($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the needed authorization credentials from the request.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function getCredentials(Request $request)
|
|
||||||
{
|
|
||||||
$credentials = $request->only('password');
|
|
||||||
$credentials['id'] = null;
|
|
||||||
|
|
||||||
$contactKey = session('contact_key');
|
|
||||||
if ($contactKey) {
|
|
||||||
$contact = Contact::where('contact_key', '=', $contactKey)->first();
|
|
||||||
if ($contact && ! $contact->is_deleted) {
|
|
||||||
$credentials['id'] = $contact->id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $credentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the user login request.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function validateLogin(Request $request)
|
|
||||||
{
|
|
||||||
$this->validate($request, [
|
|
||||||
'password' => 'required',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function getSessionExpired()
|
|
||||||
{
|
|
||||||
return view('clientauth.sessionexpired')->with(['clientauth' => true]);
|
|
||||||
}
|
|
||||||
}
|
|
86
app/Http/Controllers/ClientAuth/ForgotPasswordController.php
Normal file
86
app/Http/Controllers/ClientAuth/ForgotPasswordController.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\ClientAuth;
|
||||||
|
|
||||||
|
use Password;
|
||||||
|
use Config;
|
||||||
|
use App\Models\Contact;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||||
|
|
||||||
|
class ForgotPasswordController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Password Reset Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller is responsible for handling password reset emails and
|
||||||
|
| includes a trait which assists in sending these notifications from
|
||||||
|
| your application to your users. Feel free to explore this trait.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use SendsPasswordResetEmails;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('guest:client');
|
||||||
|
|
||||||
|
//Config::set('auth.defaults.passwords', 'client');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function showLinkRequestForm()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'clientauth' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! session('contact_key')) {
|
||||||
|
return \Redirect::to('/client/session_expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('clientauth.passwords.email')->with($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a reset link to the given user.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function sendResetLinkEmail(Request $request)
|
||||||
|
{
|
||||||
|
$contactId = null;
|
||||||
|
$contactKey = session('contact_key');
|
||||||
|
if ($contactKey) {
|
||||||
|
$contact = Contact::where('contact_key', '=', $contactKey)->first();
|
||||||
|
if ($contact && ! $contact->is_deleted && $contact->email) {
|
||||||
|
$contactId = $contact->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->broker()->sendResetLink(['id' => $contactId], function (Message $message) {
|
||||||
|
$message->subject($this->getEmailSubject());
|
||||||
|
});
|
||||||
|
|
||||||
|
return $response == Password::RESET_LINK_SENT
|
||||||
|
? $this->sendResetLinkResponse($response)
|
||||||
|
: $this->sendResetLinkFailedResponse($request, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function broker()
|
||||||
|
{
|
||||||
|
return Password::broker('clients');
|
||||||
|
}
|
||||||
|
}
|
171
app/Http/Controllers/ClientAuth/LoginController.php
Normal file
171
app/Http/Controllers/ClientAuth/LoginController.php
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\ClientAuth;
|
||||||
|
|
||||||
|
use Utils;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Contact;
|
||||||
|
use App\Models\Account;
|
||||||
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
|
||||||
|
class LoginController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Login Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller handles authenticating users for the application and
|
||||||
|
| redirecting them to your home screen. The controller uses a trait
|
||||||
|
| to conveniently provide its functionality to your applications.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after login.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = '/client/dashboard';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('guest:client', ['except' => 'logout']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the guard to be used during authentication.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\Auth\StatefulGuard
|
||||||
|
*/
|
||||||
|
protected function guard()
|
||||||
|
{
|
||||||
|
return auth()->guard('client');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function showLoginForm()
|
||||||
|
{
|
||||||
|
$subdomain = Utils::getSubdomain(\Request::server('HTTP_HOST'));
|
||||||
|
$hasAccountIndentifier = request()->account_key || ($subdomain && $subdomain != 'app');
|
||||||
|
|
||||||
|
if (! session('contact_key')) {
|
||||||
|
if (Utils::isNinja()) {
|
||||||
|
if (! $hasAccountIndentifier) {
|
||||||
|
return redirect('/client/session_expired');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (! $hasAccountIndentifier && Account::count() > 1) {
|
||||||
|
return redirect('/client/session_expired');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('clientauth.login')->with(['clientauth' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the needed authorization credentials from the request.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function credentials(Request $request)
|
||||||
|
{
|
||||||
|
if ($contactKey = session('contact_key')) {
|
||||||
|
$credentials = $request->only('password');
|
||||||
|
$credentials['contact_key'] = $contactKey;
|
||||||
|
} else {
|
||||||
|
$credentials = $request->only('email', 'password');
|
||||||
|
$account = false;
|
||||||
|
|
||||||
|
// resovle the email to a contact/account
|
||||||
|
if ($accountKey = request()->account_key) {
|
||||||
|
$account = Account::whereAccountKey($accountKey)->first();
|
||||||
|
} else {
|
||||||
|
$subdomain = Utils::getSubdomain(\Request::server('HTTP_HOST'));
|
||||||
|
if ($subdomain != 'app') {
|
||||||
|
$account = Account::whereSubdomain($subdomain)->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($account) {
|
||||||
|
$credentials['account_id'] = $account->id;
|
||||||
|
} else {
|
||||||
|
abort(500, 'Account not resolved in client login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the post-authentication response.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
private function authenticated(Request $request, Authenticatable $contact)
|
||||||
|
{
|
||||||
|
session(['contact_key' => $contact->contact_key]);
|
||||||
|
|
||||||
|
return redirect()->intended($this->redirectPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the failed login response instance.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
protected function sendFailedLoginResponse(Request $request)
|
||||||
|
{
|
||||||
|
return redirect()->back()
|
||||||
|
->withInput($request->only($this->username(), 'remember'))
|
||||||
|
->withErrors([
|
||||||
|
$this->username() => trans('texts.invalid_credentials'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the user login request - don't require the email
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function validateLogin(Request $request)
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'password' => 'required',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! session('contact_key')) {
|
||||||
|
$rules['email'] = 'required|email';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->validate($request, $rules);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getSessionExpired()
|
||||||
|
{
|
||||||
|
return view('clientauth.sessionexpired')->with(['clientauth' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -13,86 +13,6 @@ use Illuminate\Support\Facades\Password;
|
|||||||
|
|
||||||
class PasswordController extends Controller
|
class PasswordController extends Controller
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Password Reset Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller is responsible for handling password reset requests
|
|
||||||
| and uses a simple trait to include this behavior. You're free to
|
|
||||||
| explore this trait and override any methods you wish to tweak.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use ResetsPasswords;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $redirectTo = '/client/dashboard';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new password controller instance.
|
|
||||||
*
|
|
||||||
* @internal param \Illuminate\Contracts\Auth\Guard $auth
|
|
||||||
* @internal param \Illuminate\Contracts\Auth\PasswordBroker $passwords
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->middleware('guest');
|
|
||||||
Config::set('auth.defaults.passwords', 'client');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
|
||||||
*/
|
|
||||||
public function showLinkRequestForm()
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'clientauth' => true,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (! session('contact_key')) {
|
|
||||||
return \Redirect::to('/client/sessionexpired');
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('clientauth.password')->with($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a reset link to the given user.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function sendResetLinkEmail(Request $request)
|
|
||||||
{
|
|
||||||
$broker = $this->getBroker();
|
|
||||||
|
|
||||||
$contactId = null;
|
|
||||||
$contactKey = session('contact_key');
|
|
||||||
if ($contactKey) {
|
|
||||||
$contact = Contact::where('contact_key', '=', $contactKey)->first();
|
|
||||||
if ($contact && ! $contact->is_deleted && $contact->email) {
|
|
||||||
$contactId = $contact->id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = Password::broker($broker)->sendResetLink(['id' => $contactId], function (Message $message) {
|
|
||||||
$message->subject($this->getEmailSubject());
|
|
||||||
});
|
|
||||||
|
|
||||||
switch ($response) {
|
|
||||||
case Password::RESET_LINK_SENT:
|
|
||||||
return $this->getSendResetLinkEmailSuccessResponse($response);
|
|
||||||
|
|
||||||
case Password::INVALID_USER:
|
|
||||||
default:
|
|
||||||
return $this->getSendResetLinkEmailFailureResponse($response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the password reset view for the given token.
|
* Display the password reset view for the given token.
|
||||||
*
|
*
|
||||||
@ -116,7 +36,7 @@ class PasswordController extends Controller
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (! session('contact_key')) {
|
if (! session('contact_key')) {
|
||||||
return \Redirect::to('/client/sessionexpired');
|
return \Redirect::to('/client/session_expired');
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('clientauth.reset')->with($data);
|
return view('clientauth.reset')->with($data);
|
||||||
|
70
app/Http/Controllers/ClientAuth/ResetPasswordController.php
Normal file
70
app/Http/Controllers/ClientAuth/ResetPasswordController.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
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;
|
||||||
|
use App\Models\PasswordReset;
|
||||||
|
|
||||||
|
class ResetPasswordController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Password Reset Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller is responsible for handling password reset requests
|
||||||
|
| and uses a simple trait to include this behavior. You're free to
|
||||||
|
| explore this trait and override any methods you wish to tweak.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use ResetsPasswords;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after resetting their password.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = '/client/dashboard';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('guest:client');
|
||||||
|
|
||||||
|
//Config::set('auth.defaults.passwords', 'client');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function broker()
|
||||||
|
{
|
||||||
|
return Password::broker('clients');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function guard()
|
||||||
|
{
|
||||||
|
return auth()->guard('client');
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -14,6 +14,7 @@ use App\Ninja\Repositories\CreditRepository;
|
|||||||
use App\Ninja\Repositories\DocumentRepository;
|
use App\Ninja\Repositories\DocumentRepository;
|
||||||
use App\Ninja\Repositories\InvoiceRepository;
|
use App\Ninja\Repositories\InvoiceRepository;
|
||||||
use App\Ninja\Repositories\PaymentRepository;
|
use App\Ninja\Repositories\PaymentRepository;
|
||||||
|
use App\Ninja\Repositories\TaskRepository;
|
||||||
use App\Services\PaymentService;
|
use App\Services\PaymentService;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Barracuda\ArchiveStream\ZipArchive;
|
use Barracuda\ArchiveStream\ZipArchive;
|
||||||
@ -36,7 +37,14 @@ class ClientPortalController extends BaseController
|
|||||||
private $paymentRepo;
|
private $paymentRepo;
|
||||||
private $documentRepo;
|
private $documentRepo;
|
||||||
|
|
||||||
public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, DocumentRepository $documentRepo, PaymentService $paymentService, CreditRepository $creditRepo)
|
public function __construct(
|
||||||
|
InvoiceRepository $invoiceRepo,
|
||||||
|
PaymentRepository $paymentRepo,
|
||||||
|
ActivityRepository $activityRepo,
|
||||||
|
DocumentRepository $documentRepo,
|
||||||
|
PaymentService $paymentService,
|
||||||
|
CreditRepository $creditRepo,
|
||||||
|
TaskRepository $taskRepo)
|
||||||
{
|
{
|
||||||
$this->invoiceRepo = $invoiceRepo;
|
$this->invoiceRepo = $invoiceRepo;
|
||||||
$this->paymentRepo = $paymentRepo;
|
$this->paymentRepo = $paymentRepo;
|
||||||
@ -44,6 +52,7 @@ class ClientPortalController extends BaseController
|
|||||||
$this->documentRepo = $documentRepo;
|
$this->documentRepo = $documentRepo;
|
||||||
$this->paymentService = $paymentService;
|
$this->paymentService = $paymentService;
|
||||||
$this->creditRepo = $creditRepo;
|
$this->creditRepo = $creditRepo;
|
||||||
|
$this->taskRepo = $taskRepo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function view($invitationKey)
|
public function view($invitationKey)
|
||||||
@ -133,9 +142,6 @@ class ClientPortalController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$showApprove = $invoice->quote_invoice_id ? false : true;
|
$showApprove = $invoice->quote_invoice_id ? false : true;
|
||||||
if ($invoice->due_date) {
|
|
||||||
$showApprove = time() < strtotime($invoice->getOriginal('due_date'));
|
|
||||||
}
|
|
||||||
if ($invoice->invoice_status_id >= INVOICE_STATUS_APPROVED) {
|
if ($invoice->invoice_status_id >= INVOICE_STATUS_APPROVED) {
|
||||||
$showApprove = false;
|
$showApprove = false;
|
||||||
}
|
}
|
||||||
@ -556,6 +562,46 @@ class ClientPortalController extends BaseController
|
|||||||
return $this->creditRepo->getClientDatatable($contact->client_id);
|
return $this->creditRepo->getClientDatatable($contact->client_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function taskIndex()
|
||||||
|
{
|
||||||
|
if (! $contact = $this->getContact()) {
|
||||||
|
return $this->returnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
$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/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $account->enable_client_portal) {
|
||||||
|
return $this->returnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'color' => $color,
|
||||||
|
'account' => $account,
|
||||||
|
'title' => trans('texts.tasks'),
|
||||||
|
'entityType' => ENTITY_TASK,
|
||||||
|
'columns' => Utils::trans(['project', 'date', 'duration', 'description']),
|
||||||
|
'sortColumn' => 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->view('public_list', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function taskDatatable()
|
||||||
|
{
|
||||||
|
if (! $contact = $this->getContact()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->taskRepo->getClientDatatable($contact->client_id);
|
||||||
|
}
|
||||||
|
|
||||||
public function documentIndex()
|
public function documentIndex()
|
||||||
{
|
{
|
||||||
if (! $contact = $this->getContact()) {
|
if (! $contact = $this->getContact()) {
|
||||||
@ -601,7 +647,7 @@ class ClientPortalController extends BaseController
|
|||||||
return response()->view('error', [
|
return response()->view('error', [
|
||||||
'error' => $error ?: trans('texts.invoice_not_found'),
|
'error' => $error ?: trans('texts.invoice_not_found'),
|
||||||
'hideHeader' => true,
|
'hideHeader' => true,
|
||||||
'account' => $this->getContact()->account,
|
'account' => $this->getContact() ? $this->getContact()->account : false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +83,7 @@ class DashboardController extends BaseController
|
|||||||
'tasks' => $tasks,
|
'tasks' => $tasks,
|
||||||
'showBlueVinePromo' => $showBlueVinePromo,
|
'showBlueVinePromo' => $showBlueVinePromo,
|
||||||
'showWhiteLabelExpired' => $showWhiteLabelExpired,
|
'showWhiteLabelExpired' => $showWhiteLabelExpired,
|
||||||
|
'showExpenses' => count($expenses) && $account->isModuleEnabled(ENTITY_EXPENSE),
|
||||||
'headerClass' => in_array(\App::getLocale(), ['lt', 'pl', 'cs', 'sl', 'tr_TR']) ? 'in-large' : 'in-thin',
|
'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',
|
'footerClass' => in_array(\App::getLocale(), ['lt', 'pl', 'cs', 'sl', 'tr_TR']) ? '' : 'in-thin',
|
||||||
];
|
];
|
||||||
|
@ -170,7 +170,7 @@ class ExportController extends BaseController
|
|||||||
|
|
||||||
if ($request->input('include') === 'all' || $request->input('clients')) {
|
if ($request->input('include') === 'all' || $request->input('clients')) {
|
||||||
$data['clients'] = Client::scope()
|
$data['clients'] = Client::scope()
|
||||||
->with('user', 'contacts', 'country', 'currency')
|
->with('user', 'contacts', 'country', 'currency', 'shipping_country')
|
||||||
->withArchived()
|
->withArchived()
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
@ -24,15 +24,8 @@ class IntegrationController extends Controller
|
|||||||
return Response::json('Event is invalid', 500);
|
return Response::json('Event is invalid', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$subscription = Subscription::where('account_id', '=', Auth::user()->account_id)
|
$subscription = Subscription::createNew();
|
||||||
->where('event_id', '=', $eventId)->first();
|
$subscription->event_id = $eventId;
|
||||||
|
|
||||||
if (! $subscription) {
|
|
||||||
$subscription = new Subscription();
|
|
||||||
$subscription->account_id = Auth::user()->account_id;
|
|
||||||
$subscription->event_id = $eventId;
|
|
||||||
}
|
|
||||||
|
|
||||||
$subscription->target_url = trim(Input::get('target_url'));
|
$subscription->target_url = trim(Input::get('target_url'));
|
||||||
$subscription->save();
|
$subscription->save();
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ class InvoiceController extends BaseController
|
|||||||
->where('invitations.invoice_id', '=', $invoice->id)
|
->where('invitations.invoice_id', '=', $invoice->id)
|
||||||
->where('invitations.account_id', '=', Auth::user()->account_id)
|
->where('invitations.account_id', '=', Auth::user()->account_id)
|
||||||
->where('invitations.deleted_at', '=', null)
|
->where('invitations.deleted_at', '=', null)
|
||||||
->select('contacts.public_id')->lists('public_id');
|
->select('contacts.public_id')->pluck('public_id');
|
||||||
|
|
||||||
$clients = Client::scope()->withTrashed()->with('contacts', 'country');
|
$clients = Client::scope()->withTrashed()->with('contacts', 'country');
|
||||||
|
|
||||||
@ -590,6 +590,28 @@ class InvoiceController extends BaseController
|
|||||||
return View::make('invoices.history', $data);
|
return View::make('invoices.history', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deliveryNote(InvoiceRequest $request)
|
||||||
|
{
|
||||||
|
$invoice = $request->entity();
|
||||||
|
$invoice->load('user', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'account.country', 'client.contacts', 'client.country', 'client.shipping_country');
|
||||||
|
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
||||||
|
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
|
||||||
|
$invoice->features = [
|
||||||
|
'customize_invoice_design' => Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
|
||||||
|
'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY),
|
||||||
|
'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS),
|
||||||
|
];
|
||||||
|
$invoice->invoice_type_id = intval($invoice->invoice_type_id);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'invoice' => $invoice,
|
||||||
|
'invoiceDesigns' => InvoiceDesign::getDesigns(),
|
||||||
|
'invoiceFonts' => Cache::get('fonts'),
|
||||||
|
];
|
||||||
|
|
||||||
|
return View::make('invoices.delivery_note', $data);
|
||||||
|
}
|
||||||
|
|
||||||
public function checkInvoiceNumber($invoicePublicId = false)
|
public function checkInvoiceNumber($invoicePublicId = false)
|
||||||
{
|
{
|
||||||
$invoiceNumber = request()->invoice_number;
|
$invoiceNumber = request()->invoice_number;
|
||||||
|
@ -114,10 +114,16 @@ class OnlinePaymentController extends BaseController
|
|||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function doPayment(CreateOnlinePaymentRequest $request)
|
public function doPayment(CreateOnlinePaymentRequest $request, $invitationKey, $gatewayTypeAlias = false)
|
||||||
{
|
{
|
||||||
$invitation = $request->invitation;
|
$invitation = $request->invitation;
|
||||||
$gatewayTypeId = Session::get($invitation->id . 'gateway_type');
|
|
||||||
|
if ($gatewayTypeAlias) {
|
||||||
|
$gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias);
|
||||||
|
} else {
|
||||||
|
$gatewayTypeId = Session::get($invitation->id . 'gateway_type');
|
||||||
|
}
|
||||||
|
|
||||||
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayTypeId);
|
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayTypeId);
|
||||||
|
|
||||||
if (! $invitation->invoice->canBePaid() && ! request()->update) {
|
if (! $invitation->invoice->canBePaid() && ! request()->update) {
|
||||||
@ -184,7 +190,9 @@ class OnlinePaymentController extends BaseController
|
|||||||
|
|
||||||
private function completePurchase($invitation, $isOffsite = false)
|
private function completePurchase($invitation, $isOffsite = false)
|
||||||
{
|
{
|
||||||
if ($redirectUrl = session('redirect_url:' . $invitation->invitation_key)) {
|
if (request()->wantsJson()) {
|
||||||
|
return response()->json(RESULT_SUCCESS);
|
||||||
|
} elseif ($redirectUrl = session('redirect_url:' . $invitation->invitation_key)) {
|
||||||
$separator = strpos($redirectUrl, '?') === false ? '?' : '&';
|
$separator = strpos($redirectUrl, '?') === false ? '?' : '&';
|
||||||
|
|
||||||
return redirect()->to($redirectUrl . $separator . 'invoice_id=' . $invitation->invoice->public_id);
|
return redirect()->to($redirectUrl . $separator . 'invoice_id=' . $invitation->invoice->public_id);
|
||||||
@ -412,4 +420,28 @@ class OnlinePaymentController extends BaseController
|
|||||||
return redirect()->to($link);
|
return redirect()->to($link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function showAppleMerchantId()
|
||||||
|
{
|
||||||
|
if (Utils::isNinja()) {
|
||||||
|
$subdomain = Utils::getSubdomain(\Request::server('HTTP_HOST'));
|
||||||
|
$account = Account::whereSubdomain($subdomain)->first();
|
||||||
|
} else {
|
||||||
|
$account = Account::first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $account) {
|
||||||
|
exit("Account not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
$accountGateway = $account->account_gateways()
|
||||||
|
->whereGatewayId(GATEWAY_STRIPE)->first();
|
||||||
|
|
||||||
|
if (! $account) {
|
||||||
|
exit("Apple merchant id not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $accountGateway->getConfigField('appleMerchantId');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use App\Http\Requests\CreateProjectRequest;
|
|||||||
use App\Http\Requests\ProjectRequest;
|
use App\Http\Requests\ProjectRequest;
|
||||||
use App\Http\Requests\UpdateProjectRequest;
|
use App\Http\Requests\UpdateProjectRequest;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
|
use App\Models\Project;
|
||||||
use App\Ninja\Datatables\ProjectDatatable;
|
use App\Ninja\Datatables\ProjectDatatable;
|
||||||
use App\Ninja\Repositories\ProjectRepository;
|
use App\Ninja\Repositories\ProjectRepository;
|
||||||
use App\Services\ProjectService;
|
use App\Services\ProjectService;
|
||||||
@ -95,6 +96,11 @@ class ProjectController extends BaseController
|
|||||||
|
|
||||||
Session::flash('message', trans('texts.updated_project'));
|
Session::flash('message', trans('texts.updated_project'));
|
||||||
|
|
||||||
|
$action = Input::get('action');
|
||||||
|
if (in_array($action, ['archive', 'delete', 'restore', 'invoice'])) {
|
||||||
|
return self::bulk();
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->to($project->getRoute());
|
return redirect()->to($project->getRoute());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,14 +108,51 @@ class ProjectController extends BaseController
|
|||||||
{
|
{
|
||||||
$action = Input::get('action');
|
$action = Input::get('action');
|
||||||
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
||||||
$count = $this->projectService->bulk($ids, $action);
|
|
||||||
|
|
||||||
if ($count > 0) {
|
if ($action == 'invoice') {
|
||||||
$field = $count == 1 ? "{$action}d_project" : "{$action}d_projects";
|
$data = [];
|
||||||
$message = trans("texts.$field", ['count' => $count]);
|
$clientPublicId = false;
|
||||||
Session::flash('message', $message);
|
$lastClientId = false;
|
||||||
|
$lastProjectId = false;
|
||||||
|
$projects = Project::scope($ids)
|
||||||
|
->with(['client', 'tasks' => function ($query) {
|
||||||
|
$query->whereNull('invoice_id');
|
||||||
|
}])
|
||||||
|
->get();
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
if (! $clientPublicId) {
|
||||||
|
$clientPublicId = $project->client->public_id;
|
||||||
|
}
|
||||||
|
if ($lastClientId && $lastClientId != $project->client_id) {
|
||||||
|
return redirect('projects')->withError(trans('texts.project_error_multiple_clients'));
|
||||||
|
}
|
||||||
|
$lastClientId = $project->client_id;
|
||||||
|
|
||||||
|
foreach ($project->tasks as $task) {
|
||||||
|
if ($task->is_running) {
|
||||||
|
return redirect('projects')->withError(trans('texts.task_error_running'));
|
||||||
|
}
|
||||||
|
$showProject = $lastProjectId != $task->project_id;
|
||||||
|
$data[] = [
|
||||||
|
'publicId' => $task->public_id,
|
||||||
|
'description' => $task->present()->invoiceDescription(auth()->user()->account, $showProject),
|
||||||
|
'duration' => $task->getHours(),
|
||||||
|
'cost' => $task->getRate(),
|
||||||
|
];
|
||||||
|
$lastProjectId = $task->project_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redirect("invoices/create/{$clientPublicId}")->with('tasks', $data);
|
||||||
|
} else {
|
||||||
|
$count = $this->projectService->bulk($ids, $action);
|
||||||
|
|
||||||
|
if ($count > 0) {
|
||||||
|
$field = $count == 1 ? "{$action}d_project" : "{$action}d_projects";
|
||||||
|
$message = trans("texts.$field", ['count' => $count]);
|
||||||
|
Session::flash('message', $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->to('/projects');
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->to('/projects');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,6 +149,13 @@ class QuoteController extends BaseController
|
|||||||
$invitation = Invitation::with('invoice.invoice_items', 'invoice.invitations')->where('invitation_key', '=', $invitationKey)->firstOrFail();
|
$invitation = Invitation::with('invoice.invoice_items', 'invoice.invitations')->where('invitation_key', '=', $invitationKey)->firstOrFail();
|
||||||
$invoice = $invitation->invoice;
|
$invoice = $invitation->invoice;
|
||||||
|
|
||||||
|
if ($invoice->due_date) {
|
||||||
|
$carbonDueDate = \Carbon::parse($invoice->due_date);
|
||||||
|
if (! $carbonDueDate->isToday() && ! $carbonDueDate->isFuture()) {
|
||||||
|
return redirect("view/{$invitationKey}")->withError(trans('texts.quote_has_expired'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$invitationKey = $this->invoiceService->approveQuote($invoice, $invitation);
|
$invitationKey = $this->invoiceService->approveQuote($invoice, $invitation);
|
||||||
Session::flash('message', trans('texts.quote_is_approved'));
|
Session::flash('message', trans('texts.quote_is_approved'));
|
||||||
|
|
||||||
|
@ -2,13 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Jobs\ExportReportResults;
|
||||||
|
use App\Jobs\RunReport;
|
||||||
use App\Models\Account;
|
use App\Models\Account;
|
||||||
|
use App\Models\ScheduledReport;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Input;
|
use Input;
|
||||||
use Str;
|
|
||||||
use Utils;
|
use Utils;
|
||||||
use View;
|
use View;
|
||||||
use Excel;
|
use Carbon;
|
||||||
|
use Validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ReportController.
|
* Class ReportController.
|
||||||
@ -94,22 +97,30 @@ class ReportController extends BaseController
|
|||||||
|
|
||||||
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
|
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
|
||||||
$isExport = $action == 'export';
|
$isExport = $action == 'export';
|
||||||
$reportClass = '\\App\\Ninja\\Reports\\' . Str::studly($reportType) . 'Report';
|
$config = [
|
||||||
$options = [
|
|
||||||
'date_field' => $dateField,
|
'date_field' => $dateField,
|
||||||
'invoice_status' => request()->invoice_status,
|
'status_ids' => request()->status_ids,
|
||||||
'group_dates_by' => request()->group_dates_by,
|
'group_dates_by' => request()->group_dates_by,
|
||||||
'document_filter' => request()->document_filter,
|
'document_filter' => request()->document_filter,
|
||||||
|
'currency_type' => request()->currency_type,
|
||||||
'export_format' => $format,
|
'export_format' => $format,
|
||||||
|
'start_date' => $params['startDate'],
|
||||||
|
'end_date' => $params['endDate'],
|
||||||
];
|
];
|
||||||
$report = new $reportClass($startDate, $endDate, $isExport, $options);
|
$report = dispatch(new RunReport(auth()->user(), $reportType, $config, $isExport));
|
||||||
if (Input::get('report_type')) {
|
$params = array_merge($params, $report->exportParams);
|
||||||
$report->run();
|
switch ($action) {
|
||||||
}
|
case 'export':
|
||||||
$params['report'] = $report;
|
return dispatch(new ExportReportResults(auth()->user(), $format, $reportType, $params))->export($format);
|
||||||
$params = array_merge($params, $report->results());
|
break;
|
||||||
if ($isExport) {
|
case 'schedule':
|
||||||
return self::export($format, $reportType, $params);
|
self::schedule($params, $config);
|
||||||
|
return redirect('/reports');
|
||||||
|
break;
|
||||||
|
case 'cancel_schedule':
|
||||||
|
self::cancelSchdule();
|
||||||
|
return redirect('/reports');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$params['columns'] = [];
|
$params['columns'] = [];
|
||||||
@ -118,112 +129,47 @@ class ReportController extends BaseController
|
|||||||
$params['report'] = false;
|
$params['report'] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return View::make('reports.chart_builder', $params);
|
$params['scheduledReports'] = ScheduledReport::scope()->whereUserId(auth()->user()->id)->get();
|
||||||
|
|
||||||
|
return View::make('reports.report_builder', $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function schedule($params, $options)
|
||||||
* @param $format
|
|
||||||
* @param $reportType
|
|
||||||
* @param $params
|
|
||||||
* @todo: Add summary to export
|
|
||||||
*/
|
|
||||||
private function export($format, $reportType, $params)
|
|
||||||
{
|
{
|
||||||
if (! Auth::user()->hasPermission('view_all')) {
|
$validator = Validator::make(request()->all(), [
|
||||||
exit;
|
'frequency' => 'required|in:daily,weekly,biweekly,monthly',
|
||||||
|
'send_date' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
session()->now('message', trans('texts.scheduled_report_error'));
|
||||||
|
} else {
|
||||||
|
$options['report_type'] = $params['reportType'];
|
||||||
|
$options['range'] = request('range');
|
||||||
|
$options['start_date_offset'] = $options['range'] ? '' : Carbon::parse($params['startDate'])->diffInDays(null, false); // null,false to get the relative/non-absolute diff
|
||||||
|
$options['end_date_offset'] = $options['range'] ? '' : Carbon::parse($params['endDate'])->diffInDays(null, false);
|
||||||
|
|
||||||
|
unset($options['start_date']);
|
||||||
|
unset($options['end_date']);
|
||||||
|
unset($options['group_dates_by']);
|
||||||
|
|
||||||
|
$schedule = ScheduledReport::createNew();
|
||||||
|
$schedule->config = json_encode($options);
|
||||||
|
$schedule->frequency = request('frequency');
|
||||||
|
$schedule->send_date = Utils::toSqlDate(request('send_date'));
|
||||||
|
$schedule->save();
|
||||||
|
|
||||||
|
session()->flash('message', trans('texts.created_scheduled_report'));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$format = strtolower($format);
|
private function cancelSchdule()
|
||||||
$data = $params['displayData'];
|
{
|
||||||
$columns = $params['columns'];
|
ScheduledReport::scope()
|
||||||
$totals = $params['reportTotals'];
|
->whereUserId(auth()->user()->id)
|
||||||
$report = $params['report'];
|
->wherePublicId(request('scheduled_report_id'))
|
||||||
|
->delete();
|
||||||
|
|
||||||
$filename = "{$params['startDate']}-{$params['endDate']}_invoiceninja-".strtolower(Utils::normalizeChars(trans("texts.$reportType")))."-report";
|
session()->flash('message', trans('texts.deleted_scheduled_report'));
|
||||||
|
|
||||||
$formats = ['csv', 'pdf', 'xlsx', 'zip'];
|
|
||||||
if (! in_array($format, $formats)) {
|
|
||||||
throw new \Exception("Invalid format request to export report");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Get labeled header
|
|
||||||
$data = array_merge(
|
|
||||||
[
|
|
||||||
array_map(function($col) {
|
|
||||||
return $col['label'];
|
|
||||||
}, $report->tableHeaderArray())
|
|
||||||
],
|
|
||||||
$data
|
|
||||||
);
|
|
||||||
|
|
||||||
$summary = [];
|
|
||||||
if (count(array_values($totals))) {
|
|
||||||
$summary[] = array_merge([
|
|
||||||
trans("texts.totals")
|
|
||||||
], array_map(function ($key) {
|
|
||||||
return trans("texts.{$key}");
|
|
||||||
}, array_keys(array_values(array_values($totals)[0])[0])));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($totals as $currencyId => $each) {
|
|
||||||
foreach ($each as $dimension => $val) {
|
|
||||||
$tmp = [];
|
|
||||||
$tmp[] = Utils::getFromCache($currencyId, 'currencies')->name . (($dimension) ? ' - ' . $dimension : '');
|
|
||||||
foreach ($val as $id => $field) {
|
|
||||||
$tmp[] = Utils::formatMoney($field, $currencyId);
|
|
||||||
}
|
|
||||||
$summary[] = $tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Excel::create($filename, function($excel) use($report, $data, $reportType, $format, $summary) {
|
|
||||||
|
|
||||||
$excel->sheet(trans("texts.$reportType"), function($sheet) use($report, $data, $format, $summary) {
|
|
||||||
$sheet->setOrientation('landscape');
|
|
||||||
$sheet->freezeFirstRow();
|
|
||||||
if ($format == 'pdf') {
|
|
||||||
$sheet->setAllBorders('thin');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($format == 'csv') {
|
|
||||||
$sheet->rows(array_merge($data, [[]], $summary));
|
|
||||||
} else {
|
|
||||||
$sheet->rows($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Styling header
|
|
||||||
$sheet->cells('A1:'.Utils::num2alpha(count($data[0])-1).'1', function($cells) {
|
|
||||||
$cells->setBackground('#777777');
|
|
||||||
$cells->setFontColor('#FFFFFF');
|
|
||||||
$cells->setFontSize(13);
|
|
||||||
$cells->setFontFamily('Calibri');
|
|
||||||
$cells->setFontWeight('bold');
|
|
||||||
});
|
|
||||||
$sheet->setAutoSize(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (count($summary)) {
|
|
||||||
$excel->sheet(trans("texts.totals"), function($sheet) use($report, $summary, $format) {
|
|
||||||
$sheet->setOrientation('landscape');
|
|
||||||
$sheet->freezeFirstRow();
|
|
||||||
|
|
||||||
if ($format == 'pdf') {
|
|
||||||
$sheet->setAllBorders('thin');
|
|
||||||
}
|
|
||||||
$sheet->rows($summary);
|
|
||||||
|
|
||||||
// Styling header
|
|
||||||
$sheet->cells('A1:'.Utils::num2alpha(count($summary[0])-1).'1', function($cells) {
|
|
||||||
$cells->setBackground('#777777');
|
|
||||||
$cells->setFontColor('#FFFFFF');
|
|
||||||
$cells->setFontSize(13);
|
|
||||||
$cells->setFontFamily('Calibri');
|
|
||||||
$cells->setFontWeight('bold');
|
|
||||||
});
|
|
||||||
$sheet->setAutoSize(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
})->export($format);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
159
app/Http/Controllers/SubscriptionController.php
Normal file
159
app/Http/Controllers/SubscriptionController.php
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use App\Services\SubscriptionService;
|
||||||
|
use Auth;
|
||||||
|
use Input;
|
||||||
|
use Redirect;
|
||||||
|
use Session;
|
||||||
|
use URL;
|
||||||
|
use Validator;
|
||||||
|
use View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SubscriptionController.
|
||||||
|
*/
|
||||||
|
class SubscriptionController extends BaseController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var SubscriptionService
|
||||||
|
*/
|
||||||
|
protected $subscriptionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SubscriptionController constructor.
|
||||||
|
*
|
||||||
|
* @param SubscriptionService $subscriptionService
|
||||||
|
*/
|
||||||
|
public function __construct(SubscriptionService $subscriptionService)
|
||||||
|
{
|
||||||
|
//parent::__construct();
|
||||||
|
|
||||||
|
$this->subscriptionService = $subscriptionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return Redirect::to('settings/' . ACCOUNT_API_TOKENS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function getDatatable()
|
||||||
|
{
|
||||||
|
return $this->subscriptionService->getDatatable(Auth::user()->account_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $publicId
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function edit($publicId)
|
||||||
|
{
|
||||||
|
$subscription = Subscription::scope($publicId)->firstOrFail();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'subscription' => $subscription,
|
||||||
|
'method' => 'PUT',
|
||||||
|
'url' => 'subscriptions/' . $publicId,
|
||||||
|
'title' => trans('texts.edit_subscription'),
|
||||||
|
];
|
||||||
|
|
||||||
|
return View::make('accounts.subscription', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $publicId
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function update($publicId)
|
||||||
|
{
|
||||||
|
return $this->save($publicId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function store()
|
||||||
|
{
|
||||||
|
return $this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'subscription' => null,
|
||||||
|
'method' => 'POST',
|
||||||
|
'url' => 'subscriptions',
|
||||||
|
'title' => trans('texts.add_subscription'),
|
||||||
|
];
|
||||||
|
|
||||||
|
return View::make('accounts.subscription', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function bulk()
|
||||||
|
{
|
||||||
|
$action = Input::get('bulk_action');
|
||||||
|
$ids = Input::get('bulk_public_id');
|
||||||
|
|
||||||
|
$count = $this->subscriptionService->bulk($ids, $action);
|
||||||
|
|
||||||
|
Session::flash('message', trans('texts.archived_subscription'));
|
||||||
|
|
||||||
|
return Redirect::to('settings/' . ACCOUNT_API_TOKENS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $subscriptionPublicId
|
||||||
|
*
|
||||||
|
* @return $this|\Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function save($subscriptionPublicId = false)
|
||||||
|
{
|
||||||
|
if (Auth::user()->account->hasFeature(FEATURE_API)) {
|
||||||
|
$rules = [
|
||||||
|
'event_id' => 'required',
|
||||||
|
'target_url' => 'required|url',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($subscriptionPublicId) {
|
||||||
|
$subscription = Subscription::scope($subscriptionPublicId)->firstOrFail();
|
||||||
|
} else {
|
||||||
|
$subscription = Subscription::createNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = Validator::make(Input::all(), $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return Redirect::to($subscriptionPublicId ? 'subscriptions/edit' : 'subscriptions/create')->withInput()->withErrors($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$subscription->fill(request()->all());
|
||||||
|
$subscription->save();
|
||||||
|
|
||||||
|
if ($subscriptionPublicId) {
|
||||||
|
$message = trans('texts.updated_subscription');
|
||||||
|
} else {
|
||||||
|
$message = trans('texts.created_subscription');
|
||||||
|
}
|
||||||
|
|
||||||
|
Session::flash('message', $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Redirect::to('settings/' . ACCOUNT_API_TOKENS);
|
||||||
|
}
|
||||||
|
}
|
@ -308,7 +308,6 @@ class TaskController extends BaseController
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$count = $this->taskService->bulk($ids, $action);
|
$count = $this->taskService->bulk($ids, $action);
|
||||||
|
|
||||||
if (request()->wantsJson()) {
|
if (request()->wantsJson()) {
|
||||||
return response()->json($count);
|
return response()->json($count);
|
||||||
} else {
|
} else {
|
||||||
|
@ -9,33 +9,57 @@ class Kernel extends HttpKernel
|
|||||||
/**
|
/**
|
||||||
* The application's global HTTP middleware stack.
|
* The application's global HTTP middleware stack.
|
||||||
*
|
*
|
||||||
|
* These middleware are run during every request to your application.
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $middleware = [
|
protected $middleware = [
|
||||||
'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',
|
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
|
||||||
'Illuminate\Cookie\Middleware\EncryptCookies',
|
];
|
||||||
'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
|
|
||||||
'Illuminate\Session\Middleware\StartSession',
|
/**
|
||||||
'Illuminate\View\Middleware\ShareErrorsFromSession',
|
* The application's route middleware groups.
|
||||||
'App\Http\Middleware\VerifyCsrfToken',
|
*
|
||||||
'App\Http\Middleware\DuplicateSubmissionCheck',
|
* @var array
|
||||||
'App\Http\Middleware\QueryLogging',
|
*/
|
||||||
'App\Http\Middleware\StartupCheck',
|
protected $middlewareGroups = [
|
||||||
|
'web' => [
|
||||||
|
\App\Http\Middleware\EncryptCookies::class,
|
||||||
|
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
|
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
|
//\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
\App\Http\Middleware\DuplicateSubmissionCheck::class,
|
||||||
|
\App\Http\Middleware\QueryLogging::class,
|
||||||
|
\App\Http\Middleware\StartupCheck::class,
|
||||||
|
],
|
||||||
|
'api' => [
|
||||||
|
\App\Http\Middleware\ApiCheck::class,
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
'api' => [
|
||||||
|
'throttle:60,1',
|
||||||
|
'bindings',
|
||||||
|
],
|
||||||
|
*/
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The application's route middleware.
|
* The application's route middleware.
|
||||||
*
|
*
|
||||||
|
* These middleware may be assigned to groups or used individually.
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $routeMiddleware = [
|
protected $routeMiddleware = [
|
||||||
'lookup' => 'App\Http\Middleware\DatabaseLookup',
|
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||||
'auth' => 'App\Http\Middleware\Authenticate',
|
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||||
'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
|
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
'permissions.required' => 'App\Http\Middleware\PermissionsRequired',
|
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||||
'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
|
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||||
'api' => 'App\Http\Middleware\ApiCheck',
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
'cors' => '\Barryvdh\Cors\HandleCors',
|
'lookup' => \App\Http\Middleware\DatabaseLookup::class,
|
||||||
'throttle' => 'Illuminate\Routing\Middleware\ThrottleRequests',
|
'permissions.required' => \App\Http\Middleware\PermissionsRequired::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,8 @@ class ApiCheck
|
|||||||
{
|
{
|
||||||
$loggingIn = $request->is('api/v1/login')
|
$loggingIn = $request->is('api/v1/login')
|
||||||
|| $request->is('api/v1/register')
|
|| $request->is('api/v1/register')
|
||||||
|| $request->is('api/v1/oauth_login');
|
|| $request->is('api/v1/oauth_login')
|
||||||
|
|| $request->is('api/v1/ios_subscription_status');
|
||||||
|
|
||||||
$headers = Utils::getApiHeaders();
|
$headers = Utils::getApiHeaders();
|
||||||
$hasApiSecret = false;
|
$hasApiSecret = false;
|
||||||
|
@ -64,8 +64,9 @@ class Authenticate
|
|||||||
Session::put('contact_key', $contact->contact_key);
|
Session::put('contact_key', $contact->contact_key);
|
||||||
}
|
}
|
||||||
if (! $contact) {
|
if (! $contact) {
|
||||||
return \Redirect::to('client/sessionexpired');
|
return \Redirect::to('client/session_expired');
|
||||||
}
|
}
|
||||||
|
|
||||||
$account = $contact->account;
|
$account = $contact->account;
|
||||||
|
|
||||||
if (Auth::guard('user')->check() && Auth::user('user')->account_id == $account->id) {
|
if (Auth::guard('user')->check() && Auth::user('user')->account_id == $account->id) {
|
||||||
@ -86,8 +87,8 @@ class Authenticate
|
|||||||
$authenticated = true;
|
$authenticated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env('PHANTOMJS_SECRET') && $request->phantomjs_secret && hash_equals(env('PHANTOMJS_SECRET'), $request->phantomjs_secret)) {
|
if ($authenticated) {
|
||||||
$authenticated = true;
|
$request->merge(['contact' => $contact]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ use App\Models\LookupInvitation;
|
|||||||
use App\Models\LookupAccountToken;
|
use App\Models\LookupAccountToken;
|
||||||
use App\Models\LookupUser;
|
use App\Models\LookupUser;
|
||||||
use Auth;
|
use Auth;
|
||||||
|
use Utils;
|
||||||
|
|
||||||
class DatabaseLookup
|
class DatabaseLookup
|
||||||
{
|
{
|
||||||
@ -44,6 +45,13 @@ class DatabaseLookup
|
|||||||
LookupInvitation::setServerByField('invitation_key', $key);
|
LookupInvitation::setServerByField('invitation_key', $key);
|
||||||
} elseif ($key = request()->contact_key ?: session('contact_key')) {
|
} elseif ($key = request()->contact_key ?: session('contact_key')) {
|
||||||
LookupContact::setServerByField('contact_key', $key);
|
LookupContact::setServerByField('contact_key', $key);
|
||||||
|
} elseif ($key = request()->account_key) {
|
||||||
|
LookupAccount::setServerByField('account_key', $key);
|
||||||
|
} else {
|
||||||
|
$subdomain = Utils::getSubdomain(\Request::server('HTTP_HOST'));
|
||||||
|
if ($subdomain != 'app') {
|
||||||
|
LookupAccount::setServerByField('subdomain', $subdomain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} elseif ($guard == 'postmark') {
|
} elseif ($guard == 'postmark') {
|
||||||
LookupInvitation::setServerByField('message_id', request()->MessageID);
|
LookupInvitation::setServerByField('message_id', request()->MessageID);
|
||||||
|
17
app/Http/Middleware/EncryptCookies.php
Normal file
17
app/Http/Middleware/EncryptCookies.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies as BaseEncrypter;
|
||||||
|
|
||||||
|
class EncryptCookies extends BaseEncrypter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The names of the cookies that should not be encrypted.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
@ -39,12 +39,21 @@ class RedirectIfAuthenticated
|
|||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next, $guard = null)
|
||||||
{
|
{
|
||||||
if ($this->auth->check() && Client::scope()->count() > 0) {
|
if (auth()->guard($guard)->check()) {
|
||||||
Session::reflash();
|
Session::reflash();
|
||||||
|
|
||||||
return new RedirectResponse(url('/dashboard'));
|
switch ($guard) {
|
||||||
|
case 'client':
|
||||||
|
if (session('contact_key')) {
|
||||||
|
return redirect('/client/dashboard');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return redirect('/dashboard');
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
@ -55,8 +55,8 @@ class StartupCheck
|
|||||||
$file = storage_path() . '/version.txt';
|
$file = storage_path() . '/version.txt';
|
||||||
$version = @file_get_contents($file);
|
$version = @file_get_contents($file);
|
||||||
if ($version != NINJA_VERSION) {
|
if ($version != NINJA_VERSION) {
|
||||||
if (version_compare(phpversion(), '5.5.9', '<')) {
|
if (version_compare(phpversion(), '7.0.0', '<')) {
|
||||||
dd('Please update PHP to >= 5.5.9');
|
dd('Please update PHP to >= 7.0.0');
|
||||||
}
|
}
|
||||||
$handle = fopen($file, 'w');
|
$handle = fopen($file, 'w');
|
||||||
fwrite($handle, NINJA_VERSION);
|
fwrite($handle, NINJA_VERSION);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
use App\Models\Invitation;
|
use App\Models\Invitation;
|
||||||
|
use App\Models\GatewayType;
|
||||||
|
|
||||||
class CreateOnlinePaymentRequest extends Request
|
class CreateOnlinePaymentRequest extends Request
|
||||||
{
|
{
|
||||||
@ -26,7 +27,7 @@ class CreateOnlinePaymentRequest extends Request
|
|||||||
$account = $this->invitation->account;
|
$account = $this->invitation->account;
|
||||||
|
|
||||||
$paymentDriver = $account->paymentDriver($this->invitation, $this->gateway_type);
|
$paymentDriver = $account->paymentDriver($this->invitation, $this->gateway_type);
|
||||||
|
|
||||||
return $paymentDriver->rules();
|
return $paymentDriver->rules();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +40,12 @@ class CreateOnlinePaymentRequest extends Request
|
|||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
$input['invitation'] = $invitation;
|
$input['invitation'] = $invitation;
|
||||||
$input['gateway_type'] = session($invitation->id . 'gateway_type');
|
|
||||||
|
if ($gatewayTypeAlias = request()->gateway_type) {
|
||||||
|
$input['gateway_type'] = GatewayType::getIdFromAlias($gatewayTypeAlias);
|
||||||
|
} else {
|
||||||
|
$input['gateway_type'] = session($invitation->id . 'gateway_type');
|
||||||
|
}
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ class CreateProjectRequest extends ProjectRequest
|
|||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => sprintf('required|unique:projects,name,,id,account_id,%s', $this->user()->account_id),
|
'name' => 'required',
|
||||||
'client_id' => 'required',
|
'client_id' => 'required',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ class UpdateProjectRequest extends ProjectRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'name' => sprintf('required|unique:projects,name,%s,id,account_id,%s', $this->entity()->id, $this->user()->account_id),
|
'name' => 'required',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
126
app/Jobs/ExportReportResults.php
Normal file
126
app/Jobs/ExportReportResults.php
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Utils;
|
||||||
|
use Excel;
|
||||||
|
use App\Jobs\Job;
|
||||||
|
|
||||||
|
class ExportReportResults extends Job
|
||||||
|
{
|
||||||
|
public function __construct($user, $format, $reportType, $params)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
$this->format = strtolower($format);
|
||||||
|
$this->reportType = $reportType;
|
||||||
|
$this->params = $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if (! $this->user->hasPermission('view_all')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$format = $this->format;
|
||||||
|
$reportType = $this->reportType;
|
||||||
|
$params = $this->params;
|
||||||
|
|
||||||
|
$data = $params['displayData'];
|
||||||
|
$columns = $params['columns'];
|
||||||
|
$totals = $params['reportTotals'];
|
||||||
|
$report = $params['report'];
|
||||||
|
|
||||||
|
$filename = "{$params['startDate']}-{$params['endDate']}_invoiceninja-".strtolower(Utils::normalizeChars(trans("texts.$reportType")))."-report";
|
||||||
|
|
||||||
|
$formats = ['csv', 'pdf', 'xlsx', 'zip'];
|
||||||
|
if (! in_array($format, $formats)) {
|
||||||
|
throw new \Exception("Invalid format request to export report");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get labeled header
|
||||||
|
$data = array_merge(
|
||||||
|
[
|
||||||
|
array_map(function($col) {
|
||||||
|
return $col['label'];
|
||||||
|
}, $report->tableHeaderArray())
|
||||||
|
],
|
||||||
|
$data
|
||||||
|
);
|
||||||
|
|
||||||
|
$summary = [];
|
||||||
|
if (count(array_values($totals))) {
|
||||||
|
$summary[] = array_merge([
|
||||||
|
trans("texts.totals")
|
||||||
|
], array_map(function ($key) {
|
||||||
|
return trans("texts.{$key}");
|
||||||
|
}, array_keys(array_values(array_values($totals)[0])[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($totals as $currencyId => $each) {
|
||||||
|
foreach ($each as $dimension => $val) {
|
||||||
|
$tmp = [];
|
||||||
|
$tmp[] = Utils::getFromCache($currencyId, 'currencies')->name . (($dimension) ? ' - ' . $dimension : '');
|
||||||
|
foreach ($val as $id => $field) {
|
||||||
|
$tmp[] = Utils::formatMoney($field, $currencyId);
|
||||||
|
}
|
||||||
|
$summary[] = $tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Excel::create($filename, function($excel) use($report, $data, $reportType, $format, $summary) {
|
||||||
|
|
||||||
|
$excel->sheet(trans("texts.$reportType"), function($sheet) use($report, $data, $format, $summary) {
|
||||||
|
$sheet->setOrientation('landscape');
|
||||||
|
$sheet->freezeFirstRow();
|
||||||
|
if ($format == 'pdf') {
|
||||||
|
$sheet->setAllBorders('thin');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($format == 'csv') {
|
||||||
|
$sheet->rows(array_merge($data, [[]], $summary));
|
||||||
|
} else {
|
||||||
|
$sheet->rows($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Styling header
|
||||||
|
$sheet->cells('A1:'.Utils::num2alpha(count($data[0])-1).'1', function($cells) {
|
||||||
|
$cells->setBackground('#777777');
|
||||||
|
$cells->setFontColor('#FFFFFF');
|
||||||
|
$cells->setFontSize(13);
|
||||||
|
$cells->setFontFamily('Calibri');
|
||||||
|
$cells->setFontWeight('bold');
|
||||||
|
});
|
||||||
|
$sheet->setAutoSize(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (count($summary)) {
|
||||||
|
$excel->sheet(trans("texts.totals"), function($sheet) use($report, $summary, $format) {
|
||||||
|
$sheet->setOrientation('landscape');
|
||||||
|
$sheet->freezeFirstRow();
|
||||||
|
|
||||||
|
if ($format == 'pdf') {
|
||||||
|
$sheet->setAllBorders('thin');
|
||||||
|
}
|
||||||
|
$sheet->rows($summary);
|
||||||
|
|
||||||
|
// Styling header
|
||||||
|
$sheet->cells('A1:'.Utils::num2alpha(count($summary[0])-1).'1', function($cells) {
|
||||||
|
$cells->setBackground('#777777');
|
||||||
|
$cells->setFontColor('#FFFFFF');
|
||||||
|
$cells->setFontSize(13);
|
||||||
|
$cells->setFontFamily('Calibri');
|
||||||
|
$cells->setFontWeight('bold');
|
||||||
|
});
|
||||||
|
$sheet->setAutoSize(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
86
app/Jobs/RunReport.php
Normal file
86
app/Jobs/RunReport.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App;
|
||||||
|
use Str;
|
||||||
|
use Utils;
|
||||||
|
use Carbon;
|
||||||
|
use App\Jobs\Job;
|
||||||
|
|
||||||
|
class RunReport extends Job
|
||||||
|
{
|
||||||
|
public function __construct($user, $reportType, $config, $isExport = false)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
$this->reportType = $reportType;
|
||||||
|
$this->config = $config;
|
||||||
|
$this->isExport = $isExport;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if (! $this->user->hasPermission('view_all')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$reportType = $this->reportType;
|
||||||
|
$config = $this->config;
|
||||||
|
$isExport = $this->isExport;
|
||||||
|
$reportClass = '\\App\\Ninja\\Reports\\' . Str::studly($reportType) . 'Report';
|
||||||
|
|
||||||
|
if (! empty($config['range'])) {
|
||||||
|
switch ($config['range']) {
|
||||||
|
case 'this_month':
|
||||||
|
$startDate = Carbon::now()->firstOfMonth()->toDateString();
|
||||||
|
$endDate = Carbon::now()->lastOfMonth()->toDateString();
|
||||||
|
break;
|
||||||
|
case 'last_month':
|
||||||
|
$startDate = Carbon::now()->subMonth()->firstOfMonth()->toDateString();
|
||||||
|
$endDate = Carbon::now()->subMonth()->lastOfMonth()->toDateString();
|
||||||
|
break;
|
||||||
|
case 'this_year':
|
||||||
|
$startDate = Carbon::now()->firstOfYear()->toDateString();
|
||||||
|
$endDate = Carbon::now()->lastOfYear()->toDateString();
|
||||||
|
break;
|
||||||
|
case 'last_year':
|
||||||
|
$startDate = Carbon::now()->subYear()->firstOfYear()->toDateString();
|
||||||
|
$endDate = Carbon::now()->subYear()->lastOfYear()->toDateString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} elseif (! empty($config['start_date_offset'])) {
|
||||||
|
$startDate = Carbon::now()->subDays($config['start_date_offset'])->toDateString();
|
||||||
|
$endDate = Carbon::now()->subDays($config['end_date_offset'])->toDateString();
|
||||||
|
} else {
|
||||||
|
$startDate = $config['start_date'];
|
||||||
|
$endDate = $config['end_date'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// send email as user
|
||||||
|
if (App::runningInConsole() && $this->user) {
|
||||||
|
auth()->onceUsingId($this->user->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$report = new $reportClass($startDate, $endDate, $isExport, $config);
|
||||||
|
$report->run();
|
||||||
|
|
||||||
|
if (App::runningInConsole() && $this->user) {
|
||||||
|
auth()->logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'startDate' => $startDate,
|
||||||
|
'endDate' => $endDate,
|
||||||
|
'report' => $report,
|
||||||
|
];
|
||||||
|
|
||||||
|
$report->exportParams = array_merge($params, $report->results());
|
||||||
|
|
||||||
|
return $report;
|
||||||
|
}
|
||||||
|
}
|
@ -108,6 +108,11 @@ class Utils
|
|||||||
return self::getResllerType() ? true : false;
|
return self::getResllerType() ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function isRootFolder()
|
||||||
|
{
|
||||||
|
return strlen(preg_replace('/[^\/]/', '', url('/'))) == 2;
|
||||||
|
}
|
||||||
|
|
||||||
public static function clientViewCSS()
|
public static function clientViewCSS()
|
||||||
{
|
{
|
||||||
$account = false;
|
$account = false;
|
||||||
@ -459,6 +464,11 @@ class Utils
|
|||||||
|
|
||||||
public static function parseFloat($value)
|
public static function parseFloat($value)
|
||||||
{
|
{
|
||||||
|
// check for comma as decimal separator
|
||||||
|
if (preg_match('/,[\d]{1,2}$/', $value)) {
|
||||||
|
$value = str_replace(',', '.', $value);
|
||||||
|
}
|
||||||
|
|
||||||
$value = preg_replace('/[^0-9\.\-]/', '', $value);
|
$value = preg_replace('/[^0-9\.\-]/', '', $value);
|
||||||
|
|
||||||
return floatval($value);
|
return floatval($value);
|
||||||
|
@ -101,6 +101,8 @@ class HandleUserLoggedIn
|
|||||||
// warn if using the default app key
|
// warn if using the default app key
|
||||||
if (in_array(config('app.key'), ['SomeRandomString', 'SomeRandomStringSomeRandomString', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'])) {
|
if (in_array(config('app.key'), ['SomeRandomString', 'SomeRandomStringSomeRandomString', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'])) {
|
||||||
Session::flash('error', trans('texts.error_app_key_set_to_default'));
|
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'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,21 +132,25 @@ class SubscriptionListener
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$subscription = $entity->account->getSubscription($eventId);
|
$subscriptions = $entity->account->getSubscriptions($eventId);
|
||||||
|
|
||||||
if ($subscription) {
|
if (! $subscriptions->count()) {
|
||||||
$manager = new Manager();
|
return;
|
||||||
$manager->setSerializer(new ArraySerializer());
|
}
|
||||||
$manager->parseIncludes($include);
|
|
||||||
|
|
||||||
$resource = new Item($entity, $transformer, $entity->getEntityType());
|
$manager = new Manager();
|
||||||
$data = $manager->createData($resource)->toArray();
|
$manager->setSerializer(new ArraySerializer());
|
||||||
|
$manager->parseIncludes($include);
|
||||||
|
|
||||||
// For legacy Zapier support
|
$resource = new Item($entity, $transformer, $entity->getEntityType());
|
||||||
if (isset($data['client_id'])) {
|
$data = $manager->createData($resource)->toArray();
|
||||||
$data['client_name'] = $entity->client->getDisplayName();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// For legacy Zapier support
|
||||||
|
if (isset($data['client_id'])) {
|
||||||
|
$data['client_name'] = $entity->client->getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($subscriptions as $subscription) {
|
||||||
Utils::notifyZapier($subscription, $data);
|
Utils::notifyZapier($subscription, $data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,6 +177,7 @@ class Account extends Eloquent
|
|||||||
'credit_number_prefix',
|
'credit_number_prefix',
|
||||||
'credit_number_pattern',
|
'credit_number_pattern',
|
||||||
'task_rate',
|
'task_rate',
|
||||||
|
'inclusive_taxes',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -216,7 +217,6 @@ class Account extends Eloquent
|
|||||||
ENTITY_QUOTE => 4,
|
ENTITY_QUOTE => 4,
|
||||||
ENTITY_TASK => 8,
|
ENTITY_TASK => 8,
|
||||||
ENTITY_EXPENSE => 16,
|
ENTITY_EXPENSE => 16,
|
||||||
ENTITY_VENDOR => 32,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public static $dashboardSections = [
|
public static $dashboardSections = [
|
||||||
@ -233,6 +233,7 @@ class Account extends Eloquent
|
|||||||
'due_date',
|
'due_date',
|
||||||
'hours',
|
'hours',
|
||||||
'id_number',
|
'id_number',
|
||||||
|
'invoice',
|
||||||
'item',
|
'item',
|
||||||
'line_total',
|
'line_total',
|
||||||
'outstanding',
|
'outstanding',
|
||||||
@ -240,6 +241,7 @@ class Account extends Eloquent
|
|||||||
'partial_due',
|
'partial_due',
|
||||||
'po_number',
|
'po_number',
|
||||||
'quantity',
|
'quantity',
|
||||||
|
'quote',
|
||||||
'rate',
|
'rate',
|
||||||
'service',
|
'service',
|
||||||
'subtotal',
|
'subtotal',
|
||||||
@ -1008,6 +1010,15 @@ class Account extends Eloquent
|
|||||||
$this->company->save();
|
$this->company->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasReminders()
|
||||||
|
{
|
||||||
|
if (! $this->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->enable_reminder1 || $this->enable_reminder2 || $this->enable_reminder3;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $feature
|
* @param $feature
|
||||||
*
|
*
|
||||||
@ -1293,9 +1304,9 @@ class Account extends Eloquent
|
|||||||
*
|
*
|
||||||
* @return \Illuminate\Database\Eloquent\Model|null|static
|
* @return \Illuminate\Database\Eloquent\Model|null|static
|
||||||
*/
|
*/
|
||||||
public function getSubscription($eventId)
|
public function getSubscriptions($eventId)
|
||||||
{
|
{
|
||||||
return Subscription::where('account_id', '=', $this->id)->where('event_id', '=', $eventId)->first();
|
return Subscription::where('account_id', '=', $this->id)->where('event_id', '=', $eventId)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1625,10 +1636,17 @@ class Account extends Eloquent
|
|||||||
ENTITY_TASK,
|
ENTITY_TASK,
|
||||||
ENTITY_EXPENSE,
|
ENTITY_EXPENSE,
|
||||||
ENTITY_VENDOR,
|
ENTITY_VENDOR,
|
||||||
|
ENTITY_PROJECT,
|
||||||
])) {
|
])) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($entityType == ENTITY_VENDOR) {
|
||||||
|
$entityType = ENTITY_EXPENSE;
|
||||||
|
} elseif ($entityType == ENTITY_PROJECT) {
|
||||||
|
$entityType = ENTITY_TASK;
|
||||||
|
}
|
||||||
|
|
||||||
// note: single & checks bitmask match
|
// note: single & checks bitmask match
|
||||||
return $this->enabled_modules & static::$modules[$entityType];
|
return $this->enabled_modules & static::$modules[$entityType];
|
||||||
}
|
}
|
||||||
@ -1692,6 +1710,11 @@ class Account extends Eloquent
|
|||||||
return $this->company->accounts->count() > 1;
|
return $this->company->accounts->count() > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPrimaryAccount()
|
||||||
|
{
|
||||||
|
return $this->company->accounts()->orderBy('id')->first();
|
||||||
|
}
|
||||||
|
|
||||||
public function financialYearStart()
|
public function financialYearStart()
|
||||||
{
|
{
|
||||||
if (! $this->financial_year_start) {
|
if (! $this->financial_year_start) {
|
||||||
@ -1712,6 +1735,29 @@ class Account extends Eloquent
|
|||||||
{
|
{
|
||||||
return $this->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $this->enable_portal_password;
|
return $this->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $this->enable_portal_password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getBaseUrl()
|
||||||
|
{
|
||||||
|
if ($this->hasFeature(FEATURE_CUSTOM_URL)) {
|
||||||
|
if ($this->iframe_url) {
|
||||||
|
return $this->iframe_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Utils::isNinjaProd() && ! Utils::isReseller()) {
|
||||||
|
$url = $this->present()->clientPortalLink();
|
||||||
|
} else {
|
||||||
|
$url = url('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->subdomain) {
|
||||||
|
$url = Utils::replaceSubdomain($url, $this->subdomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $url;
|
||||||
|
} else {
|
||||||
|
return url('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Account::creating(function ($account)
|
Account::creating(function ($account)
|
||||||
@ -1719,6 +1765,13 @@ Account::creating(function ($account)
|
|||||||
LookupAccount::createAccount($account->account_key, $account->company_id);
|
LookupAccount::createAccount($account->account_key, $account->company_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Account::updating(function ($account) {
|
||||||
|
$dirty = $account->getDirty();
|
||||||
|
if (array_key_exists('subdomain', $dirty)) {
|
||||||
|
LookupAccount::updateAccount($account->account_key, $account);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Account::updated(function ($account) {
|
Account::updated(function ($account) {
|
||||||
// prevent firing event if the invoice/quote counter was changed
|
// prevent firing event if the invoice/quote counter was changed
|
||||||
// TODO: remove once counters are moved to separate table
|
// TODO: remove once counters are moved to separate table
|
||||||
|
@ -136,6 +136,15 @@ class AccountGateway extends EntityModel
|
|||||||
return $this->getConfigField('publishableKey');
|
return $this->getConfigField('publishableKey');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAppleMerchantId()
|
||||||
|
{
|
||||||
|
if (! $this->isGateway(GATEWAY_STRIPE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getConfigField('appleMerchantId');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
@ -144,6 +153,14 @@ class AccountGateway extends EntityModel
|
|||||||
return ! empty($this->getConfigField('enableAch'));
|
return ! empty($this->getConfigField('enableAch'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getApplePayEnabled()
|
||||||
|
{
|
||||||
|
return ! empty($this->getConfigField('enableApplePay'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
|
@ -53,10 +53,16 @@ class Client extends EntityModel
|
|||||||
'quote_number_counter',
|
'quote_number_counter',
|
||||||
'public_notes',
|
'public_notes',
|
||||||
'task_rate',
|
'task_rate',
|
||||||
|
'shipping_address1',
|
||||||
|
'shipping_address2',
|
||||||
|
'shipping_city',
|
||||||
|
'shipping_state',
|
||||||
|
'shipping_postal_code',
|
||||||
|
'shipping_country_id',
|
||||||
|
'show_tasks_in_portal',
|
||||||
|
'send_reminders',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
@ -179,6 +185,14 @@ class Client extends EntityModel
|
|||||||
return $this->belongsTo('App\Models\Country');
|
return $this->belongsTo('App\Models\Country');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
*/
|
||||||
|
public function shipping_country()
|
||||||
|
{
|
||||||
|
return $this->belongsTo('App\Models\Country');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
*/
|
*/
|
||||||
@ -375,7 +389,7 @@ class Client extends EntityModel
|
|||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function hasAddress()
|
public function hasAddress($shipping = false)
|
||||||
{
|
{
|
||||||
$fields = [
|
$fields = [
|
||||||
'address1',
|
'address1',
|
||||||
@ -387,6 +401,9 @@ class Client extends EntityModel
|
|||||||
];
|
];
|
||||||
|
|
||||||
foreach ($fields as $field) {
|
foreach ($fields as $field) {
|
||||||
|
if ($shipping) {
|
||||||
|
$field = 'shipping_' . $field;
|
||||||
|
}
|
||||||
if ($this->$field) {
|
if ($this->$field) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -489,6 +506,20 @@ class Client extends EntityModel
|
|||||||
return $this->account->currency ? $this->account->currency->code : 'USD';
|
return $this->account->currency ? $this->account->currency->code : 'USD';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCountryCode()
|
||||||
|
{
|
||||||
|
if ($country = $this->country) {
|
||||||
|
return $country->iso_3166_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->account) {
|
||||||
|
$this->load('account');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->account->country ? $this->account->country->iso_3166_2 : 'US';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $isQuote
|
* @param $isQuote
|
||||||
*
|
*
|
||||||
|
@ -9,13 +9,20 @@ use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
|||||||
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
|
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use App\Models\LookupContact;
|
use App\Models\LookupContact;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Contact.
|
* Class Contact.
|
||||||
*/
|
*/
|
||||||
class Contact extends EntityModel implements AuthenticatableContract, CanResetPasswordContract
|
class Contact extends EntityModel implements AuthenticatableContract, CanResetPasswordContract
|
||||||
{
|
{
|
||||||
use SoftDeletes, Authenticatable, CanResetPassword;
|
use SoftDeletes;
|
||||||
|
use Authenticatable;
|
||||||
|
use CanResetPassword;
|
||||||
|
use Notifiable;
|
||||||
|
|
||||||
|
protected $guard = 'client';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@ -42,6 +49,17 @@ class Contact extends EntityModel implements AuthenticatableContract, CanResetPa
|
|||||||
'custom_value2',
|
'custom_value2',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes excluded from the model's JSON form.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
|
'remember_token',
|
||||||
|
'confirmation_code',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
@ -165,6 +183,12 @@ class Contact extends EntityModel implements AuthenticatableContract, CanResetPa
|
|||||||
|
|
||||||
return "{$url}/client/dashboard/{$this->contact_key}";
|
return "{$url}/client/dashboard/{$this->contact_key}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sendPasswordResetNotification($token)
|
||||||
|
{
|
||||||
|
//$this->notify(new ResetPasswordNotification($token));
|
||||||
|
app('App\Ninja\Mailers\ContactMailer')->sendPasswordReset($this, $token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Contact::creating(function ($contact)
|
Contact::creating(function ($contact)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Eloquent;
|
use Eloquent;
|
||||||
|
use Str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Currency.
|
* Class Currency.
|
||||||
@ -28,4 +29,12 @@ class Currency extends Eloquent
|
|||||||
{
|
{
|
||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getTranslatedName()
|
||||||
|
{
|
||||||
|
return trans('texts.currency_' . Str::slug($this->name, '_'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,6 +161,7 @@ class EntityModel extends Eloquent
|
|||||||
|
|
||||||
$query->where($this->getTable() .'.account_id', '=', $accountId);
|
$query->where($this->getTable() .'.account_id', '=', $accountId);
|
||||||
|
|
||||||
|
// If 'false' is passed as the publicId return nothing rather than everything
|
||||||
if (func_num_args() > 1 && ! $publicId && ! $accountId) {
|
if (func_num_args() > 1 && ! $publicId && ! $accountId) {
|
||||||
$query->where('id', '=', 0);
|
$query->where('id', '=', 0);
|
||||||
return $query;
|
return $query;
|
||||||
@ -326,6 +327,7 @@ class EntityModel extends Eloquent
|
|||||||
'settings' => 'cog',
|
'settings' => 'cog',
|
||||||
'self-update' => 'download',
|
'self-update' => 'download',
|
||||||
'reports' => 'th-list',
|
'reports' => 'th-list',
|
||||||
|
'projects' => 'briefcase',
|
||||||
];
|
];
|
||||||
|
|
||||||
return array_get($icons, $entityType);
|
return array_get($icons, $entityType);
|
||||||
|
@ -42,9 +42,8 @@ class Gateway extends Eloquent
|
|||||||
*/
|
*/
|
||||||
public static $preferred = [
|
public static $preferred = [
|
||||||
GATEWAY_PAYPAL_EXPRESS,
|
GATEWAY_PAYPAL_EXPRESS,
|
||||||
GATEWAY_BITPAY,
|
|
||||||
GATEWAY_DWOLLA,
|
|
||||||
GATEWAY_STRIPE,
|
GATEWAY_STRIPE,
|
||||||
|
GATEWAY_WEPAY,
|
||||||
GATEWAY_BRAINTREE,
|
GATEWAY_BRAINTREE,
|
||||||
GATEWAY_AUTHORIZE_NET,
|
GATEWAY_AUTHORIZE_NET,
|
||||||
GATEWAY_MOLLIE,
|
GATEWAY_MOLLIE,
|
||||||
@ -140,7 +139,6 @@ class Gateway extends Eloquent
|
|||||||
public function scopePrimary($query, $accountGatewaysIds)
|
public function scopePrimary($query, $accountGatewaysIds)
|
||||||
{
|
{
|
||||||
$query->where('payment_library_id', '=', 1)
|
$query->where('payment_library_id', '=', 1)
|
||||||
->where('id', '!=', GATEWAY_WEPAY)
|
|
||||||
->whereIn('id', static::$preferred)
|
->whereIn('id', static::$preferred)
|
||||||
->whereIn('id', $accountGatewaysIds);
|
->whereIn('id', $accountGatewaysIds);
|
||||||
}
|
}
|
||||||
@ -152,7 +150,6 @@ class Gateway extends Eloquent
|
|||||||
public function scopeSecondary($query, $accountGatewaysIds)
|
public function scopeSecondary($query, $accountGatewaysIds)
|
||||||
{
|
{
|
||||||
$query->where('payment_library_id', '=', 1)
|
$query->where('payment_library_id', '=', 1)
|
||||||
->where('id', '!=', GATEWAY_WEPAY)
|
|
||||||
->whereNotIn('id', static::$preferred)
|
->whereNotIn('id', static::$preferred)
|
||||||
->whereIn('id', $accountGatewaysIds);
|
->whereIn('id', $accountGatewaysIds);
|
||||||
}
|
}
|
||||||
@ -178,11 +175,13 @@ class Gateway extends Eloquent
|
|||||||
$link = 'https://applications.sagepay.com/apply/2C02C252-0F8A-1B84-E10D-CF933EFCAA99';
|
$link = 'https://applications.sagepay.com/apply/2C02C252-0F8A-1B84-E10D-CF933EFCAA99';
|
||||||
} elseif ($this->id == GATEWAY_STRIPE) {
|
} elseif ($this->id == GATEWAY_STRIPE) {
|
||||||
$link = 'https://dashboard.stripe.com/account/apikeys';
|
$link = 'https://dashboard.stripe.com/account/apikeys';
|
||||||
|
} elseif ($this->id == GATEWAY_WEPAY) {
|
||||||
|
$link = url('/gateways/create?wepay=true');
|
||||||
}
|
}
|
||||||
|
|
||||||
$key = 'texts.gateway_help_'.$this->id;
|
$key = 'texts.gateway_help_'.$this->id;
|
||||||
$str = trans($key, [
|
$str = trans($key, [
|
||||||
'link' => "<a href='$link' target='_blank'>Click here</a>",
|
'link' => "<a href='$link' >Click here</a>",
|
||||||
'complete_link' => url('/complete'),
|
'complete_link' => url('/complete'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -460,6 +460,38 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
return $query->where('invoice_type_id', '=', $typeId);
|
return $query->where('invoice_type_id', '=', $typeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $query
|
||||||
|
* @param $typeId
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function scopeStatusIds($query, $statusIds)
|
||||||
|
{
|
||||||
|
if (! $statusIds || (is_array($statusIds) && ! count($statusIds))) {
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->where(function ($query) use ($statusIds) {
|
||||||
|
foreach ($statusIds as $statusId) {
|
||||||
|
$query->orWhere('invoice_status_id', '=', $statusId);
|
||||||
|
}
|
||||||
|
if (in_array(INVOICE_STATUS_UNPAID, $statusIds)) {
|
||||||
|
$query->orWhere(function ($query) {
|
||||||
|
$query->where('balance', '>', 0)
|
||||||
|
->where('is_public', '=', true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (in_array(INVOICE_STATUS_OVERDUE, $statusIds)) {
|
||||||
|
$query->orWhere(function ($query) {
|
||||||
|
$query->where('balance', '>', 0)
|
||||||
|
->where('due_date', '<', date('Y-m-d'))
|
||||||
|
->where('is_public', '=', true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $typeId
|
* @param $typeId
|
||||||
*
|
*
|
||||||
@ -955,6 +987,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
'include_item_taxes_inline',
|
'include_item_taxes_inline',
|
||||||
'invoice_fields',
|
'invoice_fields',
|
||||||
'show_currency_code',
|
'show_currency_code',
|
||||||
|
'inclusive_taxes',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
foreach ($this->invoice_items as $invoiceItem) {
|
foreach ($this->invoice_items as $invoiceItem) {
|
||||||
@ -1319,17 +1352,26 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
public function getTaxes($calculatePaid = false)
|
public function getTaxes($calculatePaid = false)
|
||||||
{
|
{
|
||||||
$taxes = [];
|
$taxes = [];
|
||||||
|
$account = $this->account;
|
||||||
$taxable = $this->getTaxable();
|
$taxable = $this->getTaxable();
|
||||||
$paidAmount = $this->getAmountPaid($calculatePaid);
|
$paidAmount = $this->getAmountPaid($calculatePaid);
|
||||||
|
|
||||||
if ($this->tax_name1) {
|
if ($this->tax_name1) {
|
||||||
$invoiceTaxAmount = round($taxable * ($this->tax_rate1 / 100), 2);
|
if ($account->inclusive_taxes) {
|
||||||
|
$invoiceTaxAmount = round(($taxable * 100) / (100 + ($this->tax_rate1 * 100)), 2);
|
||||||
|
} else {
|
||||||
|
$invoiceTaxAmount = round($taxable * ($this->tax_rate1 / 100), 2);
|
||||||
|
}
|
||||||
$invoicePaidAmount = floatval($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
|
$invoicePaidAmount = floatval($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
|
||||||
$this->calculateTax($taxes, $this->tax_name1, $this->tax_rate1, $invoiceTaxAmount, $invoicePaidAmount);
|
$this->calculateTax($taxes, $this->tax_name1, $this->tax_rate1, $invoiceTaxAmount, $invoicePaidAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->tax_name2) {
|
if ($this->tax_name2) {
|
||||||
$invoiceTaxAmount = round($taxable * ($this->tax_rate2 / 100), 2);
|
if ($account->inclusive_taxes) {
|
||||||
|
$invoiceTaxAmount = round(($taxable * 100) / (100 + ($this->tax_rate2 * 100)), 2);
|
||||||
|
} else {
|
||||||
|
$invoiceTaxAmount = round($taxable * ($this->tax_rate2 / 100), 2);
|
||||||
|
}
|
||||||
$invoicePaidAmount = floatval($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
|
$invoicePaidAmount = floatval($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
|
||||||
$this->calculateTax($taxes, $this->tax_name2, $this->tax_rate2, $invoiceTaxAmount, $invoicePaidAmount);
|
$this->calculateTax($taxes, $this->tax_name2, $this->tax_rate2, $invoiceTaxAmount, $invoicePaidAmount);
|
||||||
}
|
}
|
||||||
@ -1338,13 +1380,21 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
$itemTaxable = $this->getItemTaxable($invoiceItem, $taxable);
|
$itemTaxable = $this->getItemTaxable($invoiceItem, $taxable);
|
||||||
|
|
||||||
if ($invoiceItem->tax_name1) {
|
if ($invoiceItem->tax_name1) {
|
||||||
$itemTaxAmount = round($itemTaxable * ($invoiceItem->tax_rate1 / 100), 2);
|
if ($account->inclusive_taxes) {
|
||||||
|
$itemTaxAmount = round(($itemTaxable * 100) / (100 + ($invoiceItem->tax_rate1 * 100)), 2);
|
||||||
|
} else {
|
||||||
|
$itemTaxAmount = round($itemTaxable * ($invoiceItem->tax_rate1 / 100), 2);
|
||||||
|
}
|
||||||
$itemPaidAmount = floatval($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
|
$itemPaidAmount = floatval($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
|
||||||
$this->calculateTax($taxes, $invoiceItem->tax_name1, $invoiceItem->tax_rate1, $itemTaxAmount, $itemPaidAmount);
|
$this->calculateTax($taxes, $invoiceItem->tax_name1, $invoiceItem->tax_rate1, $itemTaxAmount, $itemPaidAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($invoiceItem->tax_name2) {
|
if ($invoiceItem->tax_name2) {
|
||||||
$itemTaxAmount = round($itemTaxable * ($invoiceItem->tax_rate2 / 100), 2);
|
if ($account->inclusive_taxes) {
|
||||||
|
$itemTaxAmount = round(($itemTaxable * 100) / (100 + ($invoiceItem->tax_rate2 * 100)), 2);
|
||||||
|
} else {
|
||||||
|
$itemTaxAmount = round($itemTaxable * ($invoiceItem->tax_rate2 / 100), 2);
|
||||||
|
}
|
||||||
$itemPaidAmount = floatval($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
|
$itemPaidAmount = floatval($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
|
||||||
$this->calculateTax($taxes, $invoiceItem->tax_name2, $invoiceItem->tax_rate2, $itemTaxAmount, $itemPaidAmount);
|
$this->calculateTax($taxes, $invoiceItem->tax_name2, $invoiceItem->tax_rate2, $itemTaxAmount, $itemPaidAmount);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Eloquent;
|
use Eloquent;
|
||||||
|
use Str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class InvoiceStatus.
|
* Class InvoiceStatus.
|
||||||
@ -33,6 +34,14 @@ class InvoiceStatus extends Eloquent
|
|||||||
return INVOICE_STATUS_UNPAID;
|
return INVOICE_STATUS_UNPAID;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getTranslatedName()
|
||||||
|
{
|
||||||
|
return trans('texts.status_' . Str::slug($this->name, '_'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,4 +55,44 @@ class LookupAccount extends LookupModel
|
|||||||
return $this->lookupCompany->dbServer->name;
|
return $this->lookupCompany->dbServer->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function updateAccount($accountKey, $account)
|
||||||
|
{
|
||||||
|
if (! env('MULTI_DB_ENABLED')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = config('database.default');
|
||||||
|
config(['database.default' => DB_NINJA_LOOKUP]);
|
||||||
|
|
||||||
|
$lookupAccount = LookupAccount::whereAccountKey($accountKey)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
$lookupAccount->subdomain = $account->subdomain ?: null;
|
||||||
|
$lookupAccount->save();
|
||||||
|
|
||||||
|
config(['database.default' => $current]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function validateField($field, $value, $account = false)
|
||||||
|
{
|
||||||
|
if (! env('MULTI_DB_ENABLED')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = config('database.default');
|
||||||
|
|
||||||
|
config(['database.default' => DB_NINJA_LOOKUP]);
|
||||||
|
|
||||||
|
$lookupAccount = LookupAccount::where($field, '=', $value)->first();
|
||||||
|
|
||||||
|
if ($account) {
|
||||||
|
$isValid = ! $lookupAccount || ($lookupAccount->account_key == $account->account_key);
|
||||||
|
} else {
|
||||||
|
$isValid = ! $lookupAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
config(['database.default' => $current]);
|
||||||
|
|
||||||
|
return $isValid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
13
app/Models/PasswordReset.php
Normal file
13
app/Models/PasswordReset.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Eloquent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Client.
|
||||||
|
*/
|
||||||
|
class PasswordReset extends Eloquent
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -23,7 +23,10 @@ class Payment extends EntityModel
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
'transaction_reference',
|
||||||
'private_notes',
|
'private_notes',
|
||||||
|
'exchange_rate',
|
||||||
|
'exchange_currency_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
public static $statusClasses = [
|
public static $statusClasses = [
|
||||||
@ -303,6 +306,14 @@ class Payment extends EntityModel
|
|||||||
return $this->getCompletedAmount() > 0 && ($this->isCompleted() || $this->isPartiallyRefunded());
|
return $this->getCompletedAmount() > 0 && ($this->isCompleted() || $this->isPartiallyRefunded());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isExchanged()
|
||||||
|
{
|
||||||
|
return $this->exchange_currency_id || $this->exchange_rate != 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed|null|\stdClass|string
|
* @return mixed|null|\stdClass|string
|
||||||
*/
|
*/
|
||||||
|
@ -256,7 +256,7 @@ class PaymentMethod extends EntityModel
|
|||||||
PaymentMethod::deleting(function ($paymentMethod) {
|
PaymentMethod::deleting(function ($paymentMethod) {
|
||||||
$accountGatewayToken = $paymentMethod->account_gateway_token;
|
$accountGatewayToken = $paymentMethod->account_gateway_token;
|
||||||
if ($accountGatewayToken->default_payment_method_id == $paymentMethod->id) {
|
if ($accountGatewayToken->default_payment_method_id == $paymentMethod->id) {
|
||||||
$newDefault = $accountGatewayToken->payment_methods->first(function ($i, $paymentMethdod) use ($accountGatewayToken) {
|
$newDefault = $accountGatewayToken->payment_methods->first(function ($paymentMethdod) use ($accountGatewayToken) {
|
||||||
return $paymentMethdod->id != $accountGatewayToken->default_payment_method_id;
|
return $paymentMethdod->id != $accountGatewayToken->default_payment_method_id;
|
||||||
});
|
});
|
||||||
$accountGatewayToken->default_payment_method_id = $newDefault ? $newDefault->id : null;
|
$accountGatewayToken->default_payment_method_id = $newDefault ? $newDefault->id : null;
|
||||||
|
@ -55,6 +55,14 @@ class Project extends EntityModel
|
|||||||
{
|
{
|
||||||
return $this->belongsTo('App\Models\Client')->withTrashed();
|
return $this->belongsTo('App\Models\Client')->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
*/
|
||||||
|
public function tasks()
|
||||||
|
{
|
||||||
|
return $this->hasMany('App\Models\Task');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Project::creating(function ($project) {
|
Project::creating(function ($project) {
|
||||||
|
59
app/Models/ScheduledReport.php
Normal file
59
app/Models/ScheduledReport.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Scheduled Report
|
||||||
|
*/
|
||||||
|
class ScheduledReport extends EntityModel
|
||||||
|
{
|
||||||
|
use SoftDeletes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'frequency',
|
||||||
|
'config',
|
||||||
|
'send_date',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
*/
|
||||||
|
public function account()
|
||||||
|
{
|
||||||
|
return $this->belongsTo('App\Models\Account');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo('App\Models\User')->withTrashed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateSendDate()
|
||||||
|
{
|
||||||
|
switch ($this->frequency) {
|
||||||
|
case REPORT_FREQUENCY_DAILY;
|
||||||
|
$this->send_date = Carbon::now()->addDay()->toDateString();
|
||||||
|
break;
|
||||||
|
case REPORT_FREQUENCY_WEEKLY:
|
||||||
|
$this->send_date = Carbon::now()->addWeek()->toDateString();
|
||||||
|
break;
|
||||||
|
case REPORT_FREQUENCY_BIWEEKLY:
|
||||||
|
$this->send_date = Carbon::now()->addWeeks(2)->toDateString();
|
||||||
|
break;
|
||||||
|
case REPORT_FREQUENCY_MONTHLY:
|
||||||
|
$this->send_date = Carbon::now()->addMonth()->toDateString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
}
|
@ -8,15 +8,42 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||||||
/**
|
/**
|
||||||
* Class Subscription.
|
* Class Subscription.
|
||||||
*/
|
*/
|
||||||
class Subscription extends Eloquent
|
class Subscription extends EntityModel
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public $timestamps = true;
|
public $timestamps = true;
|
||||||
|
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $dates = ['deleted_at'];
|
protected $dates = ['deleted_at'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'event_id',
|
||||||
|
'target_url',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getEntityType()
|
||||||
|
{
|
||||||
|
return ENTITY_SUBSCRIPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
*/
|
||||||
|
public function account()
|
||||||
|
{
|
||||||
|
return $this->belongsTo('App\Models\Account');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ trait HasRecurrence
|
|||||||
$monthsSinceLastSent = ($diff->format('%y') * 12) + $diff->format('%m');
|
$monthsSinceLastSent = ($diff->format('%y') * 12) + $diff->format('%m');
|
||||||
|
|
||||||
// check we don't send a few hours early due to timezone difference
|
// check we don't send a few hours early due to timezone difference
|
||||||
if (Carbon::now()->format('Y-m-d') != Carbon::now($timezone)->format('Y-m-d')) {
|
if (Utils::isNinja() && Carbon::now()->format('Y-m-d') != Carbon::now($timezone)->format('Y-m-d')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,6 +331,7 @@ trait PresentsInvoice
|
|||||||
'unit_cost',
|
'unit_cost',
|
||||||
'custom_value1',
|
'custom_value1',
|
||||||
'custom_value2',
|
'custom_value2',
|
||||||
|
'delivery_note',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($fields as $field) {
|
foreach ($fields as $field) {
|
||||||
|
@ -11,6 +11,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
|
|||||||
use Laracasts\Presenter\PresentableTrait;
|
use Laracasts\Presenter\PresentableTrait;
|
||||||
use Session;
|
use Session;
|
||||||
use App\Models\LookupUser;
|
use App\Models\LookupUser;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class User.
|
* Class User.
|
||||||
@ -19,6 +20,7 @@ class User extends Authenticatable
|
|||||||
{
|
{
|
||||||
use PresentableTrait;
|
use PresentableTrait;
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
|
use Notifiable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
@ -176,7 +178,7 @@ class User extends Authenticatable
|
|||||||
} elseif ($this->email) {
|
} elseif ($this->email) {
|
||||||
return $this->email;
|
return $this->email;
|
||||||
} else {
|
} else {
|
||||||
return 'Guest';
|
return trans('texts.guest');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,6 +429,12 @@ class User extends Authenticatable
|
|||||||
{
|
{
|
||||||
return $this->account->company->accounts->sortBy('id')->first();
|
return $this->account->company->accounts->sortBy('id')->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sendPasswordResetNotification($token)
|
||||||
|
{
|
||||||
|
//$this->notify(new ResetPasswordNotification($token));
|
||||||
|
app('App\Ninja\Mailers\UserMailer')->sendPasswordReset($this, $token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
User::created(function ($user)
|
User::created(function ($user)
|
||||||
|
@ -68,7 +68,10 @@ class InvoiceDatatable extends EntityDatatable
|
|||||||
function ($model) {
|
function ($model) {
|
||||||
$str = '';
|
$str = '';
|
||||||
if ($model->partial_due_date) {
|
if ($model->partial_due_date) {
|
||||||
$str = Utils::fromSqlDate($model->partial_due_date) . ', ';
|
$str = Utils::fromSqlDate($model->partial_due_date);
|
||||||
|
if ($model->due_date_sql && $model->due_date_sql != '0000-00-00') {
|
||||||
|
$str .= ', ';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return $str . Utils::fromSqlDate($model->due_date_sql);
|
return $str . Utils::fromSqlDate($model->due_date_sql);
|
||||||
},
|
},
|
||||||
@ -106,11 +109,20 @@ class InvoiceDatatable extends EntityDatatable
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
trans('texts.view_history'),
|
trans("texts.{$entityType}_history"),
|
||||||
function ($model) use ($entityType) {
|
function ($model) use ($entityType) {
|
||||||
return URL::to("{$entityType}s/{$entityType}_history/{$model->public_id}");
|
return URL::to("{$entityType}s/{$entityType}_history/{$model->public_id}");
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
trans('texts.delivery_note'),
|
||||||
|
function ($model) use ($entityType) {
|
||||||
|
return url("invoices/delivery_note/{$model->public_id}");
|
||||||
|
},
|
||||||
|
function ($model) use ($entityType) {
|
||||||
|
return $entityType == ENTITY_INVOICE;
|
||||||
|
},
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'--divider--', function () {
|
'--divider--', function () {
|
||||||
return false;
|
return false;
|
||||||
|
@ -91,7 +91,13 @@ class PaymentDatatable extends EntityDatatable
|
|||||||
[
|
[
|
||||||
'amount',
|
'amount',
|
||||||
function ($model) {
|
function ($model) {
|
||||||
return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id);
|
$amount = Utils::formatMoney($model->amount, $model->currency_id, $model->country_id);
|
||||||
|
|
||||||
|
if ($model->exchange_currency_id && $model->exchange_rate != 1) {
|
||||||
|
$amount .= ' | ' . Utils::formatMoney($model->amount * $model->exchange_rate, $model->exchange_currency_id, $model->country_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $amount;
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -59,6 +59,15 @@ class ProjectDatatable extends EntityDatatable
|
|||||||
return Auth::user()->can('editByOwner', [ENTITY_PROJECT, $model->user_id]);
|
return Auth::user()->can('editByOwner', [ENTITY_PROJECT, $model->user_id]);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
trans('texts.invoice_project'),
|
||||||
|
function ($model) {
|
||||||
|
return "javascript:submitForm_project('invoice', {$model->public_id})";
|
||||||
|
},
|
||||||
|
function ($model) {
|
||||||
|
return Auth::user()->can('create', ENTITY_INVOICE);
|
||||||
|
},
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
40
app/Ninja/Datatables/SubscriptionDatatable.php
Normal file
40
app/Ninja/Datatables/SubscriptionDatatable.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Ninja\Datatables;
|
||||||
|
|
||||||
|
use URL;
|
||||||
|
|
||||||
|
class SubscriptionDatatable extends EntityDatatable
|
||||||
|
{
|
||||||
|
public $entityType = ENTITY_SUBSCRIPTION;
|
||||||
|
|
||||||
|
public function columns()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'event',
|
||||||
|
function ($model) {
|
||||||
|
return trans('texts.subscription_event_' . $model->event);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'target',
|
||||||
|
function ($model) {
|
||||||
|
return $model->target;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
uctrans('texts.edit_subscription'),
|
||||||
|
function ($model) {
|
||||||
|
return URL::to("subscriptions/{$model->public_id}/edit");
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,11 @@ class TaxRateDatatable extends EntityDatatable
|
|||||||
[
|
[
|
||||||
'type',
|
'type',
|
||||||
function ($model) {
|
function ($model) {
|
||||||
return $model->is_inclusive ? trans('texts.inclusive') : trans('texts.exclusive');
|
if (auth()->user()->account->inclusive_taxes) {
|
||||||
|
return trans('texts.inclusive');
|
||||||
|
} else {
|
||||||
|
return $model->is_inclusive ? trans('texts.inclusive') : trans('texts.exclusive');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -327,4 +327,19 @@ class ContactMailer extends Mailer
|
|||||||
|
|
||||||
$this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
|
$this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sendPasswordReset($contact, $token)
|
||||||
|
{
|
||||||
|
if (! $contact->email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$subject = trans('texts.your_password_reset_link');
|
||||||
|
$view = 'client_password';
|
||||||
|
$data = [
|
||||||
|
'token' => $token,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->sendTo($contact->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,4 +154,42 @@ class UserMailer extends Mailer
|
|||||||
|
|
||||||
$this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
|
$this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sendPasswordReset($user, $token)
|
||||||
|
{
|
||||||
|
if (! $user->email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$subject = trans('texts.your_password_reset_link');
|
||||||
|
$view = 'password';
|
||||||
|
$data = [
|
||||||
|
'token' => $token,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendScheduledReport($scheduledReport, $file)
|
||||||
|
{
|
||||||
|
$user = $scheduledReport->user;
|
||||||
|
$config = json_decode($scheduledReport->config);
|
||||||
|
|
||||||
|
if (! $user->email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$subject = sprintf('%s - %s %s', APP_NAME, trans('texts.' . $config->report_type), trans('texts.report'));
|
||||||
|
$view = 'user_message';
|
||||||
|
$data = [
|
||||||
|
'userName' => $user->getDisplayName(),
|
||||||
|
'primaryMessage' => trans('texts.scheduled_report_attached', ['type' => trans('texts.' . $config->report_type)]),
|
||||||
|
'documents' => [[
|
||||||
|
'name' => $file->filename . '.' . $config->export_format,
|
||||||
|
'data' => $file->string($config->export_format),
|
||||||
|
]]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,6 @@ class BasePaymentDriver
|
|||||||
'accountGateway' => $this->accountGateway,
|
'accountGateway' => $this->accountGateway,
|
||||||
'acceptedCreditCardTypes' => $this->accountGateway->getCreditcardTypes(),
|
'acceptedCreditCardTypes' => $this->accountGateway->getCreditcardTypes(),
|
||||||
'gateway' => $gateway,
|
'gateway' => $gateway,
|
||||||
'showAddress' => $this->accountGateway->show_address,
|
|
||||||
'showBreadcrumbs' => false,
|
'showBreadcrumbs' => false,
|
||||||
'url' => $url,
|
'url' => $url,
|
||||||
'amount' => $this->invoice()->getRequestedAmount(),
|
'amount' => $this->invoice()->getRequestedAmount(),
|
||||||
@ -407,19 +406,30 @@ class BasePaymentDriver
|
|||||||
$this->contact()->save();
|
$this->contact()->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->accountGateway->show_address || ! $this->accountGateway->update_address) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the address info
|
// update the address info
|
||||||
$client = $this->client();
|
$client = $this->client();
|
||||||
$client->address1 = trim($this->input['address1']);
|
|
||||||
$client->address2 = trim($this->input['address2']);
|
if ($this->accountGateway->show_address && $this->accountGateway->update_address) {
|
||||||
$client->city = trim($this->input['city']);
|
$client->address1 = trim($this->input['address1']);
|
||||||
$client->state = trim($this->input['state']);
|
$client->address2 = trim($this->input['address2']);
|
||||||
$client->postal_code = trim($this->input['postal_code']);
|
$client->city = trim($this->input['city']);
|
||||||
$client->country_id = trim($this->input['country_id']);
|
$client->state = trim($this->input['state']);
|
||||||
$client->save();
|
$client->postal_code = trim($this->input['postal_code']);
|
||||||
|
$client->country_id = trim($this->input['country_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->accountGateway->show_shipping_address) {
|
||||||
|
$client->shipping_address1 = trim($this->input['shipping_address1']);
|
||||||
|
$client->shipping_address2 = trim($this->input['shipping_address2']);
|
||||||
|
$client->shipping_city = trim($this->input['shipping_city']);
|
||||||
|
$client->shipping_state = trim($this->input['shipping_state']);
|
||||||
|
$client->shipping_postal_code = trim($this->input['shipping_postal_code']);
|
||||||
|
$client->shipping_country_id = trim($this->input['shipping_country_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($client->isDirty()) {
|
||||||
|
$client->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function paymentDetails($paymentMethod = false)
|
protected function paymentDetails($paymentMethod = false)
|
||||||
@ -474,22 +484,23 @@ class BasePaymentDriver
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($input['address1'])) {
|
if (isset($input['address1'])) {
|
||||||
// TODO use cache instead
|
$hasShippingAddress = $this->accountGateway->show_shipping_address;
|
||||||
$country = Country::find($input['country_id']);
|
$country = Utils::getFromCache($input['country_id'], 'countries');
|
||||||
|
$shippingCountry = $hasShippingAddress ? Utils::getFromCache($input['shipping_country_id'], 'countries') : $country;
|
||||||
|
|
||||||
$data = array_merge($data, [
|
$data = array_merge($data, [
|
||||||
'billingAddress1' => $input['address1'],
|
'billingAddress1' => trim($input['address1']),
|
||||||
'billingAddress2' => $input['address2'],
|
'billingAddress2' => trim($input['address2']),
|
||||||
'billingCity' => $input['city'],
|
'billingCity' => trim($input['city']),
|
||||||
'billingState' => $input['state'],
|
'billingState' => trim($input['state']),
|
||||||
'billingPostcode' => $input['postal_code'],
|
'billingPostcode' => trim($input['postal_code']),
|
||||||
'billingCountry' => $country->iso_3166_2,
|
'billingCountry' => $country->iso_3166_2,
|
||||||
'shippingAddress1' => $input['address1'],
|
'shippingAddress1' => $hasShippingAddress ? trim($this->input['shipping_address1']) : trim($input['address1']),
|
||||||
'shippingAddress2' => $input['address2'],
|
'shippingAddress2' => $hasShippingAddress ? trim($this->input['shipping_address2']) : trim($input['address2']),
|
||||||
'shippingCity' => $input['city'],
|
'shippingCity' => $hasShippingAddress ? trim($this->input['shipping_city']) : trim($input['city']),
|
||||||
'shippingState' => $input['state'],
|
'shippingState' => $hasShippingAddress ? trim($this->input['shipping_state']) : trim($input['state']),
|
||||||
'shippingPostcode' => $input['postal_code'],
|
'shippingPostcode' => $hasShippingAddress ? trim($this->input['shipping_postal_code']) : trim($input['postal_code']),
|
||||||
'shippingCountry' => $country->iso_3166_2,
|
'shippingCountry' => $hasShippingAddress ? $shippingCountry->iso_3166_2 : $country->iso_3166_2,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,6 +512,7 @@ class BasePaymentDriver
|
|||||||
$invoice = $this->invoice();
|
$invoice = $this->invoice();
|
||||||
$client = $this->client();
|
$client = $this->client();
|
||||||
$contact = $this->invitation->contact ?: $client->contacts()->first();
|
$contact = $this->invitation->contact ?: $client->contacts()->first();
|
||||||
|
$hasShippingAddress = $this->accountGateway->show_shipping_address;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'email' => $contact->email,
|
'email' => $contact->email,
|
||||||
@ -514,12 +526,12 @@ class BasePaymentDriver
|
|||||||
'billingState' => $client->state,
|
'billingState' => $client->state,
|
||||||
'billingCountry' => $client->country ? $client->country->iso_3166_2 : '',
|
'billingCountry' => $client->country ? $client->country->iso_3166_2 : '',
|
||||||
'billingPhone' => $contact->phone,
|
'billingPhone' => $contact->phone,
|
||||||
'shippingAddress1' => $client->address1,
|
'shippingAddress1' => $client->shipping_address1 ? $client->shipping_address1 : $client->address1,
|
||||||
'shippingAddress2' => $client->address2,
|
'shippingAddress2' => $client->shipping_address1 ? $client->shipping_address1 : $client->address2,
|
||||||
'shippingCity' => $client->city,
|
'shippingCity' => $client->shipping_address1 ? $client->shipping_address1 : $client->city,
|
||||||
'shippingPostcode' => $client->postal_code,
|
'shippingPostcode' => $client->shipping_address1 ? $client->shipping_address1 : $client->postal_code,
|
||||||
'shippingState' => $client->state,
|
'shippingState' => $client->shipping_address1 ? $client->shipping_address1 : $client->state,
|
||||||
'shippingCountry' => $client->country ? $client->country->iso_3166_2 : '',
|
'shippingCountry' => $client->shipping_address1 ? ($client->shipping_country ? $client->shipping_country->iso_3166_2 : '') : ($client->country ? $client->country->iso_3166_2 : ''),
|
||||||
'shippingPhone' => $contact->phone,
|
'shippingPhone' => $contact->phone,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -867,23 +879,32 @@ class BasePaymentDriver
|
|||||||
return $payment;
|
return $payment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function updateClientFromOffsite($transRef, $paymentRef)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
public function completeOffsitePurchase($input)
|
public function completeOffsitePurchase($input)
|
||||||
{
|
{
|
||||||
$this->input = $input;
|
$this->input = $input;
|
||||||
$ref = array_get($this->input, 'token') ?: $this->invitation->transaction_reference;
|
$transRef = array_get($this->input, 'token') ?: $this->invitation->transaction_reference;
|
||||||
|
|
||||||
if (method_exists($this->gateway(), 'completePurchase')) {
|
if (method_exists($this->gateway(), 'completePurchase')) {
|
||||||
$details = $this->paymentDetails();
|
$details = $this->paymentDetails();
|
||||||
$response = $this->gateway()->completePurchase($details)->send();
|
$response = $this->gateway()->completePurchase($details)->send();
|
||||||
$ref = $response->getTransactionReference() ?: $ref;
|
$paymentRef = $response->getTransactionReference() ?: $transRef;
|
||||||
|
|
||||||
if ($response->isCancelled()) {
|
if ($response->isCancelled()) {
|
||||||
return false;
|
return false;
|
||||||
} elseif (! $response->isSuccessful()) {
|
} elseif (! $response->isSuccessful()) {
|
||||||
throw new Exception($response->getMessage());
|
throw new Exception($response->getMessage());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$paymentRef = $transRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->updateClientFromOffsite($transRef, $paymentRef);
|
||||||
|
|
||||||
// check invoice still has balance
|
// check invoice still has balance
|
||||||
if (! floatval($this->invoice()->balance)) {
|
if (! floatval($this->invoice()->balance)) {
|
||||||
throw new Exception(trans('texts.payment_error_code', ['code' => 'NB']));
|
throw new Exception(trans('texts.payment_error_code', ['code' => 'NB']));
|
||||||
@ -891,12 +912,12 @@ class BasePaymentDriver
|
|||||||
|
|
||||||
// check this isn't a duplicate transaction reference
|
// check this isn't a duplicate transaction reference
|
||||||
if (Payment::whereAccountId($this->invitation->account_id)
|
if (Payment::whereAccountId($this->invitation->account_id)
|
||||||
->whereTransactionReference($ref)
|
->whereTransactionReference($paymentRef)
|
||||||
->first()) {
|
->first()) {
|
||||||
throw new Exception(trans('texts.payment_error_code', ['code' => 'DT']));
|
throw new Exception(trans('texts.payment_error_code', ['code' => 'DT']));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->createPayment($ref);
|
return $this->createPayment($paymentRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tokenLinks()
|
public function tokenLinks()
|
||||||
|
@ -41,4 +41,33 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function updateClientFromOffsite($transRef, $paymentRef)
|
||||||
|
{
|
||||||
|
$response = $this->gateway()->fetchCheckout([
|
||||||
|
'token' => $transRef
|
||||||
|
])->send();
|
||||||
|
|
||||||
|
$data = $response->getData();
|
||||||
|
$client = $this->client();
|
||||||
|
|
||||||
|
if (empty($data['SHIPTOSTREET'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client->shipping_address1 = trim($data['SHIPTOSTREET']);
|
||||||
|
$client->shipping_address2 = '';
|
||||||
|
$client->shipping_city = trim($data['SHIPTOCITY']);
|
||||||
|
$client->shipping_state = trim($data['SHIPTOSTATE']);
|
||||||
|
$client->shipping_postal_code = trim($data['SHIPTOZIP']);
|
||||||
|
|
||||||
|
if ($country = cache('countries')->filter(function ($item) use ($data) {
|
||||||
|
return strtolower($item->iso_3166_2) == strtolower(trim($data['SHIPTOCOUNTRYCODE']));
|
||||||
|
})->first()) {
|
||||||
|
$client->shipping_country_id = $country->id;
|
||||||
|
} else {
|
||||||
|
$client->shipping_country_id = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,9 @@ class StripePaymentDriver extends BasePaymentDriver
|
|||||||
if ($gateway->getAlipayEnabled()) {
|
if ($gateway->getAlipayEnabled()) {
|
||||||
$types[] = GATEWAY_TYPE_ALIPAY;
|
$types[] = GATEWAY_TYPE_ALIPAY;
|
||||||
}
|
}
|
||||||
|
if ($gateway->getApplePayEnabled()) {
|
||||||
|
$types[] = GATEWAY_TYPE_APPLE_PAY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $types;
|
return $types;
|
||||||
@ -67,6 +70,10 @@ class StripePaymentDriver extends BasePaymentDriver
|
|||||||
{
|
{
|
||||||
$rules = parent::rules();
|
$rules = parent::rules();
|
||||||
|
|
||||||
|
if ($this->isGatewayType(GATEWAY_TYPE_APPLE_PAY)) {
|
||||||
|
return ['sourceToken' => 'required'];
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) {
|
if ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) {
|
||||||
$rules['authorize_ach'] = 'required';
|
$rules['authorize_ach'] = 'required';
|
||||||
}
|
}
|
||||||
@ -224,7 +231,9 @@ class StripePaymentDriver extends BasePaymentDriver
|
|||||||
|
|
||||||
// For older users the Stripe account may just have the customer token but not the card version
|
// For older users the Stripe account may just have the customer token but not the card version
|
||||||
// In that case we'd use GATEWAY_TYPE_TOKEN even though we're creating the credit card
|
// In that case we'd use GATEWAY_TYPE_TOKEN even though we're creating the credit card
|
||||||
if ($this->isGatewayType(GATEWAY_TYPE_CREDIT_CARD) || $this->isGatewayType(GATEWAY_TYPE_TOKEN)) {
|
if ($this->isGatewayType(GATEWAY_TYPE_CREDIT_CARD)
|
||||||
|
|| $this->isGatewayType(GATEWAY_TYPE_APPLE_PAY)
|
||||||
|
|| $this->isGatewayType(GATEWAY_TYPE_TOKEN)) {
|
||||||
$paymentMethod->expiration = $source['exp_year'] . '-' . $source['exp_month'] . '-01';
|
$paymentMethod->expiration = $source['exp_year'] . '-' . $source['exp_month'] . '-01';
|
||||||
$paymentMethod->payment_type_id = PaymentType::parseCardType($source['brand']);
|
$paymentMethod->payment_type_id = PaymentType::parseCardType($source['brand']);
|
||||||
} elseif ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) {
|
} elseif ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) {
|
||||||
|
@ -236,4 +236,31 @@ class AccountPresenter extends Presenter
|
|||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function clientLoginUrl()
|
||||||
|
{
|
||||||
|
$account = $this->entity;
|
||||||
|
|
||||||
|
if (Utils::isNinjaProd()) {
|
||||||
|
$url = 'https://';
|
||||||
|
$url .= $account->subdomain ?: 'app';
|
||||||
|
$url .= '.' . Domain::getDomainFromId($account->domain_id);
|
||||||
|
} else {
|
||||||
|
$url = SITE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url .= '/client/login';
|
||||||
|
|
||||||
|
if (Utils::isNinja()) {
|
||||||
|
if (! $account->subdomain) {
|
||||||
|
$url .= '?account_key=' . $account->account_key;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Account::count() > 1) {
|
||||||
|
$url .= '?account_key=' . $account->account_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,11 @@ class ClientPresenter extends EntityPresenter
|
|||||||
return $this->entity->country ? $this->entity->country->name : '';
|
return $this->entity->country ? $this->entity->country->name : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function shipping_country()
|
||||||
|
{
|
||||||
|
return $this->entity->shipping_country ? $this->entity->shipping_country->name : '';
|
||||||
|
}
|
||||||
|
|
||||||
public function balance()
|
public function balance()
|
||||||
{
|
{
|
||||||
$client = $this->entity;
|
$client = $this->entity;
|
||||||
@ -51,6 +56,53 @@ class ClientPresenter extends EntityPresenter
|
|||||||
return sprintf('%s: %s %s', trans('texts.payment_terms'), trans('texts.payment_terms_net'), $client->defaultDaysDue());
|
return sprintf('%s: %s %s', trans('texts.payment_terms'), trans('texts.payment_terms_net'), $client->defaultDaysDue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function address($addressType = ADDRESS_BILLING)
|
||||||
|
{
|
||||||
|
$str = '';
|
||||||
|
$prefix = $addressType == ADDRESS_BILLING ? '' : 'shipping_';
|
||||||
|
$client = $this->entity;
|
||||||
|
|
||||||
|
if ($address1 = $client->{$prefix . 'address1'}) {
|
||||||
|
$str .= e($address1) . '<br/>';
|
||||||
|
}
|
||||||
|
if ($address2 = $client->{$prefix . 'address2'}) {
|
||||||
|
$str .= e($address2) . '<br/>';
|
||||||
|
}
|
||||||
|
if ($cityState = $this->getCityState($addressType)) {
|
||||||
|
$str .= e($cityState) . '<br/>';
|
||||||
|
}
|
||||||
|
if ($country = $client->{$prefix . 'country'}) {
|
||||||
|
$str .= e($country->name) . '<br/>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($str) {
|
||||||
|
$str = '<b>' . trans('texts.' . $addressType) . '</b><br/>' . $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCityState($addressType = ADDRESS_BILLING)
|
||||||
|
{
|
||||||
|
$client = $this->entity;
|
||||||
|
$prefix = $addressType == ADDRESS_BILLING ? '' : 'shipping_';
|
||||||
|
$swap = $client->{$prefix . 'country'} && $client->{$prefix . 'country'}->swap_postal_code;
|
||||||
|
|
||||||
|
$city = e($client->{$prefix . 'city'});
|
||||||
|
$state = e($client->{$prefix . 'state'});
|
||||||
|
$postalCode = e($client->{$prefix . 'post_code'});
|
||||||
|
|
||||||
|
if ($city || $state || $postalCode) {
|
||||||
|
return Utils::cityStateZip($city, $state, $postalCode, $swap);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Ninja\Presenters;
|
namespace App\Ninja\Presenters;
|
||||||
|
|
||||||
|
use Str;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
class InvoiceItemPresenter extends EntityPresenter
|
class InvoiceItemPresenter extends EntityPresenter
|
||||||
@ -16,4 +17,9 @@ class InvoiceItemPresenter extends EntityPresenter
|
|||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function notes()
|
||||||
|
{
|
||||||
|
return Str::limit($this->entity->notes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,6 +242,11 @@ class InvoicePresenter extends EntityPresenter
|
|||||||
}
|
}
|
||||||
|
|
||||||
$actions[] = ['url' => url("{$entityType}s/{$entityType}_history/{$invoice->public_id}"), 'label' => trans('texts.view_history')];
|
$actions[] = ['url' => url("{$entityType}s/{$entityType}_history/{$invoice->public_id}"), 'label' => trans('texts.view_history')];
|
||||||
|
|
||||||
|
if ($entityType == ENTITY_INVOICE) {
|
||||||
|
$actions[] = ['url' => url("invoices/delivery_note/{$invoice->public_id}"), 'label' => trans('texts.delivery_note')];
|
||||||
|
}
|
||||||
|
|
||||||
$actions[] = DropdownButton::DIVIDER;
|
$actions[] = DropdownButton::DIVIDER;
|
||||||
|
|
||||||
if ($entityType == ENTITY_QUOTE) {
|
if ($entityType == ENTITY_QUOTE) {
|
||||||
|
@ -18,8 +18,8 @@ class ActivityReport extends AbstractReport
|
|||||||
{
|
{
|
||||||
$account = Auth::user()->account;
|
$account = Auth::user()->account;
|
||||||
|
|
||||||
$startDate = $this->startDate->format('Y-m-d');
|
$startDate = $this->startDate;;
|
||||||
$endDate = $this->endDate->format('Y-m-d');
|
$endDate = $this->endDate;
|
||||||
|
|
||||||
$activities = Activity::scope()
|
$activities = Activity::scope()
|
||||||
->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'task', 'expense', 'account')
|
->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'task', 'expense', 'account')
|
||||||
@ -32,7 +32,7 @@ class ActivityReport extends AbstractReport
|
|||||||
$activity->present()->createdAt,
|
$activity->present()->createdAt,
|
||||||
$client ? ($this->isExport ? $client->getDisplayName() : $client->present()->link) : '',
|
$client ? ($this->isExport ? $client->getDisplayName() : $client->present()->link) : '',
|
||||||
$activity->present()->user,
|
$activity->present()->user,
|
||||||
$activity->getMessage(),
|
$this->isExport ? strip_tags($activity->getMessage()) : $activity->getMessage(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,8 @@ class ExpenseReport extends AbstractReport
|
|||||||
$zip = Archive::instance_by_useragent(date('Y-m-d') . '_' . str_replace(' ', '_', trans('texts.expense_documents')));
|
$zip = Archive::instance_by_useragent(date('Y-m-d') . '_' . str_replace(' ', '_', trans('texts.expense_documents')));
|
||||||
foreach ($expenses->get() as $expense) {
|
foreach ($expenses->get() as $expense) {
|
||||||
foreach ($expense->documents as $document) {
|
foreach ($expense->documents as $document) {
|
||||||
$name = sprintf('%s_%s_%s_%s', date('Y-m-d'), trans('texts.expense'), $expense->public_id, $document->name);
|
$expenseId = str_pad($expense->public_id, $account->invoice_number_padding, '0', STR_PAD_LEFT);
|
||||||
|
$name = sprintf('%s_%s_%s_%s', date('Y-m-d'), trans('texts.expense'), $expenseId, $document->name);
|
||||||
$name = str_replace(' ', '_', $name);
|
$name = str_replace(' ', '_', $name);
|
||||||
$zip->add_file($name, $document->getRaw());
|
$zip->add_file($name, $document->getRaw());
|
||||||
}
|
}
|
||||||
|
@ -22,21 +22,17 @@ class InvoiceReport extends AbstractReport
|
|||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
$account = Auth::user()->account;
|
$account = Auth::user()->account;
|
||||||
$status = $this->options['invoice_status'];
|
$statusIds = $this->options['status_ids'];
|
||||||
$exportFormat = $this->options['export_format'];
|
$exportFormat = $this->options['export_format'];
|
||||||
|
|
||||||
$clients = Client::scope()
|
$clients = Client::scope()
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->withArchived()
|
->withArchived()
|
||||||
->with('contacts')
|
->with('contacts')
|
||||||
->with(['invoices' => function ($query) use ($status) {
|
->with(['invoices' => function ($query) use ($statusIds) {
|
||||||
if ($status == 'draft') {
|
|
||||||
$query->whereIsPublic(false);
|
|
||||||
} elseif (in_array($status, ['paid', 'unpaid', 'sent'])) {
|
|
||||||
$query->whereIsPublic(true);
|
|
||||||
}
|
|
||||||
$query->invoices()
|
$query->invoices()
|
||||||
->withArchived()
|
->withArchived()
|
||||||
|
->statusIds($statusIds)
|
||||||
->where('invoice_date', '>=', $this->startDate)
|
->where('invoice_date', '>=', $this->startDate)
|
||||||
->where('invoice_date', '<=', $this->endDate)
|
->where('invoice_date', '<=', $this->endDate)
|
||||||
->with(['payments' => function ($query) {
|
->with(['payments' => function ($query) {
|
||||||
@ -65,17 +61,12 @@ class InvoiceReport extends AbstractReport
|
|||||||
foreach ($client->invoices as $invoice) {
|
foreach ($client->invoices as $invoice) {
|
||||||
$payments = count($invoice->payments) ? $invoice->payments : [false];
|
$payments = count($invoice->payments) ? $invoice->payments : [false];
|
||||||
foreach ($payments as $payment) {
|
foreach ($payments as $payment) {
|
||||||
if (! $payment && $status == 'paid') {
|
|
||||||
continue;
|
|
||||||
} elseif ($payment && $status == 'unpaid') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$this->data[] = [
|
$this->data[] = [
|
||||||
$this->isExport ? $client->getDisplayName() : $client->present()->link,
|
$this->isExport ? $client->getDisplayName() : $client->present()->link,
|
||||||
$this->isExport ? $invoice->invoice_number : $invoice->present()->link,
|
$this->isExport ? $invoice->invoice_number : $invoice->present()->link,
|
||||||
$invoice->present()->invoice_date,
|
$invoice->present()->invoice_date,
|
||||||
$account->formatMoney($invoice->amount, $client),
|
$account->formatMoney($invoice->amount, $client),
|
||||||
$invoice->present()->status(),
|
$invoice->statusLabel(),
|
||||||
$payment ? $payment->present()->payment_date : '',
|
$payment ? $payment->present()->payment_date : '',
|
||||||
$payment ? $account->formatMoney($payment->getCompletedAmount(), $client) : '',
|
$payment ? $account->formatMoney($payment->getCompletedAmount(), $client) : '',
|
||||||
$payment ? $payment->present()->method : '',
|
$payment ? $payment->present()->method : '',
|
||||||
|
@ -4,6 +4,7 @@ namespace App\Ninja\Reports;
|
|||||||
|
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
use Auth;
|
use Auth;
|
||||||
|
use Utils;
|
||||||
|
|
||||||
class PaymentReport extends AbstractReport
|
class PaymentReport extends AbstractReport
|
||||||
{
|
{
|
||||||
@ -20,6 +21,7 @@ class PaymentReport extends AbstractReport
|
|||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
$account = Auth::user()->account;
|
$account = Auth::user()->account;
|
||||||
|
$currencyType = $this->options['currency_type'];
|
||||||
$invoiceMap = [];
|
$invoiceMap = [];
|
||||||
|
|
||||||
$payments = Payment::scope()
|
$payments = Payment::scope()
|
||||||
@ -39,22 +41,36 @@ class PaymentReport extends AbstractReport
|
|||||||
foreach ($payments->get() as $payment) {
|
foreach ($payments->get() as $payment) {
|
||||||
$invoice = $payment->invoice;
|
$invoice = $payment->invoice;
|
||||||
$client = $payment->client;
|
$client = $payment->client;
|
||||||
|
$amount = $payment->getCompletedAmount();
|
||||||
|
|
||||||
|
if ($currencyType == 'converted') {
|
||||||
|
$amount *= $payment->exchange_rate;
|
||||||
|
$this->addToTotals($payment->exchange_currency_id, 'paid', $amount);
|
||||||
|
$amount = Utils::formatMoney($amount, $payment->exchange_currency_id);
|
||||||
|
} else {
|
||||||
|
$this->addToTotals($client->currency_id, 'paid', $amount);
|
||||||
|
$amount = $account->formatMoney($amount, $client);
|
||||||
|
}
|
||||||
|
|
||||||
$this->data[] = [
|
$this->data[] = [
|
||||||
$this->isExport ? $client->getDisplayName() : $client->present()->link,
|
$this->isExport ? $client->getDisplayName() : $client->present()->link,
|
||||||
$this->isExport ? $invoice->invoice_number : $invoice->present()->link,
|
$this->isExport ? $invoice->invoice_number : $invoice->present()->link,
|
||||||
$invoice->present()->invoice_date,
|
$invoice->present()->invoice_date,
|
||||||
$account->formatMoney($invoice->amount, $client),
|
$account->formatMoney($invoice->amount, $client),
|
||||||
$payment->present()->payment_date,
|
$payment->present()->payment_date,
|
||||||
$account->formatMoney($payment->getCompletedAmount(), $client),
|
$amount,
|
||||||
$payment->present()->method,
|
$payment->present()->method,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (! isset($invoiceMap[$invoice->id])) {
|
if (! isset($invoiceMap[$invoice->id])) {
|
||||||
$this->addToTotals($client->currency_id, 'amount', $invoice->amount);
|
|
||||||
$invoiceMap[$invoice->id] = true;
|
$invoiceMap[$invoice->id] = true;
|
||||||
}
|
|
||||||
|
|
||||||
$this->addToTotals($client->currency_id, 'paid', $payment->getCompletedAmount());
|
if ($currencyType == 'converted') {
|
||||||
|
$this->addToTotals($payment->exchange_currency_id, 'amount', $invoice->amount * $payment->exchange_rate);
|
||||||
|
} else {
|
||||||
|
$this->addToTotals($client->currency_id, 'amount', $invoice->amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ class ProductReport extends AbstractReport
|
|||||||
'invoice_number',
|
'invoice_number',
|
||||||
'invoice_date',
|
'invoice_date',
|
||||||
'product',
|
'product',
|
||||||
|
'description',
|
||||||
'qty',
|
'qty',
|
||||||
'cost',
|
'cost',
|
||||||
//'tax_rate1',
|
//'tax_rate1',
|
||||||
@ -22,20 +23,16 @@ class ProductReport extends AbstractReport
|
|||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
$account = Auth::user()->account;
|
$account = Auth::user()->account;
|
||||||
$status = $this->options['invoice_status'];
|
$statusIds = $this->options['status_ids'];
|
||||||
|
|
||||||
$clients = Client::scope()
|
$clients = Client::scope()
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->withArchived()
|
->withArchived()
|
||||||
->with('contacts')
|
->with('contacts')
|
||||||
->with(['invoices' => function ($query) use ($status) {
|
->with(['invoices' => function ($query) use ($statusIds) {
|
||||||
if ($status == 'draft') {
|
|
||||||
$query->whereIsPublic(false);
|
|
||||||
} elseif (in_array($status, ['paid', 'unpaid', 'sent'])) {
|
|
||||||
$query->whereIsPublic(true);
|
|
||||||
}
|
|
||||||
$query->invoices()
|
$query->invoices()
|
||||||
->withArchived()
|
->withArchived()
|
||||||
|
->statusIds($statusIds)
|
||||||
->where('invoice_date', '>=', $this->startDate)
|
->where('invoice_date', '>=', $this->startDate)
|
||||||
->where('invoice_date', '<=', $this->endDate)
|
->where('invoice_date', '<=', $this->endDate)
|
||||||
->with(['invoice_items']);
|
->with(['invoice_items']);
|
||||||
@ -43,17 +40,13 @@ class ProductReport extends AbstractReport
|
|||||||
|
|
||||||
foreach ($clients->get() as $client) {
|
foreach ($clients->get() as $client) {
|
||||||
foreach ($client->invoices as $invoice) {
|
foreach ($client->invoices as $invoice) {
|
||||||
if (! $invoice->isPaid() && $status == 'paid') {
|
|
||||||
continue;
|
|
||||||
} elseif ($invoice->isPaid() && $status == 'unpaid') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
foreach ($invoice->invoice_items as $item) {
|
foreach ($invoice->invoice_items as $item) {
|
||||||
$this->data[] = [
|
$this->data[] = [
|
||||||
$this->isExport ? $client->getDisplayName() : $client->present()->link,
|
$this->isExport ? $client->getDisplayName() : $client->present()->link,
|
||||||
$this->isExport ? $invoice->invoice_number : $invoice->present()->link,
|
$this->isExport ? $invoice->invoice_number : $invoice->present()->link,
|
||||||
$invoice->present()->invoice_date,
|
$invoice->present()->invoice_date,
|
||||||
$item->product_key,
|
$item->product_key,
|
||||||
|
$this->isExport ? $item->notes : $item->present()->notes,
|
||||||
Utils::roundSignificant($item->qty, 0),
|
Utils::roundSignificant($item->qty, 0),
|
||||||
Utils::roundSignificant($item->cost, 2),
|
Utils::roundSignificant($item->cost, 2),
|
||||||
];
|
];
|
||||||
|
@ -59,7 +59,7 @@ class ProfitAndLossReport extends AbstractReport
|
|||||||
$this->data[] = [
|
$this->data[] = [
|
||||||
trans('texts.expense'),
|
trans('texts.expense'),
|
||||||
$client ? ($this->isExport ? $client->getDisplayName() : $client->present()->link) : '',
|
$client ? ($this->isExport ? $client->getDisplayName() : $client->present()->link) : '',
|
||||||
$expense->present()->amount,
|
'-' . $expense->present()->amount,
|
||||||
$expense->present()->expense_date,
|
$expense->present()->expense_date,
|
||||||
$expense->present()->category,
|
$expense->present()->category,
|
||||||
];
|
];
|
||||||
|
@ -60,6 +60,7 @@ class AccountRepository
|
|||||||
$account->ip = Request::getClientIp();
|
$account->ip = Request::getClientIp();
|
||||||
$account->account_key = strtolower(str_random(RANDOM_KEY_LENGTH));
|
$account->account_key = strtolower(str_random(RANDOM_KEY_LENGTH));
|
||||||
$account->company_id = $company->id;
|
$account->company_id = $company->id;
|
||||||
|
$account->currency_id = DEFAULT_CURRENCY;
|
||||||
|
|
||||||
// Set default language/currency based on IP
|
// Set default language/currency based on IP
|
||||||
if (\Cache::get('currencies')) {
|
if (\Cache::get('currencies')) {
|
||||||
@ -151,12 +152,17 @@ class AccountRepository
|
|||||||
if ($user->hasPermission('view_all')) {
|
if ($user->hasPermission('view_all')) {
|
||||||
$clients = Client::scope()
|
$clients = Client::scope()
|
||||||
->with('contacts', 'invoices')
|
->with('contacts', 'invoices')
|
||||||
->get();
|
->withArchived()
|
||||||
|
->with(['contacts', 'invoices' => function ($query) use ($user) {
|
||||||
|
$query->withArchived();
|
||||||
|
}])->get();
|
||||||
} else {
|
} else {
|
||||||
$clients = Client::scope()
|
$clients = Client::scope()
|
||||||
->where('user_id', '=', $user->id)
|
->where('user_id', '=', $user->id)
|
||||||
|
->withArchived()
|
||||||
->with(['contacts', 'invoices' => function ($query) use ($user) {
|
->with(['contacts', 'invoices' => function ($query) use ($user) {
|
||||||
$query->where('user_id', '=', $user->id);
|
$query->withArchived()
|
||||||
|
->where('user_id', '=', $user->id);
|
||||||
}])->get();
|
}])->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ class DashboardRepository
|
|||||||
$records->select(DB::raw('sum(expenses.amount + (expenses.amount * expenses.tax_rate1 / 100) + (expenses.amount * expenses.tax_rate2 / 100)) as total, count(expenses.id) as count, '.$timeframe.' as '.$groupBy));
|
$records->select(DB::raw('sum(expenses.amount + (expenses.amount * expenses.tax_rate1 / 100) + (expenses.amount * expenses.tax_rate2 / 100)) as total, count(expenses.id) as count, '.$timeframe.' as '.$groupBy));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $records->get();
|
return $records->get()->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function totals($accountId, $userId, $viewAll)
|
public function totals($accountId, $userId, $viewAll)
|
||||||
|
@ -607,10 +607,12 @@ class InvoiceRepository extends BaseRepository
|
|||||||
$total += $invoice->custom_value2;
|
$total += $invoice->custom_value2;
|
||||||
}
|
}
|
||||||
|
|
||||||
$taxAmount1 = round($total * ($invoice->tax_rate1 ? $invoice->tax_rate1 : 0) / 100, 2);
|
if (! $account->inclusive_taxes) {
|
||||||
$taxAmount2 = round($total * ($invoice->tax_rate2 ? $invoice->tax_rate2 : 0) / 100, 2);
|
$taxAmount1 = round($total * ($invoice->tax_rate1 ? $invoice->tax_rate1 : 0) / 100, 2);
|
||||||
$total = round($total + $taxAmount1 + $taxAmount2, 2);
|
$taxAmount2 = round($total * ($invoice->tax_rate2 ? $invoice->tax_rate2 : 0) / 100, 2);
|
||||||
$total += $itemTax;
|
$total = round($total + $taxAmount1 + $taxAmount2, 2);
|
||||||
|
$total += $itemTax;
|
||||||
|
}
|
||||||
|
|
||||||
// custom fields not charged taxes
|
// custom fields not charged taxes
|
||||||
if ($invoice->custom_value1 && ! $invoice->custom_taxes1) {
|
if ($invoice->custom_value1 && ! $invoice->custom_taxes1) {
|
||||||
@ -1174,7 +1176,10 @@ class InvoiceRepository extends BaseRepository
|
|||||||
|
|
||||||
$sql = implode(' OR ', $dates);
|
$sql = implode(' OR ', $dates);
|
||||||
$invoices = Invoice::invoiceType(INVOICE_TYPE_STANDARD)
|
$invoices = Invoice::invoiceType(INVOICE_TYPE_STANDARD)
|
||||||
->with('invoice_items')
|
->with('client', 'invoice_items')
|
||||||
|
->whereHas('client', function ($query) {
|
||||||
|
$query->whereSendReminders(true);
|
||||||
|
})
|
||||||
->whereAccountId($account->id)
|
->whereAccountId($account->id)
|
||||||
->where('balance', '>', 0)
|
->where('balance', '>', 0)
|
||||||
->where('is_recurring', '=', false)
|
->where('is_recurring', '=', false)
|
||||||
|
@ -64,6 +64,8 @@ class PaymentRepository extends BaseRepository
|
|||||||
'payments.routing_number',
|
'payments.routing_number',
|
||||||
'payments.bank_name',
|
'payments.bank_name',
|
||||||
'payments.private_notes',
|
'payments.private_notes',
|
||||||
|
'payments.exchange_rate',
|
||||||
|
'payments.exchange_currency_id',
|
||||||
'invoices.is_deleted as invoice_is_deleted',
|
'invoices.is_deleted as invoice_is_deleted',
|
||||||
'gateways.name as gateway_name',
|
'gateways.name as gateway_name',
|
||||||
'gateways.id as gateway_id',
|
'gateways.id as gateway_id',
|
||||||
@ -187,12 +189,7 @@ class PaymentRepository extends BaseRepository
|
|||||||
$payment->payment_date = date('Y-m-d');
|
$payment->payment_date = date('Y-m-d');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($input['transaction_reference'])) {
|
$payment->fill($input);
|
||||||
$payment->transaction_reference = trim($input['transaction_reference']);
|
|
||||||
}
|
|
||||||
if (isset($input['private_notes'])) {
|
|
||||||
$payment->private_notes = trim($input['private_notes']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $publicId) {
|
if (! $publicId) {
|
||||||
$clientId = $input['client_id'];
|
$clientId = $input['client_id'];
|
||||||
|
29
app/Ninja/Repositories/SubscriptionRepository.php
Normal file
29
app/Ninja/Repositories/SubscriptionRepository.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Ninja\Repositories;
|
||||||
|
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use DB;
|
||||||
|
|
||||||
|
class SubscriptionRepository extends BaseRepository
|
||||||
|
{
|
||||||
|
public function getClassName()
|
||||||
|
{
|
||||||
|
return 'App\Models\Subscription';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($accountId)
|
||||||
|
{
|
||||||
|
$query = DB::table('subscriptions')
|
||||||
|
->where('subscriptions.account_id', '=', $accountId)
|
||||||
|
->whereNull('subscriptions.deleted_at')
|
||||||
|
->select(
|
||||||
|
'subscriptions.public_id',
|
||||||
|
'subscriptions.target_url as target',
|
||||||
|
'subscriptions.event_id as event',
|
||||||
|
'subscriptions.deleted_at'
|
||||||
|
);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ use App\Models\Task;
|
|||||||
use Auth;
|
use Auth;
|
||||||
use Session;
|
use Session;
|
||||||
use DB;
|
use DB;
|
||||||
|
use Utils;
|
||||||
|
|
||||||
class TaskRepository extends BaseRepository
|
class TaskRepository extends BaseRepository
|
||||||
{
|
{
|
||||||
@ -101,6 +102,38 @@ class TaskRepository extends BaseRepository
|
|||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getClientDatatable($clientId)
|
||||||
|
{
|
||||||
|
$query = DB::table('tasks')
|
||||||
|
->leftJoin('projects', 'projects.id', '=', 'tasks.project_id')
|
||||||
|
->where('tasks.client_id', '=', $clientId)
|
||||||
|
->where('tasks.is_deleted', '=', false)
|
||||||
|
->whereNull('tasks.invoice_id')
|
||||||
|
->select(
|
||||||
|
'tasks.description',
|
||||||
|
'tasks.time_log',
|
||||||
|
'tasks.time_log as duration',
|
||||||
|
DB::raw("SUBSTRING(time_log, 3, 10) date"),
|
||||||
|
'projects.name as project'
|
||||||
|
);
|
||||||
|
|
||||||
|
$table = \Datatable::query($query)
|
||||||
|
->addColumn('project', function ($model) {
|
||||||
|
return $model->project;
|
||||||
|
})
|
||||||
|
->addColumn('date', function ($model) {
|
||||||
|
return Task::calcStartTime($model);
|
||||||
|
})
|
||||||
|
->addColumn('duration', function ($model) {
|
||||||
|
return Utils::formatTime(Task::calcDuration($model));
|
||||||
|
})
|
||||||
|
->addColumn('description', function ($model) {
|
||||||
|
return $model->description;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $table->make();
|
||||||
|
}
|
||||||
|
|
||||||
public function save($publicId, $data, $task = null)
|
public function save($publicId, $data, $task = null)
|
||||||
{
|
{
|
||||||
if ($task) {
|
if ($task) {
|
||||||
|
@ -43,6 +43,12 @@ class AccountEmailSettingsTransformer extends EntityTransformer
|
|||||||
'email_template_reminder1' => $settings->email_template_reminder1,
|
'email_template_reminder1' => $settings->email_template_reminder1,
|
||||||
'email_template_reminder2' => $settings->email_template_reminder2,
|
'email_template_reminder2' => $settings->email_template_reminder2,
|
||||||
'email_template_reminder3' => $settings->email_template_reminder3,
|
'email_template_reminder3' => $settings->email_template_reminder3,
|
||||||
|
'late_fee1_amount' => $settings->late_fee1_amount,
|
||||||
|
'late_fee1_percent' => $settings->late_fee1_percent,
|
||||||
|
'late_fee2_amount' => $settings->late_fee2_amount,
|
||||||
|
'late_fee2_percent' => $settings->late_fee2_percent,
|
||||||
|
'late_fee3_amount' => $settings->late_fee3_amount,
|
||||||
|
'late_fee3_percent' => $settings->late_fee3_percent,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,6 +275,7 @@ class AccountTransformer extends EntityTransformer
|
|||||||
'custom_contact_label1' => $account->custom_contact_label1,
|
'custom_contact_label1' => $account->custom_contact_label1,
|
||||||
'custom_contact_label2' => $account->custom_contact_label2,
|
'custom_contact_label2' => $account->custom_contact_label2,
|
||||||
'task_rate' => (float) $account->task_rate,
|
'task_rate' => (float) $account->task_rate,
|
||||||
|
'inclusive_taxes' => (bool) $account->inclusive_taxes,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,15 @@ class ClientTransformer extends EntityTransformer
|
|||||||
* @SWG\Property(property="id_number", type="string", example="123456")
|
* @SWG\Property(property="id_number", type="string", example="123456")
|
||||||
* @SWG\Property(property="language_id", type="integer", example=1)
|
* @SWG\Property(property="language_id", type="integer", example=1)
|
||||||
* @SWG\Property(property="task_rate", type="number", format="float", example=10)
|
* @SWG\Property(property="task_rate", type="number", format="float", example=10)
|
||||||
|
* @SWG\Property(property="shipping_address1", type="string", example="10 Main St.")
|
||||||
|
* @SWG\Property(property="shipping_address2", type="string", example="1st Floor")
|
||||||
|
* @SWG\Property(property="shipping_city", type="string", example="New York")
|
||||||
|
* @SWG\Property(property="shipping_state", type="string", example="NY")
|
||||||
|
* @SWG\Property(property="shipping_postal_code", type="string", example=10010)
|
||||||
|
* @SWG\Property(property="shipping_country_id", type="integer", example=840)
|
||||||
|
* @SWG\Property(property="show_tasks_in_portal", type="boolean", example=false)
|
||||||
|
* @SWG\Property(property="send_reminders", type="boolean", example=false)
|
||||||
|
* @SWG\Property(property="credit_number_counter", type="integer", example=1)
|
||||||
*/
|
*/
|
||||||
protected $defaultIncludes = [
|
protected $defaultIncludes = [
|
||||||
'contacts',
|
'contacts',
|
||||||
@ -137,6 +146,15 @@ class ClientTransformer extends EntityTransformer
|
|||||||
'invoice_number_counter' => (int) $client->invoice_number_counter,
|
'invoice_number_counter' => (int) $client->invoice_number_counter,
|
||||||
'quote_number_counter' => (int) $client->quote_number_counter,
|
'quote_number_counter' => (int) $client->quote_number_counter,
|
||||||
'task_rate' => (float) $client->task_rate,
|
'task_rate' => (float) $client->task_rate,
|
||||||
|
'shipping_address1' => $client->shipping_address1,
|
||||||
|
'shipping_address2' => $client->shipping_address2,
|
||||||
|
'shipping_city' => $client->shipping_city,
|
||||||
|
'shipping_state' => $client->shipping_state,
|
||||||
|
'shipping_postal_code' => $client->shipping_postal_code,
|
||||||
|
'shipping_country_id' => (int) $client->shipping_country_id,
|
||||||
|
'show_tasks_in_portal' => (bool) $client->show_tasks_in_portal,
|
||||||
|
'send_reminders' => (bool) $client->send_reminders,
|
||||||
|
'credit_number_counter' => (int) $client->credit_number_counter,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ class DocumentTransformer extends EntityTransformer
|
|||||||
'invoice_id' => $document->invoice_id && $document->invoice ? (int) $document->invoice->public_id : null,
|
'invoice_id' => $document->invoice_id && $document->invoice ? (int) $document->invoice->public_id : null,
|
||||||
'expense_id' => $document->expense_id && $document->expense ? (int) $document->expense->public_id : null,
|
'expense_id' => $document->expense_id && $document->expense ? (int) $document->expense->public_id : null,
|
||||||
'updated_at' => $this->getTimestamp($document->updated_at),
|
'updated_at' => $this->getTimestamp($document->updated_at),
|
||||||
|
'is_default' => (bool) $document->is_default,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,8 @@ class PaymentTransformer extends EntityTransformer
|
|||||||
'invoice_id' => (int) ($this->invoice ? $this->invoice->public_id : $payment->invoice->public_id),
|
'invoice_id' => (int) ($this->invoice ? $this->invoice->public_id : $payment->invoice->public_id),
|
||||||
'invoice_number' => $this->invoice ? $this->invoice->invoice_number : $payment->invoice->invoice_number,
|
'invoice_number' => $this->invoice ? $this->invoice->invoice_number : $payment->invoice->invoice_number,
|
||||||
'private_notes' => $payment->private_notes,
|
'private_notes' => $payment->private_notes,
|
||||||
|
'exchange_rate' => (float) $payment->exchange_rate,
|
||||||
|
'exchange_currency_id' => (int) $payment->exchange_currency_id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
app/Policies/SubscriptionPolicy.php
Normal file
18
app/Policies/SubscriptionPolicy.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class SubscriptionPolicy extends EntityPolicy
|
||||||
|
{
|
||||||
|
public static function edit(User $user, $item)
|
||||||
|
{
|
||||||
|
return $user->hasPermission('admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create(User $user, $item)
|
||||||
|
{
|
||||||
|
return $user->hasPermission('admin');
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,8 @@ use Utils;
|
|||||||
use Validator;
|
use Validator;
|
||||||
use Queue;
|
use Queue;
|
||||||
use Illuminate\Queue\Events\JobProcessing;
|
use Illuminate\Queue\Events\JobProcessing;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AppServiceProvider.
|
* Class AppServiceProvider.
|
||||||
@ -23,7 +25,9 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
// support selecting job database
|
Route::singularResourceParameters(false);
|
||||||
|
|
||||||
|
// support selecting job database
|
||||||
Queue::before(function (JobProcessing $event) {
|
Queue::before(function (JobProcessing $event) {
|
||||||
$body = $event->job->getRawBody();
|
$body = $event->job->getRawBody();
|
||||||
preg_match('/db-ninja-[\d+]/', $body, $matches);
|
preg_match('/db-ninja-[\d+]/', $body, $matches);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Gate;
|
||||||
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
|
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
|
||||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ class AuthServiceProvider extends ServiceProvider
|
|||||||
\App\Models\TaxRate::class => \App\Policies\TaxRatePolicy::class,
|
\App\Models\TaxRate::class => \App\Policies\TaxRatePolicy::class,
|
||||||
\App\Models\AccountGateway::class => \App\Policies\AccountGatewayPolicy::class,
|
\App\Models\AccountGateway::class => \App\Policies\AccountGatewayPolicy::class,
|
||||||
\App\Models\AccountToken::class => \App\Policies\TokenPolicy::class,
|
\App\Models\AccountToken::class => \App\Policies\TokenPolicy::class,
|
||||||
|
\App\Models\Subscription::class => \App\Policies\SubscriptionPolicy::class,
|
||||||
\App\Models\BankAccount::class => \App\Policies\BankAccountPolicy::class,
|
\App\Models\BankAccount::class => \App\Policies\BankAccountPolicy::class,
|
||||||
\App\Models\PaymentTerm::class => \App\Policies\PaymentTermPolicy::class,
|
\App\Models\PaymentTerm::class => \App\Policies\PaymentTermPolicy::class,
|
||||||
\App\Models\Project::class => \App\Policies\ProjectPolicy::class,
|
\App\Models\Project::class => \App\Policies\ProjectPolicy::class,
|
||||||
@ -41,12 +43,12 @@ class AuthServiceProvider extends ServiceProvider
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function boot(GateContract $gate)
|
public function boot()
|
||||||
{
|
{
|
||||||
foreach (get_class_methods(new \App\Policies\GenericEntityPolicy()) as $method) {
|
foreach (get_class_methods(new \App\Policies\GenericEntityPolicy()) as $method) {
|
||||||
$gate->define($method, "App\Policies\GenericEntityPolicy@{$method}");
|
Gate::define($method, "App\Policies\GenericEntityPolicy@{$method}");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->registerPolicies($gate);
|
$this->registerPolicies();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
|
|
||||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||||
|
|
||||||
class EventServiceProvider extends ServiceProvider
|
class EventServiceProvider extends ServiceProvider
|
||||||
@ -222,9 +221,9 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function boot(DispatcherContract $events)
|
public function boot()
|
||||||
{
|
{
|
||||||
parent::boot($events);
|
parent::boot();
|
||||||
|
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
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