1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 16:31:33 +02:00

Merge branch 'release-3.0.0'

This commit is contained in:
Hillel Coren 2017-01-23 21:55:55 +02:00
commit 7a744680ae
258 changed files with 11918 additions and 5179 deletions

View File

@ -26,9 +26,14 @@ MAILGUN_SECRET=
#POSTMARK_API_TOKEN=
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
#PHANTOMJS_BIN_PATH=/usr/local/bin/phantomjs
LOG=single
REQUIRE_HTTPS=false
API_SECRET=password
IOS_DEVICE=
ANDROID_DEVICE=
FCM_API_TOKEN=
#TRUSTED_PROXIES=
@ -46,6 +51,7 @@ API_SECRET=password
#GOOGLE_CLIENT_SECRET=
#GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google
GOOGLE_MAPS_ENABLED=true
#GOOGLE_MAPS_API_KEY=
#S3_KEY=

View File

@ -6,7 +6,8 @@ php:
# - 5.5.9
# - 5.6
# - 5.6
- 7.0
# - 7.0
- 7.1
# - hhvm
addons:

View File

@ -6,7 +6,6 @@ use App\Ninja\Repositories\AccountRepository;
use App\Services\PaymentService;
use App\Models\Invoice;
use App\Models\Account;
use Exception;
/**
* Class ChargeRenewalInvoices
@ -81,11 +80,10 @@ class ChargeRenewalInvoices extends Command
continue;
}
try {
$this->info("Charging invoice {$invoice->invoice_number}");
$this->paymentService->autoBillInvoice($invoice);
} catch (Exception $exception) {
$this->info('Error: ' . $exception->getMessage());
$this->info("Charging invoice {$invoice->invoice_number}");
if ( ! $this->paymentService->autoBillInvoice($invoice)) {
$this->info('Failed to auto-bill, emailing invoice');
$this->mailer->sendInvoice($invoice);
}
}

View File

@ -2,6 +2,7 @@
use DB;
use Mail;
use Utils;
use Carbon;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
@ -139,20 +140,26 @@ class CheckData extends Command {
ENTITY_VENDOR,
ENTITY_INVOICE,
ENTITY_USER
],
'products' => [
ENTITY_USER,
],
'expense_categories' => [
ENTITY_USER,
],
'projects' => [
ENTITY_USER,
ENTITY_CLIENT,
]
];
foreach ($tables as $table => $entityTypes) {
foreach ($entityTypes as $entityType) {
$tableName = Utils::pluralizeEntityType($entityType);
$records = DB::table($table)
->join("{$entityType}s", "{$entityType}s.id", '=', "{$table}.{$entityType}_id");
if ($entityType != ENTITY_CLIENT) {
$records = $records->join('clients', 'clients.id', '=', "{$table}.client_id");
}
$records = $records->where("{$table}.account_id", '!=', DB::raw("{$entityType}s.account_id"))
->get(["{$table}.id", 'clients.account_id', 'clients.user_id']);
->join($tableName, "{$tableName}.id", '=', "{$table}.{$entityType}_id")
->where("{$table}.account_id", '!=', DB::raw("{$tableName}.account_id"))
->get(["{$table}.id"]);
if (count($records)) {
$this->isValid = false;

582
app/Constants.php Normal file
View File

@ -0,0 +1,582 @@
<?php
if (!defined('APP_NAME'))
{
define('APP_NAME', env('APP_NAME', 'Invoice Ninja'));
define('CONTACT_EMAIL', env('MAIL_FROM_ADDRESS', env('MAIL_USERNAME')));
define('CONTACT_NAME', env('MAIL_FROM_NAME'));
define('SITE_URL', env('APP_URL'));
define('ENV_DEVELOPMENT', 'local');
define('ENV_STAGING', 'staging');
define('RECENTLY_VIEWED', 'recent_history');
define('ENTITY_CLIENT', 'client');
define('ENTITY_CONTACT', 'contact');
define('ENTITY_INVOICE', 'invoice');
define('ENTITY_DOCUMENT', 'document');
define('ENTITY_INVOICE_ITEM', 'invoice_item');
define('ENTITY_INVITATION', 'invitation');
define('ENTITY_RECURRING_INVOICE', 'recurring_invoice');
define('ENTITY_PAYMENT', 'payment');
define('ENTITY_CREDIT', 'credit');
define('ENTITY_QUOTE', 'quote');
define('ENTITY_TASK', 'task');
define('ENTITY_ACCOUNT_GATEWAY', 'account_gateway');
define('ENTITY_USER', 'user');
define('ENTITY_TOKEN', 'token');
define('ENTITY_TAX_RATE', 'tax_rate');
define('ENTITY_PRODUCT', 'product');
define('ENTITY_ACTIVITY', 'activity');
define('ENTITY_VENDOR', 'vendor');
define('ENTITY_VENDOR_ACTIVITY', 'vendor_activity');
define('ENTITY_EXPENSE', 'expense');
define('ENTITY_PAYMENT_TERM', 'payment_term');
define('ENTITY_EXPENSE_ACTIVITY', 'expense_activity');
define('ENTITY_BANK_ACCOUNT', 'bank_account');
define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount');
define('ENTITY_EXPENSE_CATEGORY', 'expense_category');
define('ENTITY_PROJECT', 'project');
define('INVOICE_TYPE_STANDARD', 1);
define('INVOICE_TYPE_QUOTE', 2);
define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user');
define('PERSON_VENDOR_CONTACT','vendorcontact');
define('BASIC_SETTINGS', 'basic_settings');
define('ADVANCED_SETTINGS', 'advanced_settings');
define('ACCOUNT_COMPANY_DETAILS', 'company_details');
define('ACCOUNT_USER_DETAILS', 'user_details');
define('ACCOUNT_LOCALIZATION', 'localization');
define('ACCOUNT_NOTIFICATIONS', 'notifications');
define('ACCOUNT_IMPORT_EXPORT', 'import_export');
define('ACCOUNT_MANAGEMENT', 'account_management');
define('ACCOUNT_PAYMENTS', 'online_payments');
define('ACCOUNT_BANKS', 'bank_accounts');
define('ACCOUNT_IMPORT_EXPENSES', 'import_expenses');
define('ACCOUNT_MAP', 'import_map');
define('ACCOUNT_EXPORT', 'export');
define('ACCOUNT_TAX_RATES', 'tax_rates');
define('ACCOUNT_PRODUCTS', 'products');
define('ACCOUNT_ADVANCED_SETTINGS', 'advanced_settings');
define('ACCOUNT_INVOICE_SETTINGS', 'invoice_settings');
define('ACCOUNT_INVOICE_DESIGN', 'invoice_design');
define('ACCOUNT_CLIENT_PORTAL', 'client_portal');
define('ACCOUNT_EMAIL_SETTINGS', 'email_settings');
define('ACCOUNT_REPORTS', 'reports');
define('ACCOUNT_USER_MANAGEMENT', 'user_management');
define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations');
define('ACCOUNT_TEMPLATES_AND_REMINDERS', 'templates_and_reminders');
define('ACCOUNT_API_TOKENS', 'api_tokens');
define('ACCOUNT_CUSTOMIZE_DESIGN', 'customize_design');
define('ACCOUNT_SYSTEM_SETTINGS', 'system_settings');
define('ACCOUNT_PAYMENT_TERMS','payment_terms');
define('ACTION_RESTORE', 'restore');
define('ACTION_ARCHIVE', 'archive');
define('ACTION_CLONE', 'clone');
define('ACTION_CONVERT', 'convert');
define('ACTION_DELETE', 'delete');
define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);
define('ACTIVITY_TYPE_DELETE_CLIENT', 3);
define('ACTIVITY_TYPE_CREATE_INVOICE', 4);
define('ACTIVITY_TYPE_UPDATE_INVOICE', 5);
define('ACTIVITY_TYPE_EMAIL_INVOICE', 6);
define('ACTIVITY_TYPE_VIEW_INVOICE', 7);
define('ACTIVITY_TYPE_ARCHIVE_INVOICE', 8);
define('ACTIVITY_TYPE_DELETE_INVOICE', 9);
define('ACTIVITY_TYPE_CREATE_PAYMENT', 10);
//define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
//define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16);
define('ACTIVITY_TYPE_DELETE_CREDIT', 17);
define('ACTIVITY_TYPE_CREATE_QUOTE', 18);
define('ACTIVITY_TYPE_UPDATE_QUOTE', 19);
define('ACTIVITY_TYPE_EMAIL_QUOTE', 20);
define('ACTIVITY_TYPE_VIEW_QUOTE', 21);
define('ACTIVITY_TYPE_ARCHIVE_QUOTE', 22);
define('ACTIVITY_TYPE_DELETE_QUOTE', 23);
define('ACTIVITY_TYPE_RESTORE_QUOTE', 24);
define('ACTIVITY_TYPE_RESTORE_INVOICE', 25);
define('ACTIVITY_TYPE_RESTORE_CLIENT', 26);
define('ACTIVITY_TYPE_RESTORE_PAYMENT', 27);
define('ACTIVITY_TYPE_RESTORE_CREDIT', 28);
define('ACTIVITY_TYPE_APPROVE_QUOTE', 29);
define('ACTIVITY_TYPE_CREATE_VENDOR', 30);
define('ACTIVITY_TYPE_ARCHIVE_VENDOR', 31);
define('ACTIVITY_TYPE_DELETE_VENDOR', 32);
define('ACTIVITY_TYPE_RESTORE_VENDOR', 33);
define('ACTIVITY_TYPE_CREATE_EXPENSE', 34);
define('ACTIVITY_TYPE_ARCHIVE_EXPENSE', 35);
define('ACTIVITY_TYPE_DELETE_EXPENSE', 36);
define('ACTIVITY_TYPE_RESTORE_EXPENSE', 37);
define('ACTIVITY_TYPE_VOIDED_PAYMENT', 39);
define('ACTIVITY_TYPE_REFUNDED_PAYMENT', 40);
define('ACTIVITY_TYPE_FAILED_PAYMENT', 41);
define('ACTIVITY_TYPE_CREATE_TASK', 42);
define('ACTIVITY_TYPE_UPDATE_TASK', 43);
define('ACTIVITY_TYPE_ARCHIVE_TASK', 44);
define('ACTIVITY_TYPE_DELETE_TASK', 45);
define('ACTIVITY_TYPE_RESTORE_TASK', 46);
define('ACTIVITY_TYPE_UPDATE_EXPENSE', 47);
define('DEFAULT_INVOICE_NUMBER', '0001');
define('RECENTLY_VIEWED_LIMIT', 20);
define('LOGGED_ERROR_LIMIT', 100);
define('RANDOM_KEY_LENGTH', 32);
define('MAX_NUM_USERS', 20);
define('MAX_IMPORT_ROWS', 1000);
define('MAX_SUBDOMAIN_LENGTH', 30);
define('MAX_IFRAME_URL_LENGTH', 250);
define('MAX_LOGO_FILE_SIZE', 200); // KB
define('MAX_FAILED_LOGINS', 10);
define('MAX_INVOICE_ITEMS', env('MAX_INVOICE_ITEMS', 100));
define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000));// KB
define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000));// Total KB
define('MAX_ZIP_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 30000));// Total KB (uncompressed)
define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 300));// pixels
define('DEFAULT_FONT_SIZE', 9);
define('DEFAULT_HEADER_FONT', 1);// Roboto
define('DEFAULT_BODY_FONT', 1);// Roboto
define('DEFAULT_SEND_RECURRING_HOUR', 8);
define('IMPORT_CSV', 'CSV');
define('IMPORT_JSON', 'JSON');
define('IMPORT_FRESHBOOKS', 'FreshBooks');
define('IMPORT_WAVE', 'Wave');
define('IMPORT_RONIN', 'Ronin');
define('IMPORT_HIVEAGE', 'Hiveage');
define('IMPORT_ZOHO', 'Zoho');
define('IMPORT_NUTCACHE', 'Nutcache');
define('IMPORT_INVOICEABLE', 'Invoiceable');
define('IMPORT_HARVEST', 'Harvest');
define('MAX_NUM_CLIENTS', 100);
define('MAX_NUM_CLIENTS_PRO', 20000);
define('MAX_NUM_CLIENTS_LEGACY', 500);
define('MAX_INVOICE_AMOUNT', 1000000000);
define('LEGACY_CUTOFF', 57800);
define('ERROR_DELAY', 3);
define('MAX_NUM_VENDORS', 100);
define('MAX_NUM_VENDORS_PRO', 20000);
define('STATUS_ACTIVE', 'active');
define('STATUS_ARCHIVED', 'archived');
define('STATUS_DELETED', 'deleted');
define('INVOICE_STATUS_DRAFT', 1);
define('INVOICE_STATUS_SENT', 2);
define('INVOICE_STATUS_VIEWED', 3);
define('INVOICE_STATUS_APPROVED', 4);
define('INVOICE_STATUS_PARTIAL', 5);
define('INVOICE_STATUS_PAID', 6);
define('INVOICE_STATUS_OVERDUE', 7);
define('PAYMENT_STATUS_PENDING', 1);
define('PAYMENT_STATUS_VOIDED', 2);
define('PAYMENT_STATUS_FAILED', 3);
define('PAYMENT_STATUS_COMPLETED', 4);
define('PAYMENT_STATUS_PARTIALLY_REFUNDED', 5);
define('PAYMENT_STATUS_REFUNDED', 6);
define('TASK_STATUS_LOGGED', 1);
define('TASK_STATUS_RUNNING', 2);
define('TASK_STATUS_INVOICED', 3);
define('TASK_STATUS_PAID', 4);
define('EXPENSE_STATUS_LOGGED', 1);
define('EXPENSE_STATUS_INVOICED', 2);
define('EXPENSE_STATUS_PAID', 3);
define('CUSTOM_DESIGN', 11);
define('FREQUENCY_WEEKLY', 1);
define('FREQUENCY_TWO_WEEKS', 2);
define('FREQUENCY_FOUR_WEEKS', 3);
define('FREQUENCY_MONTHLY', 4);
define('FREQUENCY_THREE_MONTHS', 5);
define('FREQUENCY_SIX_MONTHS', 6);
define('FREQUENCY_ANNUALLY', 7);
define('SESSION_TIMEZONE', 'timezone');
define('SESSION_CURRENCY', 'currency');
define('SESSION_CURRENCY_DECORATOR', 'currency_decorator');
define('SESSION_DATE_FORMAT', 'dateFormat');
define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat');
define('SESSION_DATETIME_FORMAT', 'datetimeFormat');
define('SESSION_COUNTER', 'sessionCounter');
define('SESSION_LOCALE', 'sessionLocale');
define('SESSION_USER_ACCOUNTS', 'userAccounts');
define('SESSION_REFERRAL_CODE', 'referralCode');
define('SESSION_LEFT_SIDEBAR', 'showLeftSidebar');
define('SESSION_RIGHT_SIDEBAR', 'showRightSidebar');
define('SESSION_LAST_REQUEST_PAGE', 'SESSION_LAST_REQUEST_PAGE');
define('SESSION_LAST_REQUEST_TIME', 'SESSION_LAST_REQUEST_TIME');
define('CURRENCY_DOLLAR', 1);
define('CURRENCY_EURO', 3);
define('DEFAULT_TIMEZONE', 'US/Eastern');
define('DEFAULT_COUNTRY', 840); // United Stated
define('DEFAULT_CURRENCY', CURRENCY_DOLLAR);
define('DEFAULT_LANGUAGE', 1); // English
define('DEFAULT_DATE_FORMAT', 'M j, Y');
define('DEFAULT_DATE_PICKER_FORMAT', 'M d, yyyy');
define('DEFAULT_DATETIME_FORMAT', 'F j, Y g:i a');
define('DEFAULT_DATETIME_MOMENT_FORMAT', 'MMM D, YYYY h:mm:ss a');
define('DEFAULT_LOCALE', 'en');
define('DEFAULT_MAP_ZOOM', 10);
define('RESULT_SUCCESS', 'success');
define('RESULT_FAILURE', 'failure');
define('PAYMENT_LIBRARY_OMNIPAY', 1);
define('PAYMENT_LIBRARY_PHP_PAYMENTS', 2);
define('GATEWAY_AUTHORIZE_NET', 1);
define('GATEWAY_EWAY', 4);
define('GATEWAY_MOLLIE', 9);
define('GATEWAY_PAYFAST', 13);
define('GATEWAY_PAYPAL_EXPRESS', 17);
define('GATEWAY_PAYPAL_PRO', 18);
define('GATEWAY_SAGE_PAY_DIRECT', 20);
define('GATEWAY_SAGE_PAY_SERVER', 21);
define('GATEWAY_STRIPE', 23);
define('GATEWAY_GOCARDLESS', 6);
define('GATEWAY_TWO_CHECKOUT', 27);
define('GATEWAY_BEANSTREAM', 29);
define('GATEWAY_PSIGATE', 30);
define('GATEWAY_MOOLAH', 31);
define('GATEWAY_BITPAY', 42);
define('GATEWAY_DWOLLA', 43);
define('GATEWAY_CHECKOUT_COM', 47);
define('GATEWAY_CYBERSOURCE', 49);
define('GATEWAY_WEPAY', 60);
define('GATEWAY_BRAINTREE', 61);
define('GATEWAY_CUSTOM', 62);
// The customer exists, but only as a local concept
// The remote gateway doesn't understand the concept of customers
define('CUSTOMER_REFERENCE_LOCAL', 'local');
define('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2);
define('EVENT_CREATE_QUOTE', 3);
define('EVENT_CREATE_PAYMENT', 4);
define('EVENT_CREATE_VENDOR', 5);
define('EVENT_UPDATE_QUOTE', 6);
define('EVENT_DELETE_QUOTE', 7);
define('EVENT_UPDATE_INVOICE', 8);
define('EVENT_DELETE_INVOICE', 9);
define('REQUESTED_PRO_PLAN', 'REQUESTED_PRO_PLAN');
define('DEMO_ACCOUNT_ID', 'DEMO_ACCOUNT_ID');
define('PREV_USER_ID', 'PREV_USER_ID');
define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h');
define('NINJA_GATEWAY_ID', GATEWAY_STRIPE);
define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG');
define('NINJA_WEB_URL', env('NINJA_WEB_URL', 'https://www.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_DATE', '2000-01-01');
define('NINJA_VERSION', '3.0.0' . env('NINJA_VERSION_SUFFIX'));
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
define('SOCIAL_LINK_GITHUB', env('SOCIAL_LINK_GITHUB', 'https://github.com/invoiceninja/invoiceninja/'));
define('NINJA_FORUM_URL', env('NINJA_FORUM_URL', 'https://www.invoiceninja.com/forums/forum/support/'));
define('NINJA_CONTACT_URL', env('NINJA_CONTACT_URL', 'https://www.invoiceninja.com/contact/'));
define('NINJA_FROM_EMAIL', env('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'));
define('NINJA_IOS_APP_URL', 'https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1072566815');
define('RELEASES_URL', env('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja'));
define('ZAPIER_URL', env('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja'));
define('OUTDATE_BROWSER_URL', env('OUTDATE_BROWSER_URL', 'http://browsehappy.com/'));
define('PDFMAKE_DOCS', env('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html'));
define('PHANTOMJS_CLOUD', env('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/api/browser/v2/'));
define('PHP_DATE_FORMATS', env('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php'));
define('REFERRAL_PROGRAM_URL', env('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/referral-program/'));
define('EMAIL_MARKUP_URL', env('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup'));
define('OFX_HOME_URL', env('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'));
define('GOOGLE_ANALYITCS_URL', env('GOOGLE_ANALYITCS_URL', 'https://www.google-analytics.com/collect'));
define('TRANSIFEX_URL', env('TRANSIFEX_URL', 'https://www.transifex.com/invoice-ninja/invoice-ninja'));
define('CHROME_PDF_HELP_URL', 'https://support.google.com/chrome/answer/6213030?hl=en');
define('FIREFOX_PDF_HELP_URL', 'https://support.mozilla.org/en-US/kb/view-pdf-files-firefox');
define('MSBOT_LOGIN_URL', 'https://login.microsoftonline.com/common/oauth2/v2.0/token');
define('MSBOT_LUIS_URL', 'https://api.projectoxford.ai/luis/v1/application');
define('SKYPE_API_URL', 'https://apis.skype.com/v3');
define('MSBOT_STATE_URL', 'https://state.botframework.com/v3');
define('BLANK_IMAGE', 'data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=');
define('COUNT_FREE_DESIGNS', 4);
define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design
define('PRODUCT_ONE_CLICK_INSTALL', 1);
define('PRODUCT_INVOICE_DESIGNS', 2);
define('PRODUCT_WHITE_LABEL', 3);
define('PRODUCT_SELF_HOST', 4);
define('WHITE_LABEL_AFFILIATE_KEY', '92D2J5');
define('INVOICE_DESIGNS_AFFILIATE_KEY', 'T3RS74');
define('SELF_HOST_AFFILIATE_KEY', '8S69AD');
define('PLAN_PRICE_PRO_MONTHLY', env('PLAN_PRICE_PRO_MONTHLY', 8));
define('PLAN_PRICE_ENTERPRISE_MONTHLY_2', env('PLAN_PRICE_ENTERPRISE_MONTHLY_2', 12));
define('PLAN_PRICE_ENTERPRISE_MONTHLY_5', env('PLAN_PRICE_ENTERPRISE_MONTHLY_5', 18));
define('PLAN_PRICE_ENTERPRISE_MONTHLY_10', env('PLAN_PRICE_ENTERPRISE_MONTHLY_10', 24));
define('PLAN_PRICE_ENTERPRISE_MONTHLY_20', env('PLAN_PRICE_ENTERPRISE_MONTHLY_20', 36));
define('WHITE_LABEL_PRICE', env('WHITE_LABEL_PRICE', 20));
define('INVOICE_DESIGNS_PRICE', env('INVOICE_DESIGNS_PRICE', 10));
define('USER_TYPE_SELF_HOST', 'SELF_HOST');
define('USER_TYPE_CLOUD_HOST', 'CLOUD_HOST');
define('NEW_VERSION_AVAILABLE', 'NEW_VERSION_AVAILABLE');
define('TEST_USERNAME', 'user@example.com');
define('TEST_PASSWORD', 'password');
define('API_SECRET', 'API_SECRET');
define('DEFAULT_API_PAGE_SIZE', 15);
define('MAX_API_PAGE_SIZE', 500);
define('IOS_DEVICE', env('IOS_DEVICE', ''));
define('ANDROID_DEVICE', env('ANDROID_DEVICE', ''));
define('TOKEN_BILLING_DISABLED', 1);
define('TOKEN_BILLING_OPT_IN', 2);
define('TOKEN_BILLING_OPT_OUT', 3);
define('TOKEN_BILLING_ALWAYS', 4);
define('PAYMENT_TYPE_CREDIT', 1);
define('PAYMENT_TYPE_ACH', 5);
define('PAYMENT_TYPE_VISA', 6);
define('PAYMENT_TYPE_MASTERCARD', 7);
define('PAYMENT_TYPE_AMERICAN_EXPRESS', 8);
define('PAYMENT_TYPE_DISCOVER', 9);
define('PAYMENT_TYPE_DINERS', 10);
define('PAYMENT_TYPE_EUROCARD', 11);
define('PAYMENT_TYPE_NOVA', 12);
define('PAYMENT_TYPE_CREDIT_CARD_OTHER', 13);
define('PAYMENT_TYPE_PAYPAL', 14);
define('PAYMENT_TYPE_CARTE_BLANCHE', 17);
define('PAYMENT_TYPE_UNIONPAY', 18);
define('PAYMENT_TYPE_JCB', 19);
define('PAYMENT_TYPE_LASER', 20);
define('PAYMENT_TYPE_MAESTRO', 21);
define('PAYMENT_TYPE_SOLO', 22);
define('PAYMENT_TYPE_SWITCH', 23);
define('PAYMENT_METHOD_STATUS_NEW', 'new');
define('PAYMENT_METHOD_STATUS_VERIFICATION_FAILED', 'verification_failed');
define('PAYMENT_METHOD_STATUS_VERIFIED', 'verified');
define('GATEWAY_TYPE_CREDIT_CARD', 1);
define('GATEWAY_TYPE_BANK_TRANSFER', 2);
define('GATEWAY_TYPE_PAYPAL', 3);
define('GATEWAY_TYPE_BITCOIN', 4);
define('GATEWAY_TYPE_DWOLLA', 5);
define('GATEWAY_TYPE_CUSTOM', 6);
define('GATEWAY_TYPE_TOKEN', 'token');
define('REMINDER1', 'reminder1');
define('REMINDER2', 'reminder2');
define('REMINDER3', 'reminder3');
define('REMINDER_DIRECTION_AFTER', 1);
define('REMINDER_DIRECTION_BEFORE', 2);
define('REMINDER_FIELD_DUE_DATE', 1);
define('REMINDER_FIELD_INVOICE_DATE', 2);
define('FILTER_INVOICE_DATE', 'invoice_date');
define('FILTER_PAYMENT_DATE', 'payment_date');
define('SOCIAL_GOOGLE', 'Google');
define('SOCIAL_FACEBOOK', 'Facebook');
define('SOCIAL_GITHUB', 'GitHub');
define('SOCIAL_LINKEDIN', 'LinkedIn');
define('USER_STATE_ACTIVE', 'active');
define('USER_STATE_PENDING', 'pending');
define('USER_STATE_DISABLED', 'disabled');
define('USER_STATE_ADMIN', 'admin');
define('USER_STATE_OWNER', 'owner');
define('API_SERIALIZER_ARRAY', 'array');
define('API_SERIALIZER_JSON', 'json');
define('EMAIL_DESIGN_PLAIN', 1);
define('EMAIL_DESIGN_LIGHT', 2);
define('EMAIL_DESIGN_DARK', 3);
define('BANK_LIBRARY_OFX', 1);
define('CURRENCY_DECORATOR_CODE', 'code');
define('CURRENCY_DECORATOR_SYMBOL', 'symbol');
define('CURRENCY_DECORATOR_NONE', 'none');
define('RESELLER_REVENUE_SHARE', 'A');
define('RESELLER_LIMITED_USERS', 'B');
define('AUTO_BILL_OFF', 1);
define('AUTO_BILL_OPT_IN', 2);
define('AUTO_BILL_OPT_OUT', 3);
define('AUTO_BILL_ALWAYS', 4);
// These must be lowercase
define('PLAN_FREE', 'free');
define('PLAN_PRO', 'pro');
define('PLAN_ENTERPRISE', 'enterprise');
define('PLAN_WHITE_LABEL', 'white_label');
define('PLAN_TERM_MONTHLY', 'month');
define('PLAN_TERM_YEARLY', 'year');
// Pro
define('FEATURE_CUSTOMIZE_INVOICE_DESIGN', 'customize_invoice_design');
define('FEATURE_REMOVE_CREATED_BY', 'remove_created_by');
define('FEATURE_DIFFERENT_DESIGNS', 'different_designs');
define('FEATURE_EMAIL_TEMPLATES_REMINDERS', 'email_templates_reminders');
define('FEATURE_INVOICE_SETTINGS', 'invoice_settings');
define('FEATURE_CUSTOM_EMAILS', 'custom_emails');
define('FEATURE_PDF_ATTACHMENT', 'pdf_attachment');
define('FEATURE_MORE_INVOICE_DESIGNS', 'more_invoice_designs');
define('FEATURE_QUOTES', 'quotes');
define('FEATURE_TASKS', 'tasks');
define('FEATURE_EXPENSES', 'expenses');
define('FEATURE_REPORTS', 'reports');
define('FEATURE_BUY_NOW_BUTTONS', 'buy_now_buttons');
define('FEATURE_API', 'api');
define('FEATURE_CLIENT_PORTAL_PASSWORD', 'client_portal_password');
define('FEATURE_CUSTOM_URL', 'custom_url');
define('FEATURE_MORE_CLIENTS', 'more_clients'); // No trial allowed
// Whitelabel
define('FEATURE_WHITE_LABEL', 'feature_white_label');
// Enterprise
define('FEATURE_DOCUMENTS', 'documents');
// No Trial allowed
define('FEATURE_USERS', 'users');// Grandfathered for old Pro users
define('FEATURE_USER_PERMISSIONS', 'user_permissions');
// Pro users who started paying on or before this date will be able to manage users
define('PRO_USERS_GRANDFATHER_DEADLINE', '2016-06-04');
define('EXTRAS_GRANDFATHER_COMPANY_ID', 35089);
// WePay
define('WEPAY_PRODUCTION', 'production');
define('WEPAY_STAGE', 'stage');
define('WEPAY_CLIENT_ID', env('WEPAY_CLIENT_ID'));
define('WEPAY_CLIENT_SECRET', env('WEPAY_CLIENT_SECRET'));
define('WEPAY_AUTO_UPDATE', env('WEPAY_AUTO_UPDATE', false));
define('WEPAY_ENVIRONMENT', env('WEPAY_ENVIRONMENT', WEPAY_PRODUCTION));
define('WEPAY_ENABLE_CANADA', env('WEPAY_ENABLE_CANADA', false));
define('WEPAY_THEME', env('WEPAY_THEME','{"name":"Invoice Ninja","primary_color":"0b4d78","secondary_color":"0b4d78","background_color":"f8f8f8","button_color":"33b753"}'));
define('SKYPE_CARD_RECEIPT', 'message/card.receipt');
define('SKYPE_CARD_CAROUSEL', 'message/card.carousel');
define('SKYPE_CARD_HERO', '');
define('BOT_STATE_GET_EMAIL', 'get_email');
define('BOT_STATE_GET_CODE', 'get_code');
define('BOT_STATE_READY', 'ready');
define('SIMILAR_MIN_THRESHOLD', 50);
// https://docs.botframework.com/en-us/csharp/builder/sdkreference/attachments.html
define('SKYPE_BUTTON_OPEN_URL', 'openUrl');
define('SKYPE_BUTTON_IM_BACK', 'imBack');
define('SKYPE_BUTTON_POST_BACK', 'postBack');
define('SKYPE_BUTTON_CALL', 'call'); // "tel:123123123123"
define('SKYPE_BUTTON_PLAY_AUDIO', 'playAudio');
define('SKYPE_BUTTON_PLAY_VIDEO', 'playVideo');
define('SKYPE_BUTTON_SHOW_IMAGE', 'showImage');
define('SKYPE_BUTTON_DOWNLOAD_FILE', 'downloadFile');
define('INVOICE_FIELDS_CLIENT', 'client_fields');
define('INVOICE_FIELDS_INVOICE', 'invoice_fields');
define('INVOICE_FIELDS_ACCOUNT', 'account_fields');
$creditCards = [
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],
4 => ['card' => 'images/credit_cards/Test-AmericanExpress-Icon.png', 'text' => 'American Express'],
8 => ['card' => 'images/credit_cards/Test-Diners-Icon.png', 'text' => 'Diners'],
16 => ['card' => 'images/credit_cards/Test-Discover-Icon.png', 'text' => 'Discover']
];
define('CREDIT_CARDS', serialize($creditCards));
$cachedTables = [
'currencies' => 'App\Models\Currency',
'sizes' => 'App\Models\Size',
'industries' => 'App\Models\Industry',
'timezones' => 'App\Models\Timezone',
'dateFormats' => 'App\Models\DateFormat',
'datetimeFormats' => 'App\Models\DatetimeFormat',
'languages' => 'App\Models\Language',
'paymentTerms' => 'App\Models\PaymentTerm',
'paymentTypes' => 'App\Models\PaymentType',
'countries' => 'App\Models\Country',
'invoiceDesigns' => 'App\Models\InvoiceDesign',
'invoiceStatus' => 'App\Models\InvoiceStatus',
'frequencies' => 'App\Models\Frequency',
'gateways' => 'App\Models\Gateway',
'gatewayTypes' => 'App\Models\GatewayType',
'fonts' => 'App\Models\Font',
'banks' => 'App\Models\Bank',
];
define('CACHED_TABLES', serialize($cachedTables));
// TODO remove these translation functions
function uctrans($text)
{
return ucwords(trans($text));
}
// optional trans: only return the string if it's translated
function otrans($text)
{
$locale = Session::get(SESSION_LOCALE);
if ($locale == 'en') {
return trans($text);
} else {
$string = trans($text);
$english = trans($text, [], 'en');
return $string != $english ? $string : '';
}
}
// include modules in translations
function mtrans($entityType, $text = false)
{
if ( ! $text) {
$text = $entityType;
}
// check if this has been translated in a module language file
if ( ! Utils::isNinjaProd() && $module = Module::find($entityType)) {
$key = "{$module->getLowerName()}::texts.{$text}";
$value = trans($key);
if ($key != $value) {
return $value;
}
}
return trans("texts.{$text}");
}
}

29
app/Constants/Domain.php Normal file
View File

@ -0,0 +1,29 @@
<?php namespace App\Constants;
class Domain
{
const INVOICENINJA_COM = 1;
const INVOICE_SERVICES = 2;
public static function getDomainFromId($id)
{
switch ($id) {
case static::INVOICENINJA_COM:
return 'invoiceninja.com';
case static::INVOICE_SERVICES:
return 'invoice.services';
}
return 'invoiceninja.com';
}
public static function getLinkFromId($id)
{
return 'https://app.' . static::getDomainFromId($id);
}
public static function getEmailFromId($id)
{
return 'maildelivery@' . static::getDomainFromId($id);
}
}

View File

@ -17,14 +17,20 @@ class InvoiceInvitationWasEmailed extends Event
*/
public $invitation;
/**
* @var String
*/
public $notes;
/**
* Create a new event instance.
*
* @param Invitation $invitation
*/
public function __construct(Invitation $invitation)
public function __construct(Invitation $invitation, $notes)
{
$this->invitation = $invitation;
$this->notes = $notes;
}
}

View File

@ -15,14 +15,20 @@ class QuoteInvitationWasEmailed extends Event
*/
public $invitation;
/**
* @var String
*/
public $notes;
/**
* Create a new event instance.
*
* @param Invitation $invitation
*/
public function __construct(Invitation $invitation)
public function __construct(Invitation $invitation, $notes)
{
$this->invitation = $invitation;
$this->notes = $notes;
}
}

View File

@ -27,11 +27,14 @@ class AccountApiController extends BaseAPIController
$this->accountRepo = $accountRepo;
}
public function ping()
public function ping(Request $request)
{
$headers = Utils::getApiHeaders();
return Response::make(RESULT_SUCCESS, 200, $headers);
if(hash_equals(env(API_SECRET),$request->api_secret))
return Response::make(RESULT_SUCCESS, 200, $headers);
else
return $this->errorResponse(['message'=>'API Secret does not match .env variable'], 400);
}
public function register(RegisterRequest $request)

View File

@ -39,6 +39,9 @@ use App\Services\AuthService;
use App\Services\PaymentService;
use App\Http\Requests\UpdateAccountRequest;
use App\Http\Requests\SaveClientPortalSettings;
use App\Http\Requests\SaveEmailSettings;
/**
* Class AccountController
*/
@ -233,6 +236,7 @@ class AccountController extends BaseController
$company->plan_expires = date_create()->modify($term == PLAN_TERM_MONTHLY ? '+1 month' : '+1 year')->format('Y-m-d');
}
$company->trial_plan = null;
$company->plan = $plan;
$company->save();
@ -521,7 +525,7 @@ class AccountController extends BaseController
$data = [
'account' => Auth::user()->account,
'title' => trans('texts.tax_rates'),
'taxRates' => TaxRate::scope()->get(['id', 'name', 'rate']),
'taxRates' => TaxRate::scope()->whereIsInclusive(false)->get(['id', 'name', 'rate']),
];
return View::make('accounts.tax_rates', $data);
@ -555,12 +559,12 @@ class AccountController extends BaseController
$document = new stdClass();
$client->name = 'Sample Client';
$client->address1 = trans('texts.address1');
$client->city = trans('texts.city');
$client->state = trans('texts.state');
$client->postal_code = trans('texts.postal_code');
$client->work_phone = trans('texts.work_phone');
$client->work_email = trans('texts.work_id');
$client->address1 = '10 Main St.';
$client->city = 'New York';
$client->state = 'NY';
$client->postal_code = '10000';
$client->work_phone = '(212) 555-0000';
$client->work_email = 'sample@example.com';
$invoice->invoice_number = '0000';
$invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d'));
@ -713,14 +717,10 @@ class AccountController extends BaseController
return AccountController::export();
} elseif ($section === ACCOUNT_INVOICE_SETTINGS) {
return AccountController::saveInvoiceSettings();
} elseif ($section === ACCOUNT_EMAIL_SETTINGS) {
return AccountController::saveEmailSettings();
} elseif ($section === ACCOUNT_INVOICE_DESIGN) {
return AccountController::saveInvoiceDesign();
} elseif ($section === ACCOUNT_CUSTOMIZE_DESIGN) {
return AccountController::saveCustomizeDesign();
} elseif ($section === ACCOUNT_CLIENT_PORTAL) {
return AccountController::saveClientPortal();
} elseif ($section === ACCOUNT_TEMPLATES_AND_REMINDERS) {
return AccountController::saveEmailTemplates();
} elseif ($section === ACCOUNT_PRODUCTS) {
@ -788,53 +788,30 @@ class AccountController extends BaseController
/**
* @return \Illuminate\Http\RedirectResponse
*/
private function saveClientPortal()
public function saveClientPortalSettings(SaveClientPortalSettings $request)
{
$account = Auth::user()->account;
$account->fill(Input::all());
// Only allowed for pro Invoice Ninja users or white labeled self-hosted users
if (Auth::user()->account->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) {
$input_css = Input::get('client_view_css');
if (Utils::isNinja()) {
// Allow referencing the body element
$input_css = preg_replace('/(?<![a-z0-9\-\_\#\.])body(?![a-z0-9\-\_])/i', '.body', $input_css);
//
// Inspired by http://stackoverflow.com/a/5209050/1721527, dleavitt <https://stackoverflow.com/users/362110/dleavitt>
//
// Create a new configuration object
$config = \HTMLPurifier_Config::createDefault();
$config->set('Filter.ExtractStyleBlocks', true);
$config->set('CSS.AllowImportant', true);
$config->set('CSS.AllowTricky', true);
$config->set('CSS.Trusted', true);
// Create a new purifier instance
$purifier = new \HTMLPurifier($config);
// Wrap our CSS in style tags and pass to purifier.
// we're not actually interested in the html response though
$html = $purifier->purify('<style>'.$input_css.'</style>');
// The "style" blocks are stored seperately
$output_css = $purifier->context->get('StyleBlocks');
// Get the first style block
$sanitized_css = count($output_css) ? $output_css[0] : '';
} else {
$sanitized_css = $input_css;
}
$account->client_view_css = $sanitized_css;
}
$account = $request->user()->account;
$account->fill($request->all());
$account->subdomain = $request->subdomain;
$account->iframe_url = $request->iframe_url;
$account->save();
Session::flash('message', trans('texts.updated_settings'));
return redirect('settings/' . ACCOUNT_CLIENT_PORTAL)
->with('message', trans('texts.updated_settings'));
}
return Redirect::to('settings/'.ACCOUNT_CLIENT_PORTAL);
/**
* @return $this|\Illuminate\Http\RedirectResponse
*/
public function saveEmailSettings(SaveEmailSettings $request)
{
$account = $request->user()->account;
$account->fill($request->all());
$account->bcc_email = $request->bcc_email;
$account->save();
return redirect('settings/' . ACCOUNT_EMAIL_SETTINGS)
->with('message', trans('texts.updated_settings'));
}
/**
@ -904,83 +881,18 @@ class AccountController extends BaseController
return Redirect::to('settings/'.ACCOUNT_PRODUCTS);
}
/**
* @return $this|\Illuminate\Http\RedirectResponse
*/
private function saveEmailSettings()
{
if (Auth::user()->account->hasFeature(FEATURE_CUSTOM_EMAILS)) {
$user = Auth::user();
$subdomain = null;
$iframeURL = null;
$rules = [];
if (Input::get('custom_link') == 'subdomain') {
$subdomain = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', substr(strtolower(Input::get('subdomain')), 0, MAX_SUBDOMAIN_LENGTH));
if (Utils::isNinja()) {
$exclude = [
'www',
'app',
'mail',
'admin',
'blog',
'user',
'contact',
'payment',
'payments',
'billing',
'invoice',
'business',
'owner',
'info',
'ninja',
'docs',
'doc',
'documents'
];
$rules['subdomain'] = "unique:accounts,subdomain,{$user->account_id},id|not_in:" . implode(',', $exclude);
}
} else {
$iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', substr(strtolower(Input::get('iframe_url')), 0, MAX_IFRAME_URL_LENGTH));
$iframeURL = rtrim($iframeURL, '/');
}
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('settings/'.ACCOUNT_EMAIL_SETTINGS)
->withErrors($validator)
->withInput();
} else {
$account = Auth::user()->account;
$account->subdomain = $subdomain;
$account->iframe_url = $iframeURL;
$account->pdf_email_attachment = Input::get('pdf_email_attachment') ? true : false;
$account->document_email_attachment = Input::get('document_email_attachment') ? true : false;
$account->email_design_id = Input::get('email_design_id');
if (Utils::isNinja()) {
$account->enable_email_markup = Input::get('enable_email_markup') ? true : false;
}
$account->save();
Session::flash('message', trans('texts.updated_settings'));
}
}
return Redirect::to('settings/'.ACCOUNT_EMAIL_SETTINGS);
}
/**
* @return $this|\Illuminate\Http\RedirectResponse
*/
private function saveInvoiceSettings()
{
if (Auth::user()->account->hasFeature(FEATURE_INVOICE_SETTINGS)) {
$rules = [
'invoice_number_pattern' => 'has_counter',
'quote_number_pattern' => 'has_counter',
];
$rules = [];
foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_CLIENT] as $entityType) {
if (Input::get("{$entityType}_number_type") == 'pattern') {
$rules["{$entityType}_number_pattern"] = 'has_counter';
}
}
$validator = Validator::make(Input::all(), $rules);
@ -1015,6 +927,10 @@ class AccountController extends BaseController
$account->auto_convert_quote = Input::get('auto_convert_quote');
$account->recurring_invoice_number_prefix = Input::get('recurring_invoice_number_prefix');
$account->client_number_prefix = trim(Input::get('client_number_prefix'));
$account->client_number_pattern = trim(Input::get('client_number_pattern'));
$account->client_number_counter = Input::get('client_number_counter');
if (Input::has('recurring_hour')) {
$account->recurring_hour = Input::get('recurring_hour');
}
@ -1023,20 +939,14 @@ class AccountController extends BaseController
$account->quote_number_counter = Input::get('quote_number_counter');
}
if (Input::get('invoice_number_type') == 'prefix') {
$account->invoice_number_prefix = trim(Input::get('invoice_number_prefix'));
$account->invoice_number_pattern = null;
} else {
$account->invoice_number_pattern = trim(Input::get('invoice_number_pattern'));
$account->invoice_number_prefix = null;
}
if (Input::get('quote_number_type') == 'prefix') {
$account->quote_number_prefix = trim(Input::get('quote_number_prefix'));
$account->quote_number_pattern = null;
} else {
$account->quote_number_pattern = trim(Input::get('quote_number_pattern'));
$account->quote_number_prefix = null;
foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_CLIENT] as $entityType) {
if (Input::get("{$entityType}_number_type") == 'prefix') {
$account->{"{$entityType}_number_prefix"} = trim(Input::get("{$entityType}_number_prefix"));
$account->{"{$entityType}_number_pattern"} = null;
} else {
$account->{"{$entityType}_number_pattern"} = trim(Input::get("{$entityType}_number_pattern"));
$account->{"{$entityType}_number_prefix"} = null;
}
}
if (!$account->share_counter
@ -1218,6 +1128,13 @@ class AccountController extends BaseController
$user->email = trim(strtolower(Input::get('email')));
$user->phone = trim(Input::get('phone'));
if ( ! Auth::user()->is_admin) {
$user->notify_sent = Input::get('notify_sent');
$user->notify_viewed = Input::get('notify_viewed');
$user->notify_paid = Input::get('notify_paid');
$user->notify_approved = Input::get('notify_approved');
}
if (Utils::isNinja()) {
if (Input::get('referral_code') && !$user->referral_code) {
$user->referral_code = $this->accountRepo->getReferralCode();
@ -1461,22 +1378,6 @@ class AccountController extends BaseController
return Redirect::to('/settings/'.ACCOUNT_USER_DETAILS)->with('message', trans('texts.confirmation_resent'));
}
/**
* @param $plan
* @return \Illuminate\Http\RedirectResponse
*/
public function startTrial($plan)
{
/** @var \App\Models\User $user */
$user = Auth::user();
if ($user->isEligibleForTrial($plan)) {
$user->account->startTrial($plan);
}
return Redirect::back()->with('message', trans('texts.trial_success'));
}
/**
* @param $section
* @param bool $subSection

View File

@ -266,21 +266,21 @@ class AccountGatewayController extends BaseController
$config->publishableKey = $oldConfig->publishableKey;
}
$plaidClientId = Input::get('plaid_client_id');
$plaidClientId = trim(Input::get('plaid_client_id'));
if ($plaidClientId = str_replace('*', '', $plaidClientId)) {
$config->plaidClientId = $plaidClientId;
} elseif ($oldConfig && property_exists($oldConfig, 'plaidClientId')) {
$config->plaidClientId = $oldConfig->plaidClientId;
}
$plaidSecret = Input::get('plaid_secret');
$plaidSecret = trim(Input::get('plaid_secret'));
if ($plaidSecret = str_replace('*', '', $plaidSecret)) {
$config->plaidSecret = $plaidSecret;
} elseif ($oldConfig && property_exists($oldConfig, 'plaidSecret')) {
$config->plaidSecret = $oldConfig->plaidSecret;
}
$plaidPublicKey = Input::get('plaid_public_key');
$plaidPublicKey = trim(Input::get('plaid_public_key'));
if ($plaidPublicKey = str_replace('*', '', $plaidPublicKey)) {
$config->plaidPublicKey = $plaidPublicKey;
} elseif ($oldConfig && property_exists($oldConfig, 'plaidPublicKey')) {

View File

@ -56,6 +56,7 @@ class AppController extends BaseController
$app = Input::get('app');
$app['key'] = env('APP_KEY') ?: str_random(RANDOM_KEY_LENGTH);
$app['debug'] = Input::get('debug') ? 'true' : 'false';
$app['https'] = Input::get('https') ? 'true' : 'false';
$database = Input::get('database');
$dbType = 'mysql'; // $database['default'];
@ -80,6 +81,7 @@ class AppController extends BaseController
$_ENV['APP_ENV'] = 'production';
$_ENV['APP_DEBUG'] = $app['debug'];
$_ENV['REQUIRE_HTTPS'] = $app['https'];
$_ENV['APP_URL'] = $app['url'];
$_ENV['APP_KEY'] = $app['key'];
$_ENV['APP_CIPHER'] = env('APP_CIPHER', 'AES-256-CBC');
@ -157,6 +159,7 @@ class AppController extends BaseController
$_ENV['APP_URL'] = $app['url'];
$_ENV['APP_DEBUG'] = Input::get('debug') ? 'true' : 'false';
$_ENV['REQUIRE_HTTPS'] = Input::get('https') ? 'true' : 'false';
$_ENV['DB_TYPE'] = 'mysql'; // $db['default'];
$_ENV['DB_HOST'] = $db['type']['host'];
@ -279,7 +282,7 @@ class AppController extends BaseController
// legacy fix: check cipher is in .env file
if ( ! env('APP_CIPHER')) {
$fp = fopen(base_path().'/.env', 'a');
fwrite($fp, "\nAPP_CIPHER=rijndael-128");
fwrite($fp, "\nAPP_CIPHER=AES-256-CBC");
fclose($fp);
}

View File

@ -141,6 +141,7 @@ class AuthController extends Controller
if (Auth::check()) {
Event::fire(new UserLoggedIn());
/*
$users = false;
// we're linking a new account
if ($request->link_accounts && $userId && Auth::user()->id != $userId) {
@ -150,6 +151,9 @@ class AuthController extends Controller
} else {
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
}
*/
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
Session::put(SESSION_USER_ACCOUNTS, $users);
} elseif ($user) {

View File

@ -61,9 +61,9 @@ class BlueVineController extends BaseController {
}
}
$account = $user->primaryAccount();
$account->bluevine_status = 'signed_up';
$account->save();
$company = $user->account->company;
$company->bluevine_status = 'signed_up';
$company->save();
$quote_data = json_decode( $response->getBody() );
@ -74,9 +74,9 @@ class BlueVineController extends BaseController {
$user = Auth::user();
if ( $user ) {
$account = $user->primaryAccount();
$account->bluevine_status = 'ignored';
$account->save();
$company = $user->account->company;
$company->bluevine_status = 'ignored';
$company->save();
}
return 'success';

View File

@ -84,7 +84,10 @@ class ClientController extends BaseController
$user = Auth::user();
$actionLinks = [];
if($user->can('create', ENTITY_TASK)){
if ($user->can('create', ENTITY_INVOICE)){
$actionLinks[] = ['label' => trans('texts.new_invoice'), 'url' => URL::to('/invoices/create/'.$client->public_id)];
}
if ($user->can('create', ENTITY_TASK)){
$actionLinks[] = ['label' => trans('texts.new_task'), 'url' => URL::to('/tasks/create/'.$client->public_id)];
}
if (Utils::hasFeature(FEATURE_QUOTES) && $user->can('create', ENTITY_QUOTE)) {
@ -215,4 +218,28 @@ class ClientController extends BaseController
return $this->returnBulk(ENTITY_CLIENT, $action, $ids);
}
public function statement()
{
$account = Auth::user()->account;
$client = Client::scope(request()->client_id)->with('contacts')->firstOrFail();
$invoice = $account->createInvoice(ENTITY_INVOICE);
$invoice->client = $client;
$invoice->date_format = $account->date_format ? $account->date_format->format_moment : 'MMM D, YYYY';
$invoice->invoice_items = Invoice::scope()
->with(['client'])
->whereClientId($client->id)
->invoices()
->whereIsPublic(true)
->where('balance', '>', 0)
->get();
$data = [
'showBreadcrumbs' => false,
'client' => $client,
'invoice' => $invoice,
];
return view('clients.statement', $data);
}
}

View File

@ -63,6 +63,8 @@ class ClientPortalController extends BaseController
]);
}
$account->loadLocalizationSettings($client);
if (!Input::has('phantomjs') && !Input::has('silent') && !Session::has($invitationKey)
&& (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
if ($invoice->isType(INVOICE_TYPE_QUOTE)) {
@ -75,8 +77,6 @@ class ClientPortalController extends BaseController
Session::put($invitationKey, true); // track this invitation has been seen
Session::put('contact_key', $invitation->contact->contact_key);// track current contact
$account->loadLocalizationSettings($client);
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->features = [
@ -99,6 +99,11 @@ class ClientPortalController extends BaseController
'phone',
]);
// translate the client country name
if ($invoice->client->country) {
$invoice->client->country->name = trans('texts.country_' . $invoice->client->country->name);
}
$data = [];
$paymentTypes = $this->getPaymentTypes($account, $client, $invitation);
$paymentURL = '';
@ -204,9 +209,13 @@ class ClientPortalController extends BaseController
return RESULT_FAILURE;
}
$invitation->signature_base64 = Input::get('signature');
$invitation->signature_date = date_create();
$invitation->save();
if ($signature = Input::get('signature')) {
$invitation->signature_base64 = $signature;
$invitation->signature_date = date_create();
$invitation->save();
}
session(['authorized:' . $invitation->invitation_key => true]);
return RESULT_SUCCESS;
}

View File

@ -43,11 +43,13 @@ class DashboardController extends BaseController
$expenses = $dashboardRepo->expenses($accountId, $userId, $viewAll);
$tasks = $dashboardRepo->tasks($accountId, $userId, $viewAll);
$showBlueVinePromo = $user->is_admin
$showBlueVinePromo = $user->is_admin
&& env('BLUEVINE_PARTNER_UNIQUE_ID')
&& ! $account->company->bluevine_status
&& $account->created_at <= date( 'Y-m-d', strtotime( '-1 month' ));
$showWhiteLabelExpired = Utils::isSelfHost() && $account->company->hasExpiredPlan(PLAN_WHITE_LABEL);
// check if the account has quotes
$hasQuotes = false;
foreach ([$upcoming, $pastDue] as $data) {
@ -97,6 +99,7 @@ class DashboardController extends BaseController
'expenses' => $expenses,
'tasks' => $tasks,
'showBlueVinePromo' => $showBlueVinePromo,
'showWhiteLabelExpired' => $showWhiteLabelExpired,
];
if ($showBlueVinePromo) {

View File

@ -33,7 +33,20 @@ class DocumentAPIController extends BaseAPIController
}
/**
* @return \Illuminate\Http\Response
* @SWG\Get(
* path="/documents",
* summary="List of document",
* tags={"document"},
* @SWG\Response(
* response=200,
* description="A list with documents",
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Document"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function index()
{
@ -59,13 +72,29 @@ class DocumentAPIController extends BaseAPIController
}
/**
* @param CreateDocumentRequest $request
*
* @return \Illuminate\Http\Response
* @SWG\Post(
* path="/documents",
* tags={"document"},
* summary="Create a document",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Document")
* ),
* @SWG\Response(
* response=200,
* description="New document",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Document"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function store(CreateDocumentRequest $request)
{
$document = $this->documentRepo->upload($request->all());
return $this->itemResponse($document);

View File

@ -9,7 +9,6 @@ use App\Http\Requests\CreateExpenseCategoryRequest;
use App\Http\Requests\UpdateExpenseCategoryRequest;
use App\Ninja\Repositories\ExpenseCategoryRepository;
class ExpenseCategoryApiController extends BaseAPIController
{
protected $categoryRepo;
@ -19,23 +18,65 @@ class ExpenseCategoryApiController extends BaseAPIController
public function __construct(ExpenseCategoryRepository $categoryRepo, ExpenseCategoryService $categoryService)
{
parent::__construct();
$this->categoryRepo = $categoryRepo;
$this->categoryService = $categoryService;
}
/**
* @SWG\Post(
* path="/expense_categories",
* tags={"expense_category"},
* summary="Create an expense category",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/ExpenseCategory")
* ),
* @SWG\Response(
* response=200,
* description="New expense category",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/ExpenseCategory"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function store(CreateExpenseCategoryRequest $request)
{
$category = $this->categoryRepo->save($request->input());
return $this->itemResponse($category);
}
/**
* @SWG\Put(
* path="/expense_categories/{expense_category_id}",
* tags={"expense_category"},
* summary="Update an expense category",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/ExpenseCategory")
* ),
* @SWG\Response(
* response=200,
* description="Update expense category",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/ExpenseCategory"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function update(UpdateExpenseCategoryRequest $request)
{
$category = $this->categoryRepo->save($request->input(), $request->entity());
return $this->itemResponse($category);
}
public function store(CreateExpenseCategoryRequest $request)
{
$category = $this->categoryRepo->save($request->input());
return $this->itemResponse($category);
}
}

View File

@ -74,7 +74,7 @@ class ExpenseCategoryController extends BaseController
Session::flash('message', trans('texts.created_expense_category'));
return redirect()->to('/expense_categories');
return redirect()->to($category->getRoute());
}
public function update(UpdateExpenseCategoryRequest $request)

View File

@ -253,7 +253,7 @@ class ExpenseController extends BaseController
'customLabel1' => Auth::user()->account->custom_vendor_label1,
'customLabel2' => Auth::user()->account->custom_vendor_label2,
'categories' => ExpenseCategory::whereAccountId(Auth::user()->account_id)->withArchived()->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->whereIsInclusive(false)->orderBy('name')->get(),
];
}

View File

@ -32,7 +32,21 @@ class ExportController extends BaseController
{
$format = $request->input('format');
$date = date('Y-m-d');
$fileName = "invoice-ninja-{$date}";
// set the filename based on the entity types selected
if ($request->include == 'all') {
$fileName = "invoice-ninja-{$date}";
} else {
$fields = $request->all();
$fields = array_filter(array_map(function ($key) {
if ( ! in_array($key, ['format', 'include', '_token'])) {
return $key;
} else {
return null;
}
}, array_keys($fields), $fields));
$fileName = "invoice-ninja-" . join('-', $fields) . "-{$date}";
}
if ($format === 'JSON') {
return $this->returnJSON($request, $fileName);

View File

@ -1,10 +1,12 @@
<?php namespace App\Http\Controllers;
use Response;
use Request;
use Redirect;
use Auth;
use View;
use Input;
use Mail;
use Session;
use App\Models\Account;
use App\Libraries\Utils;
@ -48,14 +50,6 @@ class HomeController extends BaseController
}
}
/**
* @return \Illuminate\Contracts\View\View
*/
public function showTerms()
{
return View::make('public.terms', ['hideHeader' => true]);
}
/**
* @return \Illuminate\Contracts\View\View
*/
@ -134,4 +128,24 @@ class HomeController extends BaseController
{
return RESULT_SUCCESS;
}
/**
* @return mixed
*/
public function contactUs()
{
Mail::raw(request()->message, function ($message) {
$subject = 'Customer Message';
if ( ! Utils::isNinja()) {
$subject .= ': v' . NINJA_VERSION;
}
$message->to(CONTACT_EMAIL)
->from(CONTACT_EMAIL, Auth::user()->present()->fullName)
->replyTo(Auth::user()->email, Auth::user()->present()->fullName)
->subject($subject);
});
return redirect(Request::server('HTTP_REFERER'))
->with('message', trans('texts.contact_us_response'));
}
}

View File

@ -12,12 +12,13 @@ use App\Models\Product;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Http\Requests\InvoiceRequest;
use App\Http\Requests\CreateInvoiceAPIRequest;
use App\Http\Requests\UpdateInvoiceAPIRequest;
use App\Services\InvoiceService;
use App\Services\PaymentService;
use App\Jobs\SendInvoiceEmail;
use App\Jobs\SendPaymentEmail;
class InvoiceApiController extends BaseAPIController
{
@ -25,7 +26,7 @@ class InvoiceApiController extends BaseAPIController
protected $entityType = ENTITY_INVOICE;
public function __construct(InvoiceService $invoiceService, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, PaymentRepository $paymentRepo, Mailer $mailer, PaymentService $paymentService)
public function __construct(InvoiceService $invoiceService, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, PaymentRepository $paymentRepo, PaymentService $paymentService)
{
parent::__construct();
@ -33,7 +34,6 @@ class InvoiceApiController extends BaseAPIController
$this->clientRepo = $clientRepo;
$this->paymentRepo = $paymentRepo;
$this->invoiceService = $invoiceService;
$this->mailer = $mailer;
$this->paymentService = $paymentService;
}
@ -185,9 +185,9 @@ class InvoiceApiController extends BaseAPIController
if ($isEmailInvoice) {
if ($payment) {
$this->mailer->sendPaymentConfirmation($payment);
$this->dispatch(new SendPaymentEmail($payment));
} elseif ( ! $invoice->is_recurring) {
$this->mailer->sendInvoice($invoice);
$this->dispatch(new SendInvoiceEmail($invoice));
}
}
@ -229,7 +229,7 @@ class InvoiceApiController extends BaseAPIController
}
if (!isset($data['invoice_date'])) {
$fields['invoice_date_sql'] = date_create()->format('Y-m-d');
$fields['invoice_date_sql'] = Utils::today();
}
if (!isset($data['due_date'])) {
$fields['due_date_sql'] = false;
@ -293,7 +293,7 @@ class InvoiceApiController extends BaseAPIController
{
$invoice = $request->entity();
$this->mailer->sendInvoice($invoice);
$this->dispatch(new SendInvoiceEmail($invoice));
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();

View File

@ -9,16 +9,16 @@ use Cache;
use Redirect;
use DB;
use URL;
use DropdownButton;
use App\Models\Invoice;
use App\Models\Client;
use App\Models\Account;
use App\Models\Product;
use App\Models\Expense;
use App\Models\Payment;
use App\Models\TaxRate;
use App\Models\InvoiceDesign;
use App\Models\Activity;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Jobs\SendInvoiceEmail;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\DocumentRepository;
@ -32,7 +32,6 @@ use App\Http\Requests\UpdateInvoiceRequest;
class InvoiceController extends BaseController
{
protected $mailer;
protected $invoiceRepo;
protected $clientRepo;
protected $documentRepo;
@ -41,11 +40,10 @@ class InvoiceController extends BaseController
protected $recurringInvoiceService;
protected $entityType = ENTITY_INVOICE;
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, DocumentRepository $documentRepo, RecurringInvoiceService $recurringInvoiceService, PaymentService $paymentService)
public function __construct(InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, DocumentRepository $documentRepo, RecurringInvoiceService $recurringInvoiceService, PaymentService $paymentService)
{
// parent::__construct();
$this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
$this->clientRepo = $clientRepo;
$this->invoiceService = $invoiceService;
@ -100,10 +98,10 @@ class InvoiceController extends BaseController
if ($clone) {
$invoice->id = $invoice->public_id = null;
$invoice->is_public = false;
$invoice->invoice_number = $account->getNextInvoiceNumber($invoice);
$invoice->invoice_number = $account->getNextNumber($invoice);
$invoice->balance = $invoice->amount;
$invoice->invoice_status_id = 0;
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->invoice_date = Utils::today();
$method = 'POST';
$url = "{$entityType}s";
} else {
@ -124,44 +122,6 @@ class InvoiceController extends BaseController
'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$actions = [
['url' => 'javascript:onCloneClick()', 'label' => trans("texts.clone_{$entityType}")],
['url' => URL::to("{$entityType}s/{$entityType}_history/{$invoice->public_id}"), 'label' => trans('texts.view_history')],
DropdownButton::DIVIDER
];
if ($entityType == ENTITY_QUOTE) {
if ($invoice->quote_invoice_id) {
$actions[] = ['url' => URL::to("invoices/{$invoice->quote_invoice_id}/edit"), 'label' => trans('texts.view_invoice')];
} else {
$actions[] = ['url' => 'javascript:onConvertClick()', 'label' => trans('texts.convert_to_invoice')];
}
} elseif ($entityType == ENTITY_INVOICE) {
if ($invoice->quote_id) {
$actions[] = ['url' => URL::to("quotes/{$invoice->quote_id}/edit"), 'label' => trans('texts.view_quote')];
}
if (!$invoice->is_recurring && $invoice->balance > 0 && $invoice->is_public) {
$actions[] = ['url' => 'javascript:submitBulkAction("markPaid")', 'label' => trans('texts.mark_paid')];
$actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')];
}
foreach ($invoice->payments as $payment) {
$label = trans('texts.view_payment');
if (count($invoice->payments) > 1) {
$label .= ' - ' . $account->formatMoney($payment->amount, $invoice->client);
}
$actions[] = ['url' => $payment->present()->url, 'label' => $label];
}
}
if (count($actions) > 3) {
$actions[] = DropdownButton::DIVIDER;
}
$actions[] = ['url' => 'javascript:onArchiveClick()', 'label' => trans("texts.archive_{$entityType}")];
$actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans("texts.delete_{$entityType}")];
$lastSent = ($invoice->is_recurring && $invoice->last_sent_date) ? $invoice->recurring_invoices->last() : null;
if(!Auth::user()->hasPermission('view_all')){
@ -179,7 +139,6 @@ class InvoiceController extends BaseController
'title' => trans("texts.edit_{$entityType}"),
'client' => $invoice->client,
'isRecurring' => $invoice->is_recurring,
'actions' => $actions,
'lastSent' => $lastSent];
$data = array_merge($data, self::getViewModel($invoice));
@ -326,7 +285,11 @@ class InvoiceController extends BaseController
$defaultTax = false;
foreach ($rates as $rate) {
$options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%';
$name = $rate->name . ' ' . ($rate->rate+0) . '%';
if ($rate->is_inclusive) {
$name .= ' - ' . trans('texts.inclusive');
}
$options[($rate->is_inclusive ? '1 ' : '0 ') . $rate->rate . ' ' . $rate->name] = $name;
// load default invoice tax
if ($rate->id == $account->default_tax_rate_id) {
@ -340,7 +303,7 @@ class InvoiceController extends BaseController
if (isset($options[$key])) {
continue;
}
$options[$key] = $rate['name'] . ' ' . $rate['rate'] . '%';
$options['0 ' . $key] = $rate['name'] . ' ' . $rate['rate'] . '%';
}
}
@ -452,7 +415,8 @@ class InvoiceController extends BaseController
if ($invoice->is_recurring) {
$response = $this->emailRecurringInvoice($invoice);
} else {
$response = $this->mailer->sendInvoice($invoice, false, $pdfUpload);
$this->dispatch(new SendInvoiceEmail($invoice, false, $pdfUpload));
return true;
}
if ($response === true) {
@ -482,7 +446,8 @@ class InvoiceController extends BaseController
if ($invoice->isPaid()) {
return true;
} else {
return $this->mailer->sendInvoice($invoice);
$this->dispatch(new SendInvoiceEmail($invoice));
return true;
}
}
@ -514,6 +479,8 @@ class InvoiceController extends BaseController
if ($count > 0) {
if ($action == 'markSent') {
$key = 'marked_sent_invoice';
} elseif ($action == 'emailInvoice') {
$key = 'emailed_' . $entityType;
} elseif ($action == 'markPaid') {
$key = 'created_payment';
} else {
@ -543,6 +510,8 @@ class InvoiceController extends BaseController
public function invoiceHistory(InvoiceRequest $request)
{
$invoice = $request->entity();
$paymentId = $request->payment_id ? Payment::getPrivateId($request->payment_id) : false;
$invoice->load('user', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'account.country', 'client.contacts', 'client.country');
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
@ -553,17 +522,21 @@ class InvoiceController extends BaseController
];
$invoice->invoice_type_id = intval($invoice->invoice_type_id);
$activityTypeId = $invoice->isType(INVOICE_TYPE_QUOTE) ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
$activities = Activity::scope(false, $invoice->account_id)
->where('activity_type_id', '=', $activityTypeId)
->where('invoice_id', '=', $invoice->id)
->orderBy('id', 'desc')
->get(['id', 'created_at', 'user_id', 'json_backup']);
$activities = Activity::scope(false, $invoice->account_id);
if ($paymentId) {
$activities->whereIn('activity_type_id', [ACTIVITY_TYPE_CREATE_PAYMENT])
->where('payment_id', '=', $paymentId);
} else {
$activities->whereIn('activity_type_id', [ACTIVITY_TYPE_UPDATE_INVOICE, ACTIVITY_TYPE_UPDATE_QUOTE])
->where('invoice_id', '=', $invoice->id);
}
$activities = $activities->orderBy('id', 'desc')
->get(['id', 'created_at', 'user_id', 'json_backup', 'activity_type_id', 'payment_id']);
$versionsJson = [];
$versionsSelect = [];
$lastId = false;
//dd($activities->toArray());
foreach ($activities as $activity) {
if ($backup = json_decode($activity->json_backup)) {
$backup->invoice_date = Utils::fromSqlDate($backup->invoice_date);
@ -576,16 +549,17 @@ class InvoiceController extends BaseController
$backup->invoice_type_id = isset($backup->invoice_type_id) && intval($backup->invoice_type_id) == INVOICE_TYPE_QUOTE;
$backup->account = $invoice->account->toArray();
$versionsJson[$activity->id] = $backup;
$versionsJson[$paymentId ? 0 : $activity->id] = $backup;
$key = Utils::timestampToDateTimeString(strtotime($activity->created_at)) . ' - ' . $activity->user->getDisplayName();
$versionsSelect[$lastId ? $lastId : 0] = $key;
$versionsSelect[$lastId ?: 0] = $key;
$lastId = $activity->id;
} else {
Utils::logError('Failed to parse invoice backup');
}
}
if ($lastId) {
// Show the current version as the last in the history
if ( ! $paymentId) {
$versionsSelect[$lastId] = Utils::timestampToDateTimeString(strtotime($invoice->created_at)) . ' - ' . $invoice->user->getDisplayName();
}
@ -595,6 +569,7 @@ class InvoiceController extends BaseController
'versionsSelect' => $versionsSelect,
'invoiceDesigns' => InvoiceDesign::getDesigns(),
'invoiceFonts' => Cache::get('fonts'),
'paymentId' => $paymentId,
];
return View::make('invoices.history', $data);

View File

@ -6,6 +6,7 @@ use Input;
use Utils;
use View;
use Validator;
use Auth;
use URL;
use Cache;
use Omnipay;
@ -274,4 +275,15 @@ class NinjaController extends BaseController
Session::flash('error', $message);
Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true);
}
public function hideWhiteLabelMessage()
{
$user = Auth::user();
$company = $user->account->company;
$company->plan = null;
$company->save();
return RESULT_SUCCESS;
}
}

View File

@ -11,6 +11,7 @@ use Exception;
use Validator;
use App\Models\Invitation;
use App\Models\Account;
use App\Models\Client;
use App\Models\Payment;
use App\Models\Product;
use App\Models\PaymentMethod;
@ -76,6 +77,11 @@ class OnlinePaymentController extends BaseController
$invitation = $invitation->load('invoice.client.account.account_gateways.gateway');
$account = $invitation->account;
if ($account->requiresAuthorization($invitation->invoice) && ! session('authorized:' . $invitation->invitation_key)) {
return redirect()->to('view/' . $invitation->invitation_key);
}
$account->loadLocalizationSettings($invitation->invoice->client);
if ( ! $gatewayTypeAlias) {
@ -283,22 +289,31 @@ class OnlinePaymentController extends BaseController
return redirect()->to("{$failureUrl}/?error=invalid product");
}
$rules = [
'first_name' => 'string|max:100',
'last_name' => 'string|max:100',
'email' => 'email|string|max:100',
];
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return redirect()->to("{$failureUrl}/?error=" . $validator->errors()->first());
// check for existing client using contact_key
$client = false;
if ($contactKey = Input::get('contact_key')) {
$client = Client::scope()->whereHas('contacts', function ($query) use ($contactKey) {
$query->where('contact_key', $contactKey);
})->first();
}
if ( ! $client) {
$rules = [
'first_name' => 'string|max:100',
'last_name' => 'string|max:100',
'email' => 'email|string|max:100',
];
$data = [
'currency_id' => $account->currency_id,
'contact' => Input::all()
];
$client = $clientRepo->save($data);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return redirect()->to("{$failureUrl}/?error=" . $validator->errors()->first());
}
$data = [
'currency_id' => $account->currency_id,
'contact' => Input::all()
];
$client = $clientRepo->save($data);
}
$data = [
'client_id' => $client->id,

View File

@ -108,6 +108,9 @@ class PaymentApiController extends BaseAPIController
*/
public function store(CreatePaymentAPIRequest $request)
{
// check payment has been marked sent
$request->invoice->markSentIfUnsent();
$payment = $this->paymentRepo->save($request->input());
if (Input::get('email_receipt')) {
@ -142,7 +145,7 @@ class PaymentApiController extends BaseAPIController
public function destroy(UpdatePaymentRequest $request)
{
$payment = $request->entity();
$this->clientRepo->delete($payment);
return $this->itemResponse($payment);

View File

@ -5,6 +5,7 @@ use Session;
use Utils;
use View;
use Cache;
use DropdownButton;
use App\Models\Invoice;
use App\Models\Client;
use App\Ninja\Repositories\PaymentRepository;
@ -84,7 +85,6 @@ class PaymentController extends BaseController
{
$invoices = Invoice::scope()
->invoices()
->whereIsPublic(true)
->where('invoices.balance', '>', 0)
->with('client', 'invoice_status')
->orderBy('invoice_number')->get();
@ -122,9 +122,21 @@ class PaymentController extends BaseController
public function edit(PaymentRequest $request)
{
$payment = $request->entity();
$payment->payment_date = Utils::fromSqlDate($payment->payment_date);
$actions = [];
if ($payment->invoiceJsonBackup()) {
$actions[] = ['url' => url("/invoices/invoice_history/{$payment->invoice->public_id}?payment_id={$payment->public_id}"), 'label' => trans('texts.view_invoice')];
}
$actions[] = ['url' => url("/invoices/{$payment->invoice->public_id}/edit"), 'label' => trans('texts.edit_invoice')];
$actions[] = DropdownButton::DIVIDER;
if ( ! $payment->trashed()) {
$actions[] = ['url' => 'javascript:submitAction("archive")', 'label' => trans('texts.archive_payment')];
$actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans('texts.delete_payment')];
} else {
$actions[] = ['url' => 'javascript:submitAction("restore")', 'label' => trans('texts.restore_expense')];
}
$data = [
'client' => null,
'invoice' => null,
@ -138,6 +150,7 @@ class PaymentController extends BaseController
'method' => 'PUT',
'url' => 'payments/'.$payment->public_id,
'title' => trans('texts.edit_payment'),
'actions' => $actions,
'paymentTypes' => Cache::get('paymentTypes'),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
];
@ -151,8 +164,10 @@ class PaymentController extends BaseController
*/
public function store(CreatePaymentRequest $request)
{
$input = $request->input();
// check payment has been marked sent
$request->invoice->markSentIfUnsent();
$input = $request->input();
$input['invoice_id'] = Invoice::getPrivateId($input['invoice']);
$input['client_id'] = Client::getPrivateId($input['client']);
$payment = $this->paymentRepo->save($input);
@ -173,6 +188,10 @@ class PaymentController extends BaseController
*/
public function update(UpdatePaymentRequest $request)
{
if (in_array($request->action, ['archive', 'delete', 'restore'])) {
return self::bulk();
}
$payment = $this->paymentRepo->save($request->input(), $request->entity());
Session::flash('message', trans('texts.updated_payment'));
@ -191,7 +210,7 @@ class PaymentController extends BaseController
$count = $this->paymentService->bulk($ids, $action, ['amount'=>$amount]);
if ($count > 0) {
$message = Utils::pluralize($action=='refund'?'refunded_payment':$action.'d_payment', $count);
$message = Utils::pluralize($action=='refund' ? 'refunded_payment':$action.'d_payment', $count);
Session::flash('message', $message);
}

View File

@ -33,7 +33,20 @@ class ProductApiController extends BaseAPIController
}
/**
* @return \Illuminate\Http\Response
* @SWG\Get(
* path="/products",
* summary="List of products",
* tags={"product"},
* @SWG\Response(
* response=200,
* description="A list with products",
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Product"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function index()
{
@ -45,8 +58,25 @@ class ProductApiController extends BaseAPIController
}
/**
* @param CreateProductRequest $request
* @return \Illuminate\Http\Response
* @SWG\Post(
* path="/products",
* tags={"product"},
* summary="Create a product",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Product")
* ),
* @SWG\Response(
* response=200,
* description="New product",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Product"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function store(CreateProductRequest $request)
{
@ -56,16 +86,32 @@ class ProductApiController extends BaseAPIController
}
/**
* @param UpdateProductRequest $request
* @param $publicId
* @return \Illuminate\Http\Response
* @SWG\Put(
* path="/products/{product_id}",
* tags={"product"},
* summary="Update a product",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Product")
* ),
* @SWG\Response(
* response=200,
* description="Update product",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Product"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function update(UpdateProductRequest $request, $publicId)
{
if ($request->action) {
return $this->handleAction($request);
}
$data = $request->input();
$data['public_id'] = $publicId;
$product = $this->productRepo->save($data, $request->entity());

View File

@ -74,7 +74,7 @@ class ProductController extends BaseController
$data = [
'account' => $account,
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->whereIsInclusive(false)->get(['id', 'name', 'rate']) : null,
'product' => $product,
'entity' => $product,
'method' => 'PUT',
@ -94,7 +94,7 @@ class ProductController extends BaseController
$data = [
'account' => $account,
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->whereIsInclusive(false)->get(['id', 'name', 'rate']) : null,
'product' => null,
'method' => 'POST',
'url' => 'products',

View File

@ -6,12 +6,9 @@ use Input;
use Utils;
use DB;
use Session;
use Str;
use View;
use App\Models\Account;
use App\Models\Client;
use App\Models\Payment;
use App\Models\Expense;
use App\Models\Task;
/**
* Class ReportController
@ -67,19 +64,22 @@ class ReportController extends BaseController
}
$reportTypes = [
ENTITY_CLIENT => trans('texts.client'),
ENTITY_INVOICE => trans('texts.invoice'),
ENTITY_PRODUCT => trans('texts.product'),
ENTITY_PAYMENT => trans('texts.payment'),
ENTITY_EXPENSE => trans('texts.expense'),
ENTITY_TASK => trans('texts.task'),
ENTITY_TAX_RATE => trans('texts.tax'),
'client',
'product',
'invoice',
'invoice_details',
'aging',
'profit_and_loss',
'payment',
'expense',
'task',
'tax_rate',
];
$params = [
'startDate' => $startDate->format('Y-m-d'),
'endDate' => $endDate->format('Y-m-d'),
'reportTypes' => $reportTypes,
'reportTypes' => array_combine($reportTypes, Utils::trans($reportTypes)),
'reportType' => $reportType,
'title' => trans('texts.charts_and_reports'),
'account' => Auth::user()->account,
@ -87,8 +87,18 @@ class ReportController extends BaseController
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
$isExport = $action == 'export';
$params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport));
$reportClass = '\\App\\Ninja\\Reports\\' . Str::studly($reportType) . 'Report';
$options = [
'date_field' => $dateField,
'invoice_status' => request()->invoice_status,
'group_dates_by' => request()->group_dates_by,
];
$report = new $reportClass($startDate, $endDate, $isExport, $options);
if (Input::get('report_type')) {
$report->run();
}
$params['report'] = $report;
$params = array_merge($params, $report->results());
if ($isExport) {
self::export($reportType, $params['displayData'], $params['columns'], $params['reportTotals']);
}
@ -96,427 +106,12 @@ class ReportController extends BaseController
$params['columns'] = [];
$params['displayData'] = [];
$params['reportTotals'] = [];
$params['report'] = false;
}
return View::make('reports.chart_builder', $params);
}
/**
* @param $reportType
* @param $startDate
* @param $endDate
* @param $dateField
* @param $isExport
* @return array
*/
private function generateReport($reportType, $startDate, $endDate, $dateField, $isExport)
{
if ($reportType == ENTITY_CLIENT) {
return $this->generateClientReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_INVOICE) {
return $this->generateInvoiceReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_PRODUCT) {
return $this->generateProductReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_PAYMENT) {
return $this->generatePaymentReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_TAX_RATE) {
return $this->generateTaxRateReport($startDate, $endDate, $dateField, $isExport);
} elseif ($reportType == ENTITY_EXPENSE) {
return $this->generateExpenseReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_TASK) {
return $this->generateTaskReport($startDate, $endDate, $isExport);
}
}
private function generateTaskReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'date', 'description', 'duration'];
$displayData = [];
$tasks = Task::scope()
->with('client.contacts')
->withArchived()
->dateRange($startDate, $endDate);
foreach ($tasks->get() as $task) {
$displayData[] = [
$task->client ? ($isExport ? $task->client->getDisplayName() : $task->client->present()->link) : trans('texts.unassigned'),
link_to($task->present()->url, $task->getStartTime()),
$task->present()->description,
Utils::formatTime($task->getDuration()),
];
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => [],
];
}
/**
* @param $startDate
* @param $endDate
* @param $dateField
* @param $isExport
* @return array
*/
private function generateTaxRateReport($startDate, $endDate, $dateField, $isExport)
{
$columns = ['tax_name', 'tax_rate', 'amount', 'paid'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$clients = Client::scope()
->withArchived()
->with('contacts')
->with(['invoices' => function($query) use ($startDate, $endDate, $dateField) {
$query->with('invoice_items')->withArchived();
if ($dateField == FILTER_INVOICE_DATE) {
$query->where('invoice_date', '>=', $startDate)
->where('invoice_date', '<=', $endDate)
->with('payments');
} else {
$query->whereHas('payments', function($query) use ($startDate, $endDate) {
$query->where('payment_date', '>=', $startDate)
->where('payment_date', '<=', $endDate)
->withArchived();
})
->with(['payments' => function($query) use ($startDate, $endDate) {
$query->where('payment_date', '>=', $startDate)
->where('payment_date', '<=', $endDate)
->withArchived();
}]);
}
}]);
foreach ($clients->get() as $client) {
$currencyId = $client->currency_id ?: Auth::user()->account->getCurrencyId();
$amount = 0;
$paid = 0;
$taxTotals = [];
foreach ($client->invoices as $invoice) {
foreach ($invoice->getTaxes(true) as $key => $tax) {
if ( ! isset($taxTotals[$currencyId])) {
$taxTotals[$currencyId] = [];
}
if (isset($taxTotals[$currencyId][$key])) {
$taxTotals[$currencyId][$key]['amount'] += $tax['amount'];
$taxTotals[$currencyId][$key]['paid'] += $tax['paid'];
} else {
$taxTotals[$currencyId][$key] = $tax;
}
}
$amount += $invoice->amount;
$paid += $invoice->getAmountPaid();
}
foreach ($taxTotals as $currencyId => $taxes) {
foreach ($taxes as $tax) {
$displayData[] = [
$tax['name'],
$tax['rate'] . '%',
$account->formatMoney($tax['amount'], $client),
$account->formatMoney($tax['paid'], $client)
];
}
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $tax['amount']);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $tax['paid']);
}
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
];
}
/**
* @param $startDate
* @param $endDate
* @param $isExport
* @return array
*/
private function generatePaymentReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$payments = Payment::scope()
->withArchived()
->excludeFailed()
->whereHas('client', function($query) {
$query->where('is_deleted', '=', false);
})
->whereHas('invoice', function($query) {
$query->where('is_deleted', '=', false);
})
->with('client.contacts', 'invoice', 'payment_type', 'account_gateway.gateway')
->where('payment_date', '>=', $startDate)
->where('payment_date', '<=', $endDate);
foreach ($payments->get() as $payment) {
$invoice = $payment->invoice;
$client = $payment->client;
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date,
$account->formatMoney($invoice->amount, $client),
$payment->present()->payment_date,
$account->formatMoney($payment->getCompletedAmount(), $client),
$payment->present()->method,
];
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment->getCompletedAmount());
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
];
}
/**
* @param $startDate
* @param $endDate
* @param $isExport
* @return array
*/
private function generateInvoiceReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$clients = Client::scope()
->withTrashed()
->with('contacts')
->where('is_deleted', '=', false)
->with(['invoices' => function($query) use ($startDate, $endDate) {
$query->invoices()
->withArchived()
->where('invoice_date', '>=', $startDate)
->where('invoice_date', '<=', $endDate)
->with(['payments' => function($query) {
$query->withArchived()
->excludeFailed()
->with('payment_type', 'account_gateway.gateway');
}, 'invoice_items'])
->withTrashed();
}]);
foreach ($clients->get() as $client) {
foreach ($client->invoices as $invoice) {
$payments = count($invoice->payments) ? $invoice->payments : [false];
foreach ($payments as $payment) {
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date,
$account->formatMoney($invoice->amount, $client),
$payment ? $payment->present()->payment_date : '',
$payment ? $account->formatMoney($payment->getCompletedAmount(), $client) : '',
$payment ? $payment->present()->method : '',
];
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment ? $payment->getCompletedAmount() : 0);
}
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $invoice->balance);
}
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
];
}
/**
* @param $startDate
* @param $endDate
* @param $isExport
* @return array
*/
private function generateProductReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'invoice_number', 'invoice_date', 'quantity', 'product'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$clients = Client::scope()
->withTrashed()
->with('contacts')
->where('is_deleted', '=', false)
->with(['invoices' => function($query) use ($startDate, $endDate) {
$query->where('invoice_date', '>=', $startDate)
->where('invoice_date', '<=', $endDate)
->where('is_deleted', '=', false)
->where('is_recurring', '=', false)
->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD)
->with(['invoice_items'])
->withTrashed();
}]);
foreach ($clients->get() as $client) {
foreach ($client->invoices as $invoice) {
foreach ($invoice->invoice_items as $invoiceItem) {
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date,
round($invoiceItem->qty, 2),
$invoiceItem->product_key,
];
//$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment ? $payment->amount : 0);
}
//$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount);
//$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $invoice->balance);
}
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => [],
];
}
/**
* @param $startDate
* @param $endDate
* @param $isExport
* @return array
*/
private function generateClientReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'amount', 'paid', 'balance'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$clients = Client::scope()
->withArchived()
->with('contacts')
->with(['invoices' => function($query) use ($startDate, $endDate) {
$query->where('invoice_date', '>=', $startDate)
->where('invoice_date', '<=', $endDate)
->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD)
->where('is_recurring', '=', false)
->withArchived();
}]);
foreach ($clients->get() as $client) {
$amount = 0;
$paid = 0;
foreach ($client->invoices as $invoice) {
$amount += $invoice->amount;
$paid += $invoice->getAmountPaid();
}
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$account->formatMoney($amount, $client),
$account->formatMoney($paid, $client),
$account->formatMoney($amount - $paid, $client)
];
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $amount);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $paid);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $amount - $paid);
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
];
}
/**
* @param $startDate
* @param $endDate
* @param $isExport
* @return array
*/
private function generateExpenseReport($startDate, $endDate, $isExport)
{
$columns = ['vendor', 'client', 'date', 'expense_amount'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$expenses = Expense::scope()
->withArchived()
->with('client.contacts', 'vendor')
->where('expense_date', '>=', $startDate)
->where('expense_date', '<=', $endDate);
foreach ($expenses->get() as $expense) {
$amount = $expense->amountWithTax();
$displayData[] = [
$expense->vendor ? ($isExport ? $expense->vendor->name : $expense->vendor->present()->link) : '',
$expense->client ? ($isExport ? $expense->client->getDisplayName() : $expense->client->present()->link) : '',
$expense->present()->expense_date,
Utils::formatMoney($amount, $expense->currency_id),
];
$reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'amount', $amount);
$reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'amount', 0);
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
];
}
/**
* @param $data
* @param $currencyId
* @param $field
* @param $value
* @return mixed
*/
private function addToTotals($data, $currencyId, $field, $value) {
$currencyId = $currencyId ?: Auth::user()->account->getCurrencyId();
if (!isset($data[$currencyId][$field])) {
$data[$currencyId][$field] = 0;
}
$data[$currencyId][$field] += $value;
return $data;
}
/**
* @param $reportType
* @param $data
@ -534,6 +129,7 @@ class ReportController extends BaseController
Utils::exportData($output, $data, Utils::trans($columns));
/*
fwrite($output, trans('texts.totals'));
foreach ($totals as $currencyId => $fields) {
foreach ($fields as $key => $value) {
@ -550,6 +146,7 @@ class ReportController extends BaseController
}
fwrite($output, $csv."\n");
}
*/
fclose($output);
exit;

View File

@ -253,10 +253,11 @@ class TaskController extends BaseController
Session::flash('message', trans('texts.stopped_task'));
return Redirect::to('tasks');
} else if ($action == 'invoice' || $action == 'add_to_invoice') {
$tasks = Task::scope($ids)->with('client')->get();
$tasks = Task::scope($ids)->with('client')->orderBy('project_id', 'id')->get();
$clientPublicId = false;
$data = [];
$lastProjectId = false;
foreach ($tasks as $task) {
if ($task->client) {
if (!$clientPublicId) {
@ -276,11 +277,13 @@ class TaskController extends BaseController
}
$account = Auth::user()->account;
$showProject = $lastProjectId != $task->project_id;
$data[] = [
'publicId' => $task->public_id,
'description' => $task->description . "\n\n" . $task->present()->times($account),
'description' => $task->present()->invoiceDescription($account, $showProject),
'duration' => $task->getHours(),
];
$lastProjectId = $task->project_id;
}
if ($action == 'invoice') {

View File

@ -28,6 +28,22 @@ class TaxRateApiController extends BaseAPIController
$this->taxRateRepo = $taxRateRepo;
}
/**
* @SWG\Get(
* path="/tax_rates",
* summary="List of tax rates",
* tags={"tax_rate"},
* @SWG\Response(
* response=200,
* description="A list with tax rates",
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/TaxRate"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function index()
{
$taxRates = TaxRate::scope()
@ -37,6 +53,27 @@ class TaxRateApiController extends BaseAPIController
return $this->listResponse($taxRates);
}
/**
* @SWG\Post(
* path="/tax_rates",
* tags={"tax_rate"},
* summary="Create a tax rate",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/TaxRate")
* ),
* @SWG\Response(
* response=200,
* description="New tax rate",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/TaxRate"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function store(CreateTaxRateRequest $request)
{
$taxRate = $this->taxRateRepo->save($request->input());
@ -45,16 +82,32 @@ class TaxRateApiController extends BaseAPIController
}
/**
* @param UpdateTaxRateRequest $request
* @param $publicId
* @return \Illuminate\Http\Response
* @SWG\Put(
* path="/tax_rates/{tax_rate_id}",
* tags={"tax_rate"},
* summary="Update a tax rate",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/TaxRate")
* ),
* @SWG\Response(
* response=200,
* description="Update tax rate",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/TaxRate"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function update(UpdateTaxRateRequest $request, $publicId)
{
if ($request->action) {
return $this->handleAction($request);
}
$data = $request->input();
$data['public_id'] = $publicId;
$taxRate = $this->taxRateRepo->save($data, $request->entity());

View File

@ -240,7 +240,7 @@ class UserController extends BaseController
$user = User::where('confirmation_code', '=', $code)->get()->first();
if ($user) {
$notice_msg = trans('texts.security.confirmation');
$notice_msg = trans('texts.security_confirmation');
$user->confirmed = true;
$user->confirmation_code = '';
@ -356,7 +356,7 @@ class UserController extends BaseController
Session::put(SESSION_USER_ACCOUNTS, $users);
Session::flash('message', trans('texts.unlinked_account'));
return Redirect::to('/dashboard');
return Redirect::to('/manage_companies');
}
public function manageCompanies()

View File

@ -82,7 +82,6 @@ class VendorController extends BaseController
'actionLinks' => $actionLinks,
'showBreadcrumbs' => false,
'vendor' => $vendor,
'totalexpense' => $vendor->getTotalExpense(),
'title' => trans('texts.view_vendor'),
'hasRecurringInvoices' => false,
'hasQuotes' => false,

View File

@ -25,7 +25,9 @@ class ApiCheck {
{
$loggingIn = $request->is('api/v1/login')
|| $request->is('api/v1/register')
|| $request->is('api/v1/oauth_login');
|| $request->is('api/v1/oauth_login')
|| $request->is('api/v1/ping');
$headers = Utils::getApiHeaders();
$hasApiSecret = false;
@ -38,7 +40,8 @@ class ApiCheck {
// check API secret
if ( ! $hasApiSecret) {
sleep(ERROR_DELAY);
return Response::json('Invalid value for API_SECRET', 403, $headers);
$error['error'] = ['message'=>'Invalid value for API_SECRET'];
return Response::json($error, 403, $headers);
}
} else {
// check for a valid token
@ -50,7 +53,8 @@ class ApiCheck {
Session::set('token_id', $token->id);
} else {
sleep(ERROR_DELAY);
return Response::json('Invalid token', 403, $headers);
$error['error'] = ['message'=>'Invalid token'];
return Response::json($error, 403, $headers);
}
}
@ -59,7 +63,8 @@ class ApiCheck {
}
if (!Utils::hasFeature(FEATURE_API) && !$hasApiSecret) {
return Response::json('API requires pro plan', 403, $headers);
$error['error'] = ['message'=>'API requires pro plan'];
return Response::json($error, 403, $headers);
} else {
$key = Auth::check() ? Auth::user()->account->id : $request->getClientIp();

View File

@ -149,7 +149,8 @@ class StartupCheck
$company = Auth::user()->account->company;
$company->plan_term = PLAN_TERM_YEARLY;
$company->plan_paid = $data;
$company->plan_expires = date_create($data)->modify('+1 year')->format('Y-m-d');
$date = max(date_create($data), date_create($company->plan_expires));
$company->plan_expires = $date->modify('+1 year')->format('Y-m-d');
$company->plan = PLAN_WHITE_LABEL;
$company->save();

View File

@ -19,8 +19,14 @@ class CreateClientRequest extends ClientRequest
*/
public function rules()
{
return [
$rules = [
'contacts' => 'valid_contacts',
];
if ($this->user()->account->client_number_counter) {
$rules['id_number'] = 'unique:clients,id_number,,id,account_id,' . $this->user()->account_id;
}
return $rules;
}
}

View File

@ -1,5 +1,7 @@
<?php namespace App\Http\Requests;
use App\Models\Client;
class CreateInvoiceAPIRequest extends InvoiceRequest
{
/**
@ -31,6 +33,11 @@ class CreateInvoiceAPIRequest extends InvoiceRequest
//'end_date' => 'date',
];
if ($this->user()->account->client_number_counter) {
$clientId = Client::getPrivateId(request()->input('client')['public_id']);
$rules['client.id_number'] = 'unique:clients,id_number,'.$clientId.',id,account_id,' . $this->user()->account_id;
}
return $rules;
}
}

View File

@ -1,5 +1,7 @@
<?php namespace App\Http\Requests;
use App\Models\Client;
class CreateInvoiceRequest extends InvoiceRequest
{
/**
@ -30,6 +32,11 @@ class CreateInvoiceRequest extends InvoiceRequest
//'end_date' => 'date',
];
if ($this->user()->account->client_number_counter) {
$clientId = Client::getPrivateId(request()->input('client')['public_id']);
$rules['client.id_number'] = 'unique:clients,id_number,'.$clientId.',id,account_id,' . $this->user()->account_id;
}
/* There's a problem parsing the dates
if (Request::get('is_recurring') && Request::get('start_date') && Request::get('end_date')) {
$rules['end_date'] = 'after' . Request::get('start_date');

View File

@ -33,9 +33,8 @@ class CreatePaymentAPIRequest extends PaymentRequest
];
}
$invoice = Invoice::scope($this->invoice_id)
$this->invoice = $invoice = Invoice::scope($this->invoice_id)
->invoices()
->whereIsPublic(true)
->firstOrFail();
$this->merge([

View File

@ -22,9 +22,8 @@ class CreatePaymentRequest extends PaymentRequest
public function rules()
{
$input = $this->input();
$invoice = Invoice::scope($input['invoice'])
$this->invoice = $invoice = Invoice::scope($input['invoice'])
->invoices()
->whereIsPublic(true)
->firstOrFail();
$rules = [

View File

@ -0,0 +1,58 @@
<?php namespace App\Http\Requests;
use Utils;
use HTMLUtils;
class SaveClientPortalSettings extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->is_admin && $this->user()->isPro();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [];
if ($this->custom_link == 'subdomain' && Utils::isNinja()) {
$rules['subdomain'] = "unique:accounts,subdomain,{$this->user()->account_id},id|valid_subdomain";
}
return $rules;
}
public function sanitize()
{
$input = $this->all();
if ($this->client_view_css && Utils::isNinja()) {
$input['client_view_css'] = HTMLUtils::sanitize($this->client_view_css);
}
if ($this->custom_link == 'subdomain') {
$subdomain = substr(strtolower($input['subdomain']), 0, MAX_SUBDOMAIN_LENGTH);
$input['subdomain'] = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', $subdomain);
$input['iframe_url'] = null;
} else {
$iframeURL = substr(strtolower($input['iframe_url']), 0, MAX_IFRAME_URL_LENGTH);
$iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', $iframeURL);
$input['iframe_url'] = rtrim($iframeURL, '/');
$input['subdomain'] = null;
}
$this->replace($input);
return $this->all();
}
}

View File

@ -0,0 +1,27 @@
<?php namespace App\Http\Requests;
class SaveEmailSettings extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->is_admin && $this->user()->isPro();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'bcc_email' => 'email',
];
}
}

View File

@ -19,8 +19,14 @@ class UpdateClientRequest extends ClientRequest
*/
public function rules()
{
return [
$rules = [
'contacts' => 'valid_contacts',
];
if ($this->user()->account->client_number_counter) {
$rules['id_number'] = 'unique:clients,id_number,'.$this->entity()->id.',id,account_id,' . $this->user()->account_id;
}
return $rules;
}
}

View File

@ -1,5 +1,7 @@
<?php namespace App\Http\Requests;
use App\Models\Client;
class UpdateInvoiceAPIRequest extends InvoiceRequest
{
/**
@ -35,6 +37,11 @@ class UpdateInvoiceAPIRequest extends InvoiceRequest
//'end_date' => 'date',
];
if ($this->user()->account->client_number_counter) {
$clientId = Client::getPrivateId(request()->input('client')['public_id']);
$rules['client.id_number'] = 'unique:clients,id_number,'.$clientId.',id,account_id,' . $this->user()->account_id;
}
return $rules;
}
}

View File

@ -1,5 +1,7 @@
<?php namespace App\Http\Requests;
use App\Models\Client;
class UpdateInvoiceRequest extends InvoiceRequest
{
/**
@ -32,6 +34,11 @@ class UpdateInvoiceRequest extends InvoiceRequest
//'end_date' => 'date',
];
if ($this->user()->account->client_number_counter) {
$clientId = Client::getPrivateId(request()->input('client')['public_id']);
$rules['client.id_number'] = 'unique:clients,id_number,'.$clientId.',id,account_id,' . $this->user()->account_id;
}
/* There's a problem parsing the dates
if (Request::get('is_recurring') && Request::get('start_date') && Request::get('end_date')) {
$rules['end_date'] = 'after' . Request::get('start_date');

View File

@ -11,14 +11,6 @@
|
*/
//Crypt::decrypt();
//apc_clear_cache();
//dd(DB::getQueryLog());
//dd(Client::getPrivateId(1));
//dd(new DateTime());
//dd(App::environment());
//dd(gethostname());
//Log::error('test');
// Application setup
Route::get('/setup', 'AppController@showSetup');
@ -28,7 +20,6 @@ Route::get('/update', 'AppController@update');
// Public pages
Route::get('/', 'HomeController@showIndex');
Route::get('/terms', 'HomeController@showTerms');
Route::get('/log_error', 'HomeController@logError');
Route::get('/invoice_now', 'HomeController@invoiceNow');
Route::get('/keep_alive', 'HomeController@keepAlive');
@ -131,6 +122,7 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::get('account/get_search_data', ['as' => 'get_search_data', 'uses' => 'AccountController@getSearchData']);
Route::get('check_invoice_number/{invoice_id?}', 'InvoiceController@checkInvoiceNumber');
Route::post('save_sidebar_state', 'UserController@saveSidebarState');
Route::post('contact_us', 'HomeController@contactUs');
Route::get('settings/user_details', 'AccountController@showUserDetails');
Route::post('settings/user_details', 'AccountController@saveUserDetails');
@ -141,10 +133,11 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::get('api/clients', 'ClientController@getDatatable');
Route::get('api/activities/{client_id?}', 'ActivityController@getDatatable');
Route::post('clients/bulk', 'ClientController@bulk');
Route::get('clients/statement/{client_id}', 'ClientController@statement');
Route::resource('tasks', 'TaskController');
Route::get('api/tasks/{client_id?}', 'TaskController@getDatatable');
Route::get('tasks/create/{client_id?}', 'TaskController@create');
Route::get('tasks/create/{client_id?}/{project_id?}', 'TaskController@create');
Route::post('tasks/bulk', 'TaskController@bulk');
Route::get('projects', 'ProjectController@index');
Route::get('api/projects', 'ProjectController@getDatatable');
@ -211,7 +204,7 @@ Route::group(['middleware' => 'auth:user'], function() {
// Expense
Route::resource('expenses', 'ExpenseController');
Route::get('expenses/create/{vendor_id?}/{client_id?}', 'ExpenseController@create');
Route::get('expenses/create/{vendor_id?}/{client_id?}/{category_id?}', 'ExpenseController@create');
Route::get('api/expenses', 'ExpenseController@getDatatable');
Route::get('api/expenses/{id}', 'ExpenseController@getDatatableVendor');
Route::post('expenses/bulk', 'ExpenseController@bulk');
@ -227,6 +220,7 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::post('bluevine/signup', 'BlueVineController@signup');
Route::get('bluevine/hide_message', 'BlueVineController@hideMessage');
Route::get('bluevine/completed', 'BlueVineController@handleCompleted');
Route::get('white_label/hide_message', 'NinjaController@hideWhiteLabelMessage');
});
Route::group([
@ -237,8 +231,6 @@ Route::group([
Route::resource('users', 'UserController');
Route::post('users/bulk', 'UserController@bulk');
Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation');
Route::get('start_trial/{plan}', 'AccountController@startTrial')
->where(['plan'=>'pro']);
Route::get('/switch_account/{user_id}', 'UserController@switchAccount');
Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount');
Route::get('/manage_companies', 'UserController@manageCompanies');
@ -252,10 +244,12 @@ Route::group([
Route::post('tax_rates/bulk', 'TaxRateController@bulk');
Route::get('settings/email_preview', 'AccountController@previewEmail');
Route::post('settings/client_portal', 'AccountController@saveClientPortalSettings');
Route::post('settings/email_settings', 'AccountController@saveEmailSettings');
Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy');
Route::get('settings/data_visualizations', 'ReportController@d3');
Route::get('settings/reports', 'ReportController@showReports');
Route::post('settings/reports', 'ReportController@showReports');
Route::get('reports', 'ReportController@showReports');
Route::post('reports', 'ReportController@showReports');
Route::post('settings/change_plan', 'AccountController@changePlan');
Route::post('settings/cancel_account', 'AccountController@cancelAccount');
@ -359,573 +353,6 @@ Route::get('/comments/feed', function() {
return Redirect::to(NINJA_WEB_URL.'/comments/feed', 301);
});
if (!defined('CONTACT_EMAIL')) {
define('CONTACT_EMAIL', config('mail.from.address'));
define('CONTACT_NAME', config('mail.from.name'));
define('SITE_URL', config('app.url'));
define('ENV_DEVELOPMENT', 'local');
define('ENV_STAGING', 'staging');
define('RECENTLY_VIEWED', 'recent_history');
define('ENTITY_CLIENT', 'client');
define('ENTITY_CONTACT', 'contact');
define('ENTITY_INVOICE', 'invoice');
define('ENTITY_DOCUMENT', 'document');
define('ENTITY_INVOICE_ITEM', 'invoice_item');
define('ENTITY_INVITATION', 'invitation');
define('ENTITY_RECURRING_INVOICE', 'recurring_invoice');
define('ENTITY_PAYMENT', 'payment');
define('ENTITY_CREDIT', 'credit');
define('ENTITY_QUOTE', 'quote');
define('ENTITY_TASK', 'task');
define('ENTITY_ACCOUNT_GATEWAY', 'account_gateway');
define('ENTITY_USER', 'user');
define('ENTITY_TOKEN', 'token');
define('ENTITY_TAX_RATE', 'tax_rate');
define('ENTITY_PRODUCT', 'product');
define('ENTITY_ACTIVITY', 'activity');
define('ENTITY_VENDOR', 'vendor');
define('ENTITY_VENDOR_ACTIVITY', 'vendor_activity');
define('ENTITY_EXPENSE', 'expense');
define('ENTITY_PAYMENT_TERM', 'payment_term');
define('ENTITY_EXPENSE_ACTIVITY', 'expense_activity');
define('ENTITY_BANK_ACCOUNT', 'bank_account');
define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount');
define('ENTITY_EXPENSE_CATEGORY', 'expense_category');
define('ENTITY_PROJECT', 'project');
define('INVOICE_TYPE_STANDARD', 1);
define('INVOICE_TYPE_QUOTE', 2);
define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user');
define('PERSON_VENDOR_CONTACT','vendorcontact');
define('BASIC_SETTINGS', 'basic_settings');
define('ADVANCED_SETTINGS', 'advanced_settings');
define('ACCOUNT_COMPANY_DETAILS', 'company_details');
define('ACCOUNT_USER_DETAILS', 'user_details');
define('ACCOUNT_LOCALIZATION', 'localization');
define('ACCOUNT_NOTIFICATIONS', 'notifications');
define('ACCOUNT_IMPORT_EXPORT', 'import_export');
define('ACCOUNT_MANAGEMENT', 'account_management');
define('ACCOUNT_PAYMENTS', 'online_payments');
define('ACCOUNT_BANKS', 'bank_accounts');
define('ACCOUNT_IMPORT_EXPENSES', 'import_expenses');
define('ACCOUNT_MAP', 'import_map');
define('ACCOUNT_EXPORT', 'export');
define('ACCOUNT_TAX_RATES', 'tax_rates');
define('ACCOUNT_PRODUCTS', 'products');
define('ACCOUNT_ADVANCED_SETTINGS', 'advanced_settings');
define('ACCOUNT_INVOICE_SETTINGS', 'invoice_settings');
define('ACCOUNT_INVOICE_DESIGN', 'invoice_design');
define('ACCOUNT_CLIENT_PORTAL', 'client_portal');
define('ACCOUNT_EMAIL_SETTINGS', 'email_settings');
define('ACCOUNT_REPORTS', 'reports');
define('ACCOUNT_USER_MANAGEMENT', 'user_management');
define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations');
define('ACCOUNT_TEMPLATES_AND_REMINDERS', 'templates_and_reminders');
define('ACCOUNT_API_TOKENS', 'api_tokens');
define('ACCOUNT_CUSTOMIZE_DESIGN', 'customize_design');
define('ACCOUNT_SYSTEM_SETTINGS', 'system_settings');
define('ACCOUNT_PAYMENT_TERMS','payment_terms');
define('ACTION_RESTORE', 'restore');
define('ACTION_ARCHIVE', 'archive');
define('ACTION_CLONE', 'clone');
define('ACTION_CONVERT', 'convert');
define('ACTION_DELETE', 'delete');
define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);
define('ACTIVITY_TYPE_DELETE_CLIENT', 3);
define('ACTIVITY_TYPE_CREATE_INVOICE', 4);
define('ACTIVITY_TYPE_UPDATE_INVOICE', 5);
define('ACTIVITY_TYPE_EMAIL_INVOICE', 6);
define('ACTIVITY_TYPE_VIEW_INVOICE', 7);
define('ACTIVITY_TYPE_ARCHIVE_INVOICE', 8);
define('ACTIVITY_TYPE_DELETE_INVOICE', 9);
define('ACTIVITY_TYPE_CREATE_PAYMENT', 10);
//define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
//define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16);
define('ACTIVITY_TYPE_DELETE_CREDIT', 17);
define('ACTIVITY_TYPE_CREATE_QUOTE', 18);
define('ACTIVITY_TYPE_UPDATE_QUOTE', 19);
define('ACTIVITY_TYPE_EMAIL_QUOTE', 20);
define('ACTIVITY_TYPE_VIEW_QUOTE', 21);
define('ACTIVITY_TYPE_ARCHIVE_QUOTE', 22);
define('ACTIVITY_TYPE_DELETE_QUOTE', 23);
define('ACTIVITY_TYPE_RESTORE_QUOTE', 24);
define('ACTIVITY_TYPE_RESTORE_INVOICE', 25);
define('ACTIVITY_TYPE_RESTORE_CLIENT', 26);
define('ACTIVITY_TYPE_RESTORE_PAYMENT', 27);
define('ACTIVITY_TYPE_RESTORE_CREDIT', 28);
define('ACTIVITY_TYPE_APPROVE_QUOTE', 29);
define('ACTIVITY_TYPE_CREATE_VENDOR', 30);
define('ACTIVITY_TYPE_ARCHIVE_VENDOR', 31);
define('ACTIVITY_TYPE_DELETE_VENDOR', 32);
define('ACTIVITY_TYPE_RESTORE_VENDOR', 33);
define('ACTIVITY_TYPE_CREATE_EXPENSE', 34);
define('ACTIVITY_TYPE_ARCHIVE_EXPENSE', 35);
define('ACTIVITY_TYPE_DELETE_EXPENSE', 36);
define('ACTIVITY_TYPE_RESTORE_EXPENSE', 37);
define('ACTIVITY_TYPE_VOIDED_PAYMENT', 39);
define('ACTIVITY_TYPE_REFUNDED_PAYMENT', 40);
define('ACTIVITY_TYPE_FAILED_PAYMENT', 41);
define('ACTIVITY_TYPE_CREATE_TASK', 42);
define('ACTIVITY_TYPE_UPDATE_TASK', 43);
define('ACTIVITY_TYPE_ARCHIVE_TASK', 44);
define('ACTIVITY_TYPE_DELETE_TASK', 45);
define('ACTIVITY_TYPE_RESTORE_TASK', 46);
define('ACTIVITY_TYPE_UPDATE_EXPENSE', 47);
define('DEFAULT_INVOICE_NUMBER', '0001');
define('RECENTLY_VIEWED_LIMIT', 20);
define('LOGGED_ERROR_LIMIT', 100);
define('RANDOM_KEY_LENGTH', 32);
define('MAX_NUM_USERS', 20);
define('MAX_IMPORT_ROWS', 500);
define('MAX_SUBDOMAIN_LENGTH', 30);
define('MAX_IFRAME_URL_LENGTH', 250);
define('MAX_LOGO_FILE_SIZE', 200); // KB
define('MAX_FAILED_LOGINS', 10);
define('MAX_INVOICE_ITEMS', env('MAX_INVOICE_ITEMS', 100));
define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000));// KB
define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000));// Total KB
define('MAX_ZIP_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 30000));// Total KB (uncompressed)
define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 300));// pixels
define('DEFAULT_FONT_SIZE', 9);
define('DEFAULT_HEADER_FONT', 1);// Roboto
define('DEFAULT_BODY_FONT', 1);// Roboto
define('DEFAULT_SEND_RECURRING_HOUR', 8);
define('IMPORT_CSV', 'CSV');
define('IMPORT_JSON', 'JSON');
define('IMPORT_FRESHBOOKS', 'FreshBooks');
define('IMPORT_WAVE', 'Wave');
define('IMPORT_RONIN', 'Ronin');
define('IMPORT_HIVEAGE', 'Hiveage');
define('IMPORT_ZOHO', 'Zoho');
define('IMPORT_NUTCACHE', 'Nutcache');
define('IMPORT_INVOICEABLE', 'Invoiceable');
define('IMPORT_HARVEST', 'Harvest');
define('MAX_NUM_CLIENTS', 100);
define('MAX_NUM_CLIENTS_PRO', 20000);
define('MAX_NUM_CLIENTS_LEGACY', 500);
define('MAX_INVOICE_AMOUNT', 1000000000);
define('LEGACY_CUTOFF', 57800);
define('ERROR_DELAY', 3);
define('MAX_NUM_VENDORS', 100);
define('MAX_NUM_VENDORS_PRO', 20000);
define('STATUS_ACTIVE', 'active');
define('STATUS_ARCHIVED', 'archived');
define('STATUS_DELETED', 'deleted');
define('INVOICE_STATUS_DRAFT', 1);
define('INVOICE_STATUS_SENT', 2);
define('INVOICE_STATUS_VIEWED', 3);
define('INVOICE_STATUS_APPROVED', 4);
define('INVOICE_STATUS_PARTIAL', 5);
define('INVOICE_STATUS_PAID', 6);
define('INVOICE_STATUS_OVERDUE', 7);
define('PAYMENT_STATUS_PENDING', 1);
define('PAYMENT_STATUS_VOIDED', 2);
define('PAYMENT_STATUS_FAILED', 3);
define('PAYMENT_STATUS_COMPLETED', 4);
define('PAYMENT_STATUS_PARTIALLY_REFUNDED', 5);
define('PAYMENT_STATUS_REFUNDED', 6);
define('TASK_STATUS_LOGGED', 1);
define('TASK_STATUS_RUNNING', 2);
define('TASK_STATUS_INVOICED', 3);
define('TASK_STATUS_PAID', 4);
define('EXPENSE_STATUS_LOGGED', 1);
define('EXPENSE_STATUS_INVOICED', 2);
define('EXPENSE_STATUS_PAID', 3);
define('CUSTOM_DESIGN', 11);
define('FREQUENCY_WEEKLY', 1);
define('FREQUENCY_TWO_WEEKS', 2);
define('FREQUENCY_FOUR_WEEKS', 3);
define('FREQUENCY_MONTHLY', 4);
define('FREQUENCY_THREE_MONTHS', 5);
define('FREQUENCY_SIX_MONTHS', 6);
define('FREQUENCY_ANNUALLY', 7);
define('SESSION_TIMEZONE', 'timezone');
define('SESSION_CURRENCY', 'currency');
define('SESSION_CURRENCY_DECORATOR', 'currency_decorator');
define('SESSION_DATE_FORMAT', 'dateFormat');
define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat');
define('SESSION_DATETIME_FORMAT', 'datetimeFormat');
define('SESSION_COUNTER', 'sessionCounter');
define('SESSION_LOCALE', 'sessionLocale');
define('SESSION_USER_ACCOUNTS', 'userAccounts');
define('SESSION_REFERRAL_CODE', 'referralCode');
define('SESSION_LEFT_SIDEBAR', 'showLeftSidebar');
define('SESSION_RIGHT_SIDEBAR', 'showRightSidebar');
define('SESSION_LAST_REQUEST_PAGE', 'SESSION_LAST_REQUEST_PAGE');
define('SESSION_LAST_REQUEST_TIME', 'SESSION_LAST_REQUEST_TIME');
define('CURRENCY_DOLLAR', 1);
define('CURRENCY_EURO', 3);
define('DEFAULT_TIMEZONE', 'US/Eastern');
define('DEFAULT_COUNTRY', 840); // United Stated
define('DEFAULT_CURRENCY', CURRENCY_DOLLAR);
define('DEFAULT_LANGUAGE', 1); // English
define('DEFAULT_DATE_FORMAT', 'M j, Y');
define('DEFAULT_DATE_PICKER_FORMAT', 'M d, yyyy');
define('DEFAULT_DATETIME_FORMAT', 'F j, Y g:i a');
define('DEFAULT_DATETIME_MOMENT_FORMAT', 'MMM D, YYYY h:mm:ss a');
define('DEFAULT_LOCALE', 'en');
define('DEFAULT_MAP_ZOOM', 10);
define('RESULT_SUCCESS', 'success');
define('RESULT_FAILURE', 'failure');
define('PAYMENT_LIBRARY_OMNIPAY', 1);
define('PAYMENT_LIBRARY_PHP_PAYMENTS', 2);
define('GATEWAY_AUTHORIZE_NET', 1);
define('GATEWAY_EWAY', 4);
define('GATEWAY_MOLLIE', 9);
define('GATEWAY_PAYFAST', 13);
define('GATEWAY_PAYPAL_EXPRESS', 17);
define('GATEWAY_PAYPAL_PRO', 18);
define('GATEWAY_SAGE_PAY_DIRECT', 20);
define('GATEWAY_SAGE_PAY_SERVER', 21);
define('GATEWAY_STRIPE', 23);
define('GATEWAY_GOCARDLESS', 6);
define('GATEWAY_TWO_CHECKOUT', 27);
define('GATEWAY_BEANSTREAM', 29);
define('GATEWAY_PSIGATE', 30);
define('GATEWAY_MOOLAH', 31);
define('GATEWAY_BITPAY', 42);
define('GATEWAY_DWOLLA', 43);
define('GATEWAY_CHECKOUT_COM', 47);
define('GATEWAY_CYBERSOURCE', 49);
define('GATEWAY_WEPAY', 60);
define('GATEWAY_BRAINTREE', 61);
define('GATEWAY_CUSTOM', 62);
// The customer exists, but only as a local concept
// The remote gateway doesn't understand the concept of customers
define('CUSTOMER_REFERENCE_LOCAL', 'local');
define('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2);
define('EVENT_CREATE_QUOTE', 3);
define('EVENT_CREATE_PAYMENT', 4);
define('EVENT_CREATE_VENDOR',5);
define('REQUESTED_PRO_PLAN', 'REQUESTED_PRO_PLAN');
define('DEMO_ACCOUNT_ID', 'DEMO_ACCOUNT_ID');
define('PREV_USER_ID', 'PREV_USER_ID');
define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h');
define('NINJA_GATEWAY_ID', GATEWAY_STRIPE);
define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG');
define('NINJA_WEB_URL', env('NINJA_WEB_URL', 'https://www.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_DATE', '2000-01-01');
define('NINJA_VERSION', '2.9.5' . env('NINJA_VERSION_SUFFIX'));
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
define('SOCIAL_LINK_GITHUB', env('SOCIAL_LINK_GITHUB', 'https://github.com/invoiceninja/invoiceninja/'));
define('NINJA_FORUM_URL', env('NINJA_FORUM_URL', 'https://www.invoiceninja.com/forums/forum/support/'));
define('NINJA_CONTACT_URL', env('NINJA_CONTACT_URL', 'https://www.invoiceninja.com/contact/'));
define('NINJA_FROM_EMAIL', env('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'));
define('RELEASES_URL', env('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja'));
define('ZAPIER_URL', env('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja'));
define('OUTDATE_BROWSER_URL', env('OUTDATE_BROWSER_URL', 'http://browsehappy.com/'));
define('PDFMAKE_DOCS', env('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html'));
define('PHANTOMJS_CLOUD', env('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/api/browser/v2/'));
define('PHP_DATE_FORMATS', env('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php'));
define('REFERRAL_PROGRAM_URL', env('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/referral-program/'));
define('EMAIL_MARKUP_URL', env('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup'));
define('OFX_HOME_URL', env('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'));
define('GOOGLE_ANALYITCS_URL', env('GOOGLE_ANALYITCS_URL', 'https://www.google-analytics.com/collect'));
define('TRANSIFEX_URL', env('TRANSIFEX_URL', 'https://www.transifex.com/invoice-ninja/invoice-ninja'));
define('CHROME_PDF_HELP_URL', 'https://support.google.com/chrome/answer/6213030?hl=en');
define('FIREFOX_PDF_HELP_URL', 'https://support.mozilla.org/en-US/kb/view-pdf-files-firefox');
define('MSBOT_LOGIN_URL', 'https://login.microsoftonline.com/common/oauth2/v2.0/token');
define('MSBOT_LUIS_URL', 'https://api.projectoxford.ai/luis/v1/application');
define('SKYPE_API_URL', 'https://apis.skype.com/v3');
define('MSBOT_STATE_URL', 'https://state.botframework.com/v3');
define('BLANK_IMAGE', 'data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=');
define('COUNT_FREE_DESIGNS', 4);
define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design
define('PRODUCT_ONE_CLICK_INSTALL', 1);
define('PRODUCT_INVOICE_DESIGNS', 2);
define('PRODUCT_WHITE_LABEL', 3);
define('PRODUCT_SELF_HOST', 4);
define('WHITE_LABEL_AFFILIATE_KEY', '92D2J5');
define('INVOICE_DESIGNS_AFFILIATE_KEY', 'T3RS74');
define('SELF_HOST_AFFILIATE_KEY', '8S69AD');
define('PLAN_PRICE_PRO_MONTHLY', env('PLAN_PRICE_PRO_MONTHLY', 8));
define('PLAN_PRICE_ENTERPRISE_MONTHLY_2', env('PLAN_PRICE_ENTERPRISE_MONTHLY_2', 12));
define('PLAN_PRICE_ENTERPRISE_MONTHLY_5', env('PLAN_PRICE_ENTERPRISE_MONTHLY_5', 18));
define('PLAN_PRICE_ENTERPRISE_MONTHLY_10', env('PLAN_PRICE_ENTERPRISE_MONTHLY_10', 24));
define('WHITE_LABEL_PRICE', env('WHITE_LABEL_PRICE', 20));
define('INVOICE_DESIGNS_PRICE', env('INVOICE_DESIGNS_PRICE', 10));
define('USER_TYPE_SELF_HOST', 'SELF_HOST');
define('USER_TYPE_CLOUD_HOST', 'CLOUD_HOST');
define('NEW_VERSION_AVAILABLE', 'NEW_VERSION_AVAILABLE');
define('TEST_USERNAME', 'user@example.com');
define('TEST_PASSWORD', 'password');
define('API_SECRET', 'API_SECRET');
define('DEFAULT_API_PAGE_SIZE', 15);
define('MAX_API_PAGE_SIZE', 500);
define('IOS_PUSH_CERTIFICATE', env('IOS_PUSH_CERTIFICATE', ''));
define('TOKEN_BILLING_DISABLED', 1);
define('TOKEN_BILLING_OPT_IN', 2);
define('TOKEN_BILLING_OPT_OUT', 3);
define('TOKEN_BILLING_ALWAYS', 4);
define('PAYMENT_TYPE_CREDIT', 1);
define('PAYMENT_TYPE_ACH', 5);
define('PAYMENT_TYPE_VISA', 6);
define('PAYMENT_TYPE_MASTERCARD', 7);
define('PAYMENT_TYPE_AMERICAN_EXPRESS', 8);
define('PAYMENT_TYPE_DISCOVER', 9);
define('PAYMENT_TYPE_DINERS', 10);
define('PAYMENT_TYPE_EUROCARD', 11);
define('PAYMENT_TYPE_NOVA', 12);
define('PAYMENT_TYPE_CREDIT_CARD_OTHER', 13);
define('PAYMENT_TYPE_PAYPAL', 14);
define('PAYMENT_TYPE_CARTE_BLANCHE', 17);
define('PAYMENT_TYPE_UNIONPAY', 18);
define('PAYMENT_TYPE_JCB', 19);
define('PAYMENT_TYPE_LASER', 20);
define('PAYMENT_TYPE_MAESTRO', 21);
define('PAYMENT_TYPE_SOLO', 22);
define('PAYMENT_TYPE_SWITCH', 23);
define('PAYMENT_METHOD_STATUS_NEW', 'new');
define('PAYMENT_METHOD_STATUS_VERIFICATION_FAILED', 'verification_failed');
define('PAYMENT_METHOD_STATUS_VERIFIED', 'verified');
define('GATEWAY_TYPE_CREDIT_CARD', 1);
define('GATEWAY_TYPE_BANK_TRANSFER', 2);
define('GATEWAY_TYPE_PAYPAL', 3);
define('GATEWAY_TYPE_BITCOIN', 4);
define('GATEWAY_TYPE_DWOLLA', 5);
define('GATEWAY_TYPE_CUSTOM', 6);
define('GATEWAY_TYPE_TOKEN', 'token');
define('REMINDER1', 'reminder1');
define('REMINDER2', 'reminder2');
define('REMINDER3', 'reminder3');
define('REMINDER_DIRECTION_AFTER', 1);
define('REMINDER_DIRECTION_BEFORE', 2);
define('REMINDER_FIELD_DUE_DATE', 1);
define('REMINDER_FIELD_INVOICE_DATE', 2);
define('FILTER_INVOICE_DATE', 'invoice_date');
define('FILTER_PAYMENT_DATE', 'payment_date');
define('SOCIAL_GOOGLE', 'Google');
define('SOCIAL_FACEBOOK', 'Facebook');
define('SOCIAL_GITHUB', 'GitHub');
define('SOCIAL_LINKEDIN', 'LinkedIn');
define('USER_STATE_ACTIVE', 'active');
define('USER_STATE_PENDING', 'pending');
define('USER_STATE_DISABLED', 'disabled');
define('USER_STATE_ADMIN', 'admin');
define('USER_STATE_OWNER', 'owner');
define('API_SERIALIZER_ARRAY', 'array');
define('API_SERIALIZER_JSON', 'json');
define('EMAIL_DESIGN_PLAIN', 1);
define('EMAIL_DESIGN_LIGHT', 2);
define('EMAIL_DESIGN_DARK', 3);
define('BANK_LIBRARY_OFX', 1);
define('CURRENCY_DECORATOR_CODE', 'code');
define('CURRENCY_DECORATOR_SYMBOL', 'symbol');
define('CURRENCY_DECORATOR_NONE', 'none');
define('RESELLER_REVENUE_SHARE', 'A');
define('RESELLER_LIMITED_USERS', 'B');
define('AUTO_BILL_OFF', 1);
define('AUTO_BILL_OPT_IN', 2);
define('AUTO_BILL_OPT_OUT', 3);
define('AUTO_BILL_ALWAYS', 4);
// These must be lowercase
define('PLAN_FREE', 'free');
define('PLAN_PRO', 'pro');
define('PLAN_ENTERPRISE', 'enterprise');
define('PLAN_WHITE_LABEL', 'white_label');
define('PLAN_TERM_MONTHLY', 'month');
define('PLAN_TERM_YEARLY', 'year');
// Pro
define('FEATURE_CUSTOMIZE_INVOICE_DESIGN', 'customize_invoice_design');
define('FEATURE_REMOVE_CREATED_BY', 'remove_created_by');
define('FEATURE_DIFFERENT_DESIGNS', 'different_designs');
define('FEATURE_EMAIL_TEMPLATES_REMINDERS', 'email_templates_reminders');
define('FEATURE_INVOICE_SETTINGS', 'invoice_settings');
define('FEATURE_CUSTOM_EMAILS', 'custom_emails');
define('FEATURE_PDF_ATTACHMENT', 'pdf_attachment');
define('FEATURE_MORE_INVOICE_DESIGNS', 'more_invoice_designs');
define('FEATURE_QUOTES', 'quotes');
define('FEATURE_TASKS', 'tasks');
define('FEATURE_EXPENSES', 'expenses');
define('FEATURE_REPORTS', 'reports');
define('FEATURE_BUY_NOW_BUTTONS', 'buy_now_buttons');
define('FEATURE_API', 'api');
define('FEATURE_CLIENT_PORTAL_PASSWORD', 'client_portal_password');
define('FEATURE_CUSTOM_URL', 'custom_url');
define('FEATURE_MORE_CLIENTS', 'more_clients'); // No trial allowed
// Whitelabel
define('FEATURE_CLIENT_PORTAL_CSS', 'client_portal_css');
define('FEATURE_WHITE_LABEL', 'feature_white_label');
// Enterprise
define('FEATURE_DOCUMENTS', 'documents');
// No Trial allowed
define('FEATURE_USERS', 'users');// Grandfathered for old Pro users
define('FEATURE_USER_PERMISSIONS', 'user_permissions');
// Pro users who started paying on or before this date will be able to manage users
define('PRO_USERS_GRANDFATHER_DEADLINE', '2016-06-04');
define('EXTRAS_GRANDFATHER_COMPANY_ID', 35089);
// WePay
define('WEPAY_PRODUCTION', 'production');
define('WEPAY_STAGE', 'stage');
define('WEPAY_CLIENT_ID', env('WEPAY_CLIENT_ID'));
define('WEPAY_CLIENT_SECRET', env('WEPAY_CLIENT_SECRET'));
define('WEPAY_AUTO_UPDATE', env('WEPAY_AUTO_UPDATE', false));
define('WEPAY_ENVIRONMENT', env('WEPAY_ENVIRONMENT', WEPAY_PRODUCTION));
define('WEPAY_ENABLE_CANADA', env('WEPAY_ENABLE_CANADA', false));
define('WEPAY_THEME', env('WEPAY_THEME','{"name":"Invoice Ninja","primary_color":"0b4d78","secondary_color":"0b4d78","background_color":"f8f8f8","button_color":"33b753"}'));
define('SKYPE_CARD_RECEIPT', 'message/card.receipt');
define('SKYPE_CARD_CAROUSEL', 'message/card.carousel');
define('SKYPE_CARD_HERO', '');
define('BOT_STATE_GET_EMAIL', 'get_email');
define('BOT_STATE_GET_CODE', 'get_code');
define('BOT_STATE_READY', 'ready');
define('SIMILAR_MIN_THRESHOLD', 50);
// https://docs.botframework.com/en-us/csharp/builder/sdkreference/attachments.html
define('SKYPE_BUTTON_OPEN_URL', 'openUrl');
define('SKYPE_BUTTON_IM_BACK', 'imBack');
define('SKYPE_BUTTON_POST_BACK', 'postBack');
define('SKYPE_BUTTON_CALL', 'call'); // "tel:123123123123"
define('SKYPE_BUTTON_PLAY_AUDIO', 'playAudio');
define('SKYPE_BUTTON_PLAY_VIDEO', 'playVideo');
define('SKYPE_BUTTON_SHOW_IMAGE', 'showImage');
define('SKYPE_BUTTON_DOWNLOAD_FILE', 'downloadFile');
define('INVOICE_FIELDS_CLIENT', 'client_fields');
define('INVOICE_FIELDS_INVOICE', 'invoice_fields');
define('INVOICE_FIELDS_ACCOUNT', 'account_fields');
$creditCards = [
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],
4 => ['card' => 'images/credit_cards/Test-AmericanExpress-Icon.png', 'text' => 'American Express'],
8 => ['card' => 'images/credit_cards/Test-Diners-Icon.png', 'text' => 'Diners'],
16 => ['card' => 'images/credit_cards/Test-Discover-Icon.png', 'text' => 'Discover']
];
define('CREDIT_CARDS', serialize($creditCards));
$cachedTables = [
'currencies' => 'App\Models\Currency',
'sizes' => 'App\Models\Size',
'industries' => 'App\Models\Industry',
'timezones' => 'App\Models\Timezone',
'dateFormats' => 'App\Models\DateFormat',
'datetimeFormats' => 'App\Models\DatetimeFormat',
'languages' => 'App\Models\Language',
'paymentTerms' => 'App\Models\PaymentTerm',
'paymentTypes' => 'App\Models\PaymentType',
'countries' => 'App\Models\Country',
'invoiceDesigns' => 'App\Models\InvoiceDesign',
'invoiceStatus' => 'App\Models\InvoiceStatus',
'frequencies' => 'App\Models\Frequency',
'gateways' => 'App\Models\Gateway',
'gatewayTypes' => 'App\Models\GatewayType',
'fonts' => 'App\Models\Font',
'banks' => 'App\Models\Bank',
];
define('CACHED_TABLES', serialize($cachedTables));
function uctrans($text)
{
return ucwords(trans($text));
}
// optional trans: only return the string if it's translated
function otrans($text)
{
$locale = Session::get(SESSION_LOCALE);
if ($locale == 'en') {
return trans($text);
} else {
$string = trans($text);
$english = trans($text, [], 'en');
return $string != $english ? $string : '';
}
}
// include modules in translations
function mtrans($entityType, $text = false)
{
if ( ! $text) {
$text = $entityType;
}
if ( ! Utils::isNinjaProd() && $module = Module::find($entityType)) {
return trans("{$module->getLowerName()}::texts.{$text}");
} else {
return trans("texts.{$text}");
}
}
}
/*
if (Utils::isNinjaDev())
{
@ -934,3 +361,6 @@ if (Utils::isNinjaDev())
Auth::loginUsingId(1);
}
*/
// Include static app constants
require_once app_path() . '/Constants.php';

47
app/Jobs/Job.php Normal file
View File

@ -0,0 +1,47 @@
<?php
namespace App\Jobs;
use App\Ninja\Mailers\ContactMailer;
use Illuminate\Bus\Queueable;
use Monolog\Logger;
abstract class Job
{
use Queueable;
/**
* The name of the job.
*
* @var string
*/
protected $jobName;
/**
* Handle a job failure.
*
* @param ContactMailer $mailer
* @param Logger $logger
*/
/*
protected function failed(ContactMailer $mailer, Logger $logger)
{
if(config('queue.failed.notify_email')) {
$mailer->sendTo(
config('queue.failed.notify_email'),
config('mail.from.address'),
config('mail.from.name'),
config('queue.failed.notify_subject', trans('texts.job_failed', ['name'=>$this->jobName])),
'job_failed',
[
'name' => $this->jobName,
]
);
}
$logger->error(
trans('texts.job_failed', ['name' => $this->jobName])
);
}
*/
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Jobs;
use App\Models\Invoice;
use App\Ninja\Mailers\ContactMailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Monolog\Logger;
use Carbon;
/**
* Class SendInvoiceEmail
*/
class SendInvoiceEmail extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
/**
* @var Invoice
*/
protected $invoice;
/**
* @var bool
*/
protected $reminder;
/**
* @var string
*/
protected $pdfString;
/**
* Create a new job instance.
*
* @param Invoice $invoice
* @param string $pdf
* @param bool $reminder
*/
public function __construct(Invoice $invoice, $reminder = false, $pdfString = false)
{
$this->invoice = $invoice;
$this->reminder = $reminder;
$this->pdfString = $pdfString;
}
/**
* Execute the job.
*
* @param ContactMailer $mailer
*/
public function handle(ContactMailer $mailer)
{
$mailer->sendInvoice($this->invoice, $this->reminder, $this->pdfString);
}
/**
* Handle a job failure.
*
* @param ContactMailer $mailer
* @param Logger $logger
*/
/*
public function failed(ContactMailer $mailer, Logger $logger)
{
$this->jobName = $this->job->getName();
parent::failed($mailer, $logger);
}
*/
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Jobs;
use App\Models\Payment;
use App\Ninja\Mailers\UserMailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Monolog\Logger;
use Carbon;
/**
* Class SendInvoiceEmail
*/
class SendNotificationEmail extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
/**
* @var User
*/
protected $user;
/**
* @var Invoice
*/
protected $invoice;
/**
* @var string
*/
protected $type;
/**
* @var Payment
*/
protected $payment;
/**
* Create a new job instance.
* @param UserMailer $userMailer
* @param ContactMailer $contactMailer
* @param PushService $pushService
*/
public function __construct($user, $invoice, $type, $payment)
{
$this->user = $user;
$this->invoice = $invoice;
$this->type = $type;
$this->payment = $payment;
}
/**
* Execute the job.
*
* @param ContactMailer $mailer
*/
public function handle(UserMailer $userMailer)
{
$userMailer->sendNotification($this->user, $this->invoice, $this->type, $this->payment);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Jobs;
use App\Models\Payment;
use App\Ninja\Mailers\ContactMailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Monolog\Logger;
use Carbon;
/**
* Class SendInvoiceEmail
*/
class SendPaymentEmail extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
/**
* @var Payment
*/
protected $payment;
/**
* Create a new job instance.
* @param Payment $payment
*/
public function __construct($payment)
{
$this->payment = $payment;
}
/**
* Execute the job.
*
* @param ContactMailer $mailer
*/
public function handle(ContactMailer $contactMailer)
{
$contactMailer->sendPaymentConfirmation($this->payment);
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Jobs;
use App\Models\Invoice;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Services\PushService;
use Monolog\Logger;
use Carbon;
/**
* Class SendInvoiceEmail
*/
class SendPushNotification extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
/**
* @var Invoice
*/
protected $invoice;
/**
* @var string
*/
protected $type;
/**
* Create a new job instance.
* @param Invoice $invoice
*/
public function __construct($invoice, $type)
{
$this->invoice = $invoice;
$this->type = $type;
}
/**
* Execute the job.
*
* @param PushService $pushService
*/
public function handle(PushService $pushService)
{
$pushService->sendNotification($this->invoice, $this->type);
}
}

View File

@ -1,5 +1,7 @@
<?php namespace App\Libraries;
use JonnyW\PhantomJs\Client;
class CurlUtils
{
public static function post($url, $data, $headers = false)
@ -38,4 +40,28 @@ class CurlUtils
return $response;
}
public static function phantom($method, $url)
{
if ( ! $path = env('PHANTOMJS_BIN_PATH')) {
return false;
}
$client = Client::getInstance();
$client->getEngine()->setPath($path);
$request = $client->getMessageFactory()->createRequest($url, $method);
$response = $client->getMessageFactory()->createResponse();
// Send the request
$client->send($request, $response);
if ($response->getStatus() === 200) {
return $response->getContent();
} else {
//$response->getStatus();
return false;
}
}
}

View File

@ -0,0 +1,37 @@
<?php namespace App\Libraries;
use HTMLPurifier;
use HTMLPurifier_Config;
class HTMLUtils
{
public static function sanitize($css)
{
// Allow referencing the body element
$css = preg_replace('/(?<![a-z0-9\-\_\#\.])body(?![a-z0-9\-\_])/i', '.body', $css);
//
// Inspired by http://stackoverflow.com/a/5209050/1721527, dleavitt <https://stackoverflow.com/users/362110/dleavitt>
//
// Create a new configuration object
$config = HTMLPurifier_Config::createDefault();
$config->set('Filter.ExtractStyleBlocks', true);
$config->set('CSS.AllowImportant', true);
$config->set('CSS.AllowTricky', true);
$config->set('CSS.Trusted', true);
// Create a new purifier instance
$purifier = new HTMLPurifier($config);
// Wrap our CSS in style tags and pass to purifier.
// we're not actually interested in the html response though
$purifier->purify('<style>'.$css.'</style>');
// The "style" blocks are stored seperately
$css = $purifier->context->get('StyleBlocks');
// Get the first style block
return count($css) ? $css[0] : '';
}
}

View File

@ -67,6 +67,11 @@ class Utils
return self::isNinjaProd() || self::isNinjaDev();
}
public static function isSelfHost()
{
return ! static::isNinjaProd();
}
public static function isNinjaProd()
{
if (Utils::isReseller()) {
@ -97,13 +102,21 @@ class Utils
public static function isWhiteLabel()
{
if (Utils::isNinjaProd()) {
return false;
$account = false;
if (Utils::isNinja()) {
if (Auth::check()) {
$account = Auth::user()->account;
} elseif ($contactKey = session('contact_key')) {
if ($contact = \App\Models\Contact::whereContactKey($contactKey)->first()) {
$account = $contact->account;
}
}
} else {
$account = \App\Models\Account::first();
}
$account = \App\Models\Account::first();
return $account && $account->hasFeature(FEATURE_WHITE_LABEL);
return $account ? $account->hasFeature(FEATURE_WHITE_LABEL) : false;
}
public static function getResllerType()
@ -111,6 +124,11 @@ class Utils
return isset($_ENV['RESELLER_TYPE']) ? $_ENV['RESELLER_TYPE'] : false;
}
public static function getTermsLink()
{
return static::isNinja() ? NINJA_WEB_URL.'/terms' : NINJA_WEB_URL.'/self-hosting-the-invoice-ninja-platform';
}
public static function isOAuthEnabled()
{
$providers = [
@ -238,6 +256,8 @@ class Utils
$price = PLAN_PRICE_ENTERPRISE_MONTHLY_5;
} elseif ($numUsers <= 10) {
$price = PLAN_PRICE_ENTERPRISE_MONTHLY_10;
} elseif ($numUsers <= 20) {
$price = PLAN_PRICE_ENTERPRISE_MONTHLY_20;
} else {
static::fatalError('Invalid number of users: ' . $numUsers);
}
@ -256,8 +276,10 @@ class Utils
return 1;
} elseif ($max <= 5) {
return 3;
} else {
} elseif ($max <= 10) {
return 6;
} else {
return 11;
}
}
@ -266,7 +288,7 @@ class Utils
return substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/') + 1);
}
public static function trans($input)
public static function trans($input, $module = false)
{
$data = [];
@ -274,7 +296,11 @@ class Utils
if ($field == 'checkbox') {
$data[] = $field;
} elseif ($field) {
$data[] = trans("texts.$field");
if ($module) {
$data[] = mtrans($module, $field);
} else {
$data[] = trans("texts.$field");
}
} else {
$data[] = '';
}

View File

@ -132,7 +132,7 @@ class ActivityListener
}
$backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts')
->withArchived()
->withTrashed()
->find($event->invoice->id);
$activity = $this->activityRepo->create(
@ -200,7 +200,8 @@ class ActivityListener
ACTIVITY_TYPE_EMAIL_INVOICE,
false,
false,
$event->invitation
$event->invitation,
$event->notes
);
}
@ -238,7 +239,9 @@ class ActivityListener
return;
}
$backupQuote = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($event->quote->id);
$backupQuote = Invoice::with('invoice_items', 'client.account', 'client.contacts')
->withTrashed()
->find($event->quote->id);
$activity = $this->activityRepo->create(
$event->quote,
@ -296,7 +299,8 @@ class ActivityListener
ACTIVITY_TYPE_EMAIL_QUOTE,
false,
false,
$event->invitation
$event->invitation,
$event->notes
);
}

View File

@ -52,6 +52,10 @@ class HandleUserLoggedIn {
$account->loadLocalizationSettings();
if (strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') || strpos($_SERVER['HTTP_USER_AGENT'], 'iPad')) {
Session::flash('warning', trans('texts.iphone_app_message', ['link' => link_to(NINJA_IOS_APP_URL, trans('texts.iphone_app'))]));
}
// if they're using Stripe make sure they're using Stripe.js
$accountGateway = $account->getGatewayConfig(GATEWAY_STRIPE);
if ($accountGateway && ! $accountGateway->getPublishableStripeKey()) {

View File

@ -2,6 +2,7 @@
use Utils;
use Auth;
use App\Models\Activity;
use App\Events\InvoiceWasUpdated;
use App\Events\InvoiceWasCreated;
use App\Events\PaymentWasCreated;
@ -69,6 +70,13 @@ class InvoiceListener
$invoice->updateBalances($adjustment, $partial);
$invoice->updatePaidStatus();
// store a backup of the invoice
$activity = Activity::wherePaymentId($payment->id)
->whereActivityTypeId(ACTIVITY_TYPE_CREATE_PAYMENT)
->first();
$activity->json_backup = $invoice->hidePrivateFields()->toJSON();
$activity->save();
}
/**

View File

@ -1,46 +1,20 @@
<?php namespace App\Listeners;
use App\Ninja\Mailers\UserMailer;
use App\Ninja\Mailers\ContactMailer;
use App\Events\InvoiceWasEmailed;
use App\Events\QuoteWasEmailed;
use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed;
use App\Events\QuoteInvitationWasApproved;
use App\Events\PaymentWasCreated;
use App\Services\PushService;
use App\Jobs\SendPaymentEmail;
use App\Jobs\SendNotificationEmail;
use App\Jobs\SendPushNotification;
/**
* Class NotificationListener
*/
class NotificationListener
{
/**
* @var UserMailer
*/
protected $userMailer;
/**
* @var ContactMailer
*/
protected $contactMailer;
/**
* @var PushService
*/
protected $pushService;
/**
* NotificationListener constructor.
* @param UserMailer $userMailer
* @param ContactMailer $contactMailer
* @param PushService $pushService
*/
public function __construct(UserMailer $userMailer, ContactMailer $contactMailer, PushService $pushService)
{
$this->userMailer = $userMailer;
$this->contactMailer = $contactMailer;
$this->pushService = $pushService;
}
/**
* @param $invoice
* @param $type
@ -52,7 +26,7 @@ class NotificationListener
{
if ($user->{"notify_{$type}"})
{
$this->userMailer->sendNotification($user, $invoice, $type, $payment);
dispatch(new SendNotificationEmail($user, $invoice, $type, $payment));
}
}
}
@ -63,7 +37,7 @@ class NotificationListener
public function emailedInvoice(InvoiceWasEmailed $event)
{
$this->sendEmails($event->invoice, 'sent');
$this->pushService->sendNotification($event->invoice, 'sent');
dispatch(new SendPushNotification($event->invoice, 'sent'));
}
/**
@ -72,7 +46,7 @@ class NotificationListener
public function emailedQuote(QuoteWasEmailed $event)
{
$this->sendEmails($event->quote, 'sent');
$this->pushService->sendNotification($event->quote, 'sent');
dispatch(new SendPushNotification($event->quote, 'sent'));
}
/**
@ -85,7 +59,7 @@ class NotificationListener
}
$this->sendEmails($event->invoice, 'viewed');
$this->pushService->sendNotification($event->invoice, 'viewed');
dispatch(new SendPushNotification($event->invoice, 'viewed'));
}
/**
@ -98,7 +72,7 @@ class NotificationListener
}
$this->sendEmails($event->quote, 'viewed');
$this->pushService->sendNotification($event->quote, 'viewed');
dispatch(new SendPushNotification($event->quote, 'viewed'));
}
/**
@ -107,7 +81,7 @@ class NotificationListener
public function approvedQuote(QuoteInvitationWasApproved $event)
{
$this->sendEmails($event->quote, 'approved');
$this->pushService->sendNotification($event->quote, 'approved');
dispatch(new SendPushNotification($event->quote, 'approved'));
}
/**
@ -120,10 +94,9 @@ class NotificationListener
return;
}
$this->contactMailer->sendPaymentConfirmation($event->payment);
$this->sendEmails($event->payment->invoice, 'paid', $event->payment);
$this->pushService->sendNotification($event->payment->invoice, 'paid');
dispatch(new SendPaymentEmail($event->payment));
dispatch(new SendPushNotification($event->payment->invoice, 'paid'));
}
}

View File

@ -1,5 +1,9 @@
<?php namespace App\Listeners;
use App\Events\InvoiceWasDeleted;
use App\Events\InvoiceWasUpdated;
use App\Events\QuoteWasUpdated;
use App\Events\QuoteWasDeleted;
use Utils;
use App\Models\EntityModel;
use App\Events\ClientWasCreated;
@ -79,6 +83,42 @@ class SubscriptionListener
public function createdExpense(ExpenseWasCreated $event)
{
}
/**
* @param InvoiceWasUpdated $event
*/
public function updatedInvoice(InvoiceWasUpdated $event)
{
$transformer = new InvoiceTransformer($event->invoice->account);
$this->checkSubscriptions(EVENT_UPDATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
}
/**
* @param InvoiceWasDeleted $event
*/
public function deletedInvoice(InvoiceWasDeleted $event)
{
$transformer = new InvoiceTransformer($event->invoice->account);
$this->checkSubscriptions(EVENT_DELETE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
}
/**
* @param QuoteWasUpdated $event
*/
public function updatedQuote(QuoteWasUpdated $event)
{
$transformer = new InvoiceTransformer($event->quote->account);
$this->checkSubscriptions(EVENT_UPDATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
}
/**
* @param InvoiceWasDeleted $event
*/
public function deletedQuote(QuoteWasDeleted $event)
{
$transformer = new InvoiceTransformer($event->quote->account);
$this->checkSubscriptions(EVENT_DELETE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
}
/**

View File

@ -13,6 +13,8 @@ use Illuminate\Support\Facades\Storage;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laracasts\Presenter\PresentableTrait;
use App\Models\Traits\PresentsInvoice;
use App\Models\Traits\GeneratesNumbers;
use App\Models\Traits\SendsEmails;
/**
* Class Account
@ -22,6 +24,8 @@ class Account extends Eloquent
use PresentableTrait;
use SoftDeletes;
use PresentsInvoice;
use GeneratesNumbers;
use SendsEmails;
/**
* @var string
@ -80,6 +84,12 @@ class Account extends Eloquent
'show_accept_quote_terms',
'require_invoice_signature',
'require_quote_signature',
'pdf_email_attachment',
'document_email_attachment',
'email_design_id',
'enable_email_markup',
'domain_id',
'payment_terms',
];
/**
@ -103,11 +113,11 @@ class Account extends Eloquent
public static $advancedSettings = [
ACCOUNT_INVOICE_SETTINGS,
ACCOUNT_INVOICE_DESIGN,
ACCOUNT_CLIENT_PORTAL,
ACCOUNT_EMAIL_SETTINGS,
ACCOUNT_TEMPLATES_AND_REMINDERS,
ACCOUNT_BANKS,
ACCOUNT_CLIENT_PORTAL,
ACCOUNT_REPORTS,
//ACCOUNT_REPORTS,
ACCOUNT_DATA_VISUALIZATIONS,
ACCOUNT_API_TOKENS,
ACCOUNT_USER_MANAGEMENT,
@ -466,6 +476,17 @@ class Account extends Eloquent
return $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT;
}
public function getSampleLink()
{
$invitation = new Invitation();
$invitation->account = $this;
$invitation->invitation_key = '...';
return $invitation->getLink();
}
/**
* @param $amount
* @param null $client
@ -862,7 +883,7 @@ class Account extends Eloquent
if ($this->hasClientNumberPattern($invoice) && !$clientId) {
// do nothing, we don't yet know the value
} elseif ( ! $invoice->invoice_number) {
$invoice->invoice_number = $this->getNextInvoiceNumber($invoice);
$invoice->invoice_number = $this->getNextNumber($invoice);
}
}
@ -874,191 +895,6 @@ class Account extends Eloquent
return $invoice;
}
/**
* @param $invoice_type_id
* @return string
*/
public function getNumberPrefix($invoice_type_id)
{
if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) {
return '';
}
return ($invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_prefix : $this->invoice_number_prefix) ?: '';
}
/**
* @param $invoice_type_id
* @return bool
*/
public function hasNumberPattern($invoice_type_id)
{
if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) {
return false;
}
return $invoice_type_id == INVOICE_TYPE_QUOTE ? ($this->quote_number_pattern ? true : false) : ($this->invoice_number_pattern ? true : false);
}
/**
* @param $invoice
* @return string
*/
public function hasClientNumberPattern($invoice)
{
$pattern = $invoice->invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_pattern : $this->invoice_number_pattern;
return strstr($pattern, '$custom');
}
/**
* @param $invoice
* @return bool|mixed
*/
public function getNumberPattern($invoice)
{
$pattern = $invoice->invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_pattern : $this->invoice_number_pattern;
if (!$pattern) {
return false;
}
$search = ['{$year}'];
$replace = [date('Y')];
$search[] = '{$counter}';
$replace[] = str_pad($this->getCounter($invoice->invoice_type_id), $this->invoice_number_padding, '0', STR_PAD_LEFT);
if (strstr($pattern, '{$userId}')) {
$search[] = '{$userId}';
$replace[] = str_pad(($invoice->user->public_id + 1), 2, '0', STR_PAD_LEFT);
}
$matches = false;
preg_match('/{\$date:(.*?)}/', $pattern, $matches);
if (count($matches) > 1) {
$format = $matches[1];
$search[] = $matches[0];
$replace[] = str_replace($format, date($format), $matches[1]);
}
$pattern = str_replace($search, $replace, $pattern);
if ($invoice->client_id) {
$pattern = $this->getClientInvoiceNumber($pattern, $invoice);
}
return $pattern;
}
/**
* @param $pattern
* @param $invoice
* @return mixed
*/
private function getClientInvoiceNumber($pattern, $invoice)
{
if (!$invoice->client) {
return $pattern;
}
$search = [
'{$custom1}',
'{$custom2}',
];
$replace = [
$invoice->client->custom_value1,
$invoice->client->custom_value2,
];
return str_replace($search, $replace, $pattern);
}
/**
* @param $invoice_type_id
* @return mixed
*/
public function getCounter($invoice_type_id)
{
return $invoice_type_id == INVOICE_TYPE_QUOTE && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter;
}
/**
* @param $entityType
* @return mixed|string
*/
public function previewNextInvoiceNumber($entityType = ENTITY_INVOICE)
{
$invoice = $this->createInvoice($entityType);
return $this->getNextInvoiceNumber($invoice);
}
/**
* @param $invoice
* @param bool $validateUnique
* @return mixed|string
*/
public function getNextInvoiceNumber($invoice, $validateUnique = true)
{
if ($this->hasNumberPattern($invoice->invoice_type_id)) {
$number = $this->getNumberPattern($invoice);
} else {
$counter = $this->getCounter($invoice->invoice_type_id);
$prefix = $this->getNumberPrefix($invoice->invoice_type_id);
$counterOffset = 0;
$check = false;
// confirm the invoice number isn't already taken
do {
$number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT);
if ($validateUnique) {
$check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first();
$counter++;
$counterOffset++;
}
} while ($check);
// update the invoice counter to be caught up
if ($counterOffset > 1) {
if ($invoice->isType(INVOICE_TYPE_QUOTE)) {
if ( ! $this->share_counter) {
$this->quote_number_counter += $counterOffset - 1;
}
} else {
$this->invoice_number_counter += $counterOffset - 1;
}
$this->save();
}
}
if ($invoice->recurring_invoice_id) {
$number = $this->recurring_invoice_number_prefix . $number;
}
return $number;
}
/**
* @param $invoice
*/
public function incrementCounter($invoice)
{
// if they didn't use the counter don't increment it
if ($invoice->invoice_number != $this->getNextInvoiceNumber($invoice, false)) {
return;
}
if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) {
$this->quote_number_counter += 1;
} else {
$this->invoice_number_counter += 1;
}
$this->save();
}
/**
* @param bool $client
*/
@ -1107,6 +943,10 @@ class Account extends Eloquent
return;
}
if ($this->company->trial_started && $this->company->trial_started != '0000-00-00') {
return;
}
$this->company->trial_plan = $plan;
$this->company->trial_started = date_create()->format('Y-m-d');
$this->company->save();
@ -1166,7 +1006,6 @@ class Account extends Eloquent
return false;
}
// Fallthrough
case FEATURE_CLIENT_PORTAL_CSS:
case FEATURE_REMOVE_CREATED_BY:
return !empty($planDetails);// A plan is required even for self-hosted users
@ -1342,31 +1181,6 @@ class Account extends Eloquent
return $plan_details && $plan_details['trial'];
}
/**
* @param null $plan
* @return array|bool
*/
public function isEligibleForTrial($plan = null)
{
if (!$this->company->trial_plan) {
if ($plan) {
return $plan == PLAN_PRO || $plan == PLAN_ENTERPRISE;
} else {
return [PLAN_PRO, PLAN_ENTERPRISE];
}
}
if ($this->company->trial_plan == PLAN_PRO) {
if ($plan) {
return $plan != PLAN_PRO;
} else {
return [PLAN_ENTERPRISE];
}
}
return false;
}
/**
* @return int
*/
@ -1709,8 +1523,8 @@ class Account extends Eloquent
*/
public function showCustomField($field, $entity = false)
{
if ($this->hasFeature(FEATURE_INVOICE_SETTINGS)) {
return $this->$field ? true : false;
if ($this->hasFeature(FEATURE_INVOICE_SETTINGS) && $this->$field) {
return true;
}
if (!$entity) {
@ -1753,9 +1567,7 @@ class Account extends Eloquent
if ($headerFont != $bodyFont) {
$css .= 'h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{'.$headerFont.'}';
}
}
if ($this->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) {
// For self-hosted users, a white-label license is required for custom CSS
$css .= $this->client_view_css;
}
@ -1882,7 +1694,7 @@ class Account extends Eloquent
return $this->enabled_modules & static::$modules[$entityType];
}
public function showAuthenticatePanel($invoice)
public function requiresAuthorization($invoice)
{
return $this->showAcceptTerms($invoice) || $this->showSignature($invoice);
}
@ -1904,6 +1716,22 @@ class Account extends Eloquent
return $invoice->isQuote() ? $this->require_quote_signature : $this->require_invoice_signature;
}
public function emailMarkupEnabled()
{
if ( ! Utils::isNinja()) {
return false;
}
return $this->enable_email_markup;
}
public function defaultDueDate()
{
$numDays = $this->payment_terms == -1 ? 0 : $this->payment_terms;
return Carbon::now($this->getTimezone())->addDays($numDays)->format('Y-m-d');
}
}
Account::updated(function ($account)

View File

@ -275,6 +275,12 @@ class Client extends EntityModel
} else {
$contact = Contact::createNew();
$contact->send_invoice = true;
if (isset($data['contact_key']) && $this->account->account_key == env('NINJA_LICENSE_ACCOUNT_KEY')) {
$contact->contact_key = $data['contact_key'];
} else {
$contact->contact_key = str_random(RANDOM_KEY_LENGTH);
}
}
if (Utils::hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $this->account->enable_portal_password){
@ -379,6 +385,14 @@ class Client extends EntityModel
return ENTITY_CLIENT;
}
/**
* @return bool
*/
public function showMap()
{
return $this->hasAddress() && env('GOOGLE_MAPS_ENABLED') !== false;
}
/**
* @return bool
*/
@ -521,6 +535,7 @@ class Client extends EntityModel
Client::creating(function ($client) {
$client->setNullValues();
$client->account->incrementCounter($client);
});
Client::updating(function ($client) {

View File

@ -56,13 +56,17 @@ class Company extends Eloquent
// handle promos and discounts
public function hasActiveDiscount(Carbon $date = null)
{
if ( ! $this->discount) {
if ( ! $this->discount || ! $this->discount_expires) {
return false;
}
$date = $date ?: Carbon::today();
return $this->discount_expires && $this->discount_expires->gt($date);
if ($this->plan_term == PLAN_TERM_MONTHLY) {
return $this->discount_expires->gt($date);
} else {
return $this->discount_expires->subMonths(11)->gt($date);
}
}
public function discountedPrice($price)
@ -74,6 +78,29 @@ class Company extends Eloquent
return $price - ($price * $this->discount);
}
public function daysUntilPlanExpires()
{
if ( ! $this->hasActivePlan()) {
return 0;
}
return Carbon::parse($this->plan_expires)->diffInDays(Carbon::today());
}
public function hasActivePlan()
{
return Carbon::parse($this->plan_expires) >= Carbon::today();
}
public function hasExpiredPlan($plan)
{
if ($this->plan != $plan) {
return false;
}
return Carbon::parse($this->plan_expires) < Carbon::today();
}
public function hasEarnedPromo()
{
if ( ! Utils::isNinjaProd() || Utils::isPro()) {

View File

@ -128,7 +128,7 @@ class Contact extends EntityModel implements AuthenticatableContract, CanResetPa
public function getFullName()
{
if ($this->first_name || $this->last_name) {
return $this->first_name.' '.$this->last_name;
return trim($this->first_name.' '.$this->last_name);
} else {
return '';
}

View File

@ -289,6 +289,7 @@ class EntityModel extends Eloquent
'vendors' => 'building',
'settings' => 'cog',
'self-update' => 'download',
'reports' => 'th-list',
];
return array_get($icons, $entityType);
@ -335,4 +336,15 @@ class EntityModel extends Eloquent
return $class::getStatuses($entityType);
}
public function statusClass()
{
return '';
}
public function statusLabel()
{
return '';
}
}

View File

@ -221,6 +221,53 @@ class Expense extends EntityModel
return $statuses;
}
public static function calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance)
{
if ($invoiceId) {
if (floatval($balance) > 0) {
$label = 'invoiced';
} else {
$label = 'paid';
}
} elseif ($shouldBeInvoiced) {
$label = 'pending';
} else {
$label = 'logged';
}
return trans("texts.{$label}");
}
public static function calcStatusClass($shouldBeInvoiced, $invoiceId, $balance)
{
if ($invoiceId) {
if (floatval($balance) > 0) {
return 'default';
} else {
return 'success';
}
} elseif ($shouldBeInvoiced) {
return 'warning';
} else {
return 'primary';
}
}
public function statusClass()
{
$balance = $this->invoice ? $this->invoice->balance : 0;
return static::calcStatusClass($this->should_be_invoiced, $this->invoice_id, $balance);
}
public function statusLabel()
{
$balance = $this->invoice ? $this->invoice->balance : 0;
return static::calcStatusLabel($this->should_be_invoiced, $this->invoice_id, $balance);
}
}
Expense::creating(function ($expense) {

View File

@ -68,14 +68,19 @@ class Invitation extends EntityModel
$this->load('account');
}
$account = $this->account;
$iframe_url = $account->iframe_url;
$url = trim(SITE_URL, '/');
$iframe_url = $this->account->iframe_url;
if ($this->account->hasFeature(FEATURE_CUSTOM_URL)) {
if ($account->hasFeature(FEATURE_CUSTOM_URL)) {
if (Utils::isNinjaProd()) {
$url = $account->present()->clientPortalLink();
}
if ($iframe_url && !$forceOnsite) {
return "{$iframe_url}?{$this->invitation_key}";
} elseif ($this->account->subdomain) {
$url = Utils::replaceSubdomain($url, $this->account->subdomain);
$url = Utils::replaceSubdomain($url, $account->subdomain);
}
}

View File

@ -10,6 +10,7 @@ use App\Events\InvoiceWasCreated;
use App\Events\InvoiceWasUpdated;
use App\Events\InvoiceInvitationWasEmailed;
use App\Events\QuoteInvitationWasEmailed;
use App\Libraries\CurlUtils;
/**
* Class Invoice
@ -64,10 +65,22 @@ class Invoice extends EntityModel implements BalanceAffecting
'date:',
];
public static $statusClasses = [
INVOICE_STATUS_SENT => 'info',
INVOICE_STATUS_VIEWED => 'warning',
INVOICE_STATUS_APPROVED => 'success',
INVOICE_STATUS_PARTIAL => 'primary',
INVOICE_STATUS_PAID => 'success',
];
/**
* @var string
*/
public static $fieldInvoiceNumber = 'invoice_number';
/**
* @var string
*/
public static $fieldPONumber = 'po_number';
/**
* @var string
*/
@ -101,6 +114,7 @@ class Invoice extends EntityModel implements BalanceAffecting
return [
Client::$fieldName,
Invoice::$fieldInvoiceNumber,
Invoice::$fieldPONumber,
Invoice::$fieldInvoiceDate,
Invoice::$fieldDueDate,
Invoice::$fieldAmount,
@ -118,9 +132,11 @@ class Invoice extends EntityModel implements BalanceAffecting
return [
'number^po' => 'invoice_number',
'amount' => 'amount',
'organization' => 'name',
'client|organization' => 'name',
'paid^date' => 'paid',
'invoice_date|create_date' => 'invoice_date',
'invoice date|create date' => 'invoice_date',
'po number' => 'po_number',
'due date' => 'due_date',
'terms' => 'terms',
'notes' => 'notes',
];
@ -181,31 +197,38 @@ class Invoice extends EntityModel implements BalanceAffecting
return floatval($this->amount) - floatval($this->getOriginal('amount'));
}
/**
* @return bool
*/
public function isChanged()
{
if ($this->getRawAdjustment() != 0) {
return true;
}
foreach ([
'invoice_number',
'po_number',
'invoice_date',
'due_date',
'terms',
'public_notes',
'invoice_footer',
'partial',
] as $field) {
if ($this->$field != $this->getOriginal($field)) {
if (Utils::isNinja()) {
if ($this->getRawAdjustment() != 0) {
return true;
}
}
return false;
foreach ([
'invoice_number',
'po_number',
'invoice_date',
'due_date',
'terms',
'public_notes',
'invoice_footer',
'partial',
] as $field) {
if ($this->$field != $this->getOriginal($field)) {
return true;
}
}
return false;
} else {
$dirty = $this->getDirty();
unset($dirty['invoice_status_id']);
unset($dirty['client_enable_auto_bill']);
unset($dirty['quote_invoice_id']);
return count($dirty) > 0;
}
}
/**
@ -410,17 +433,36 @@ class Invoice extends EntityModel implements BalanceAffecting
return $this->isType(INVOICE_TYPE_STANDARD) && ! $this->is_recurring;
}
public function markSentIfUnsent()
{
if ( ! $this->isSent()) {
$this->markSent();
}
}
public function markSent()
{
if ( ! $this->isSent()) {
$this->invoice_status_id = INVOICE_STATUS_SENT;
}
$this->is_public = true;
$this->save();
$this->markInvitationsSent();
}
/**
* @param bool $notify
*/
public function markInvitationsSent($notify = false)
public function markInvitationsSent($notify = false, $reminder = false)
{
if ( ! $this->relationLoaded('invitations')) {
$this->load('invitations');
}
foreach ($this->invitations as $invitation) {
$this->markInvitationSent($invitation, false, $notify);
$this->markInvitationSent($invitation, false, $notify, $reminder);
}
}
@ -444,9 +486,9 @@ class Invoice extends EntityModel implements BalanceAffecting
* @param bool $messageId
* @param bool $notify
*/
public function markInvitationSent($invitation, $messageId = false, $notify = true)
public function markInvitationSent($invitation, $messageId = false, $notify = true, $notes = false)
{
if (!$this->isSent()) {
if ( ! $this->isSent()) {
$this->invoice_status_id = INVOICE_STATUS_SENT;
$this->save();
}
@ -460,9 +502,9 @@ class Invoice extends EntityModel implements BalanceAffecting
}
if ($this->isType(INVOICE_TYPE_QUOTE)) {
event(new QuoteInvitationWasEmailed($invitation));
event(new QuoteInvitationWasEmailed($invitation, $notes));
} else {
event(new InvoiceInvitationWasEmailed($invitation));
event(new InvoiceInvitationWasEmailed($invitation, $notes));
}
}
@ -550,7 +592,58 @@ class Invoice extends EntityModel implements BalanceAffecting
public function canBePaid()
{
return floatval($this->balance) > 0 && ! $this->is_deleted && $this->isInvoice() && $this->is_public;
return floatval($this->balance) > 0 && ! $this->is_deleted && $this->isInvoice();
}
public static function calcStatusLabel($status, $class, $entityType, $quoteInvoiceId)
{
if ($quoteInvoiceId) {
$label = 'converted';
} else if ($class == 'danger') {
$label = $entityType == ENTITY_INVOICE ? 'overdue' : 'expired';
} else {
$label = 'status_' . strtolower($status);
}
return trans("texts.{$label}");
}
public static function calcStatusClass($statusId, $balance, $dueDate)
{
if (static::calcIsOverdue($balance, $dueDate)) {
return 'danger';
}
if (isset(static::$statusClasses[$statusId])) {
return static::$statusClasses[$statusId];
}
return 'default';
}
public static function calcIsOverdue($balance, $dueDate)
{
if ( ! Utils::parseFloat($balance) > 0) {
return false;
}
if ( ! $dueDate || $dueDate == '0000-00-00') {
return false;
}
// it isn't considered overdue until the end of the day
return time() > (strtotime($dueDate) + (60*60*24));
}
public function statusClass()
{
return static::calcStatusClass($this->invoice_status_id, $this->balance, $this->due_date);
}
public function statusLabel()
{
return static::calcStatusLabel($this->invoice_status->name, $this->statusClass(), $this->getEntityType(), $this->quote_invoice_id);
>>>>>>> release-3.0.0
}
/**
@ -601,7 +694,7 @@ class Invoice extends EntityModel implements BalanceAffecting
*/
public function isSent()
{
return $this->invoice_status_id >= INVOICE_STATUS_SENT && $this->is_public;
return $this->invoice_status_id >= INVOICE_STATUS_SENT && $this->getOriginal('is_public');
}
/**
@ -633,11 +726,7 @@ class Invoice extends EntityModel implements BalanceAffecting
*/
public function isOverdue()
{
if ( ! $this->due_date) {
return false;
}
return time() > strtotime($this->due_date);
return static::calcIsOverdue($this->balance, $this->due_date);
}
/**
@ -1099,21 +1188,23 @@ class Invoice extends EntityModel implements BalanceAffecting
*/
public function getPDFString()
{
if (!env('PHANTOMJS_CLOUD_KEY')) {
if ( ! env('PHANTOMJS_CLOUD_KEY') && ! env('PHANTOMJS_BIN_PATH')) {
return false;
}
$invitation = $this->invitations[0];
$link = $invitation->getLink('view', true);
$key = env('PHANTOMJS_CLOUD_KEY');
if (Utils::isNinjaDev()) {
$link = env('TEST_LINK');
if (env('PHANTOMJS_BIN_PATH')) {
$pdfString = CurlUtils::phantom('GET', $link . '?phantomjs=true');
} elseif ($key = env('PHANTOMJS_CLOUD_KEY')) {
if (Utils::isNinjaDev()) {
$link = env('TEST_LINK');
}
$url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%22,renderType:%22html%22%7D";
$pdfString = CurlUtils::get($url);
}
$url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%22,renderType:%22html%22%7D";
$pdfString = file_get_contents($url);
$pdfString = strip_tags($pdfString);
if ( ! $pdfString || strlen($pdfString) < 200) {

View File

@ -17,6 +17,15 @@ class Payment extends EntityModel
use PresentableTrait;
use SoftDeletes;
public static $statusClasses = [
PAYMENT_STATUS_PENDING => 'info',
PAYMENT_STATUS_COMPLETED => 'success',
PAYMENT_STATUS_FAILED => 'danger',
PAYMENT_STATUS_PARTIALLY_REFUNDED => 'primary',
PAYMENT_STATUS_VOIDED => 'default',
PAYMENT_STATUS_REFUNDED => 'default',
];
/**
* @var array
*/
@ -302,6 +311,44 @@ class Payment extends EntityModel
{
return $value ? str_pad($value, 4, '0', STR_PAD_LEFT) : null;
}
public static function calcStatusLabel($statusId, $statusName, $amount)
{
if ($statusId == PAYMENT_STATUS_PARTIALLY_REFUNDED) {
return trans('texts.status_partially_refunded_amount', [
'amount' => $amount,
]);
} else {
return trans('texts.status_' . strtolower($statusName));
}
}
public static function calcStatusClass($statusId)
{
return static::$statusClasses[$statusId];
}
public function statusClass()
{
return static::calcStatusClass($this->payment_status_id);
}
public function statusLabel()
{
$amount = $this->account->formatMoney($this->refunded, $this->client);
return static::calcStatusLabel($this->payment_status_id, $this->payment_status->name, $amount);
}
public function invoiceJsonBackup()
{
$activity = Activity::wherePaymentId($this->id)
->whereActivityTypeId(ACTIVITY_TYPE_CREATE_PAYMENT)
->get(['json_backup'])
->first();
return $activity->json_backup;
}
}
Payment::creating(function ($payment) {

View File

@ -215,8 +215,64 @@ class Task extends EntityModel
return $statuses;
}
}
public static function calcStatusLabel($isRunning, $balance, $invoiceNumber)
{
if ($invoiceNumber) {
if (floatval($balance) > 0) {
$label = 'invoiced';
} else {
$label = 'paid';
}
} elseif ($isRunning) {
$label = 'running';
} else {
$label = 'logged';
}
return trans("texts.{$label}");
}
public static function calcStatusClass($isRunning, $balance, $invoiceNumber)
{
if ($invoiceNumber) {
if (floatval($balance)) {
return 'default';
} else {
return 'success';
}
} elseif ($isRunning) {
return 'primary';
} else {
return 'warning';
}
}
public function statusClass()
{
if ($this->invoice) {
$balance = $this->invoice->balance;
$invoiceNumber = $this->invoice->invoice_number;
} else {
$balance = 0;
$invoiceNumber = false;
}
return static::calcStatusClass($this->is_running, $balance, $invoiceNumber);
}
public function statusLabel()
{
if ($this->invoice) {
$balance = $this->invoice->balance;
$invoiceNumber = $this->invoice->invoice_number;
} else {
$balance = 0;
$invoiceNumber = false;
}
return static::calcStatusLabel($this->is_running, $balance, $invoiceNumber);
}
}
Task::created(function ($task) {

View File

@ -18,7 +18,8 @@ class TaxRate extends EntityModel
*/
protected $fillable = [
'name',
'rate'
'rate',
'is_inclusive',
];
/**

View File

@ -0,0 +1,236 @@
<?php namespace App\Models\Traits;
use Auth;
use Carbon;
use App\Models\Invoice;
use App\Models\Client;
/**
* Class GeneratesNumbers
*/
trait GeneratesNumbers
{
/**
* @param $entity
* @return mixed|string
*/
public function getNextNumber($entity = false)
{
$entity = $entity ?: new Client();
$entityType = $entity->getEntityType();
$counter = $this->getCounter($entityType);
$prefix = $this->getNumberPrefix($entityType);
$counterOffset = 0;
$check = false;
if ($entityType == ENTITY_CLIENT && ! $this->clientNumbersEnabled()) {
return '';
}
// confirm the invoice number isn't already taken
do {
if ($this->hasNumberPattern($entityType)) {
$number = $this->applyNumberPattern($entity, $counter);
} else {
$number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT);
}
if ($entity->isEntityType(ENTITY_CLIENT)) {
$check = Client::scope(false, $this->id)->whereIdNumber($number)->withTrashed()->first();
} else {
$check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first();
}
$counter++;
$counterOffset++;
} while ($check);
// update the counter to be caught up
if ($counterOffset > 1) {
if ($entity->isEntityType(ENTITY_CLIENT)) {
if ($this->clientNumbersEnabled()) {
$this->client_number_counter += $counterOffset - 1;
$this->save();
}
} elseif ($entity->isType(INVOICE_TYPE_QUOTE)) {
if ( ! $this->share_counter) {
$this->quote_number_counter += $counterOffset - 1;
$this->save();
}
} else {
$this->invoice_number_counter += $counterOffset - 1;
$this->save();
}
}
if ($entity->recurring_invoice_id) {
$number = $this->recurring_invoice_number_prefix . $number;
}
return $number;
}
/**
* @param $entityType
* @return string
*/
public function getNumberPrefix($entityType)
{
if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) {
return '';
}
$field = "{$entityType}_number_prefix";
return $this->$field ?: '';
}
/**
* @param $entityType
* @return bool
*/
public function getNumberPattern($entityType)
{
if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) {
return false;
}
$field = "{$entityType}_number_pattern";
return $this->$field;
}
/**
* @param $entityType
* @return bool
*/
public function hasNumberPattern($entityType)
{
return $this->getNumberPattern($entityType) ? true : false;
}
/**
* @param $entityType
* @return string
*/
public function hasClientNumberPattern($invoice)
{
$pattern = $invoice->invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_pattern : $this->invoice_number_pattern;
return strstr($pattern, '$custom');
}
/**
* @param $entity
* @return bool|mixed
*/
public function applyNumberPattern($entity, $counter = 0)
{
$entityType = $entity->getEntityType();
$counter = $counter ?: $this->getCounter($entityType);
$pattern = $this->getNumberPattern($entityType);
if (!$pattern) {
return false;
}
$search = ['{$year}'];
$replace = [date('Y')];
$search[] = '{$counter}';
$replace[] = str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT);
if (strstr($pattern, '{$userId}')) {
$userId = $entity->user ? $entity->user->public_id : (Auth::check() ? Auth::user()->public_id : 0);
$search[] = '{$userId}';
$replace[] = str_pad(($userId + 1), 2, '0', STR_PAD_LEFT);
}
$matches = false;
preg_match('/{\$date:(.*?)}/', $pattern, $matches);
if (count($matches) > 1) {
$format = $matches[1];
$search[] = $matches[0];
$date = Carbon::now(session(SESSION_TIMEZONE, DEFAULT_TIMEZONE))->format($format);
$replace[] = str_replace($format, $date, $matches[1]);
}
$pattern = str_replace($search, $replace, $pattern);
if ($entity->client_id) {
$pattern = $this->getClientInvoiceNumber($pattern, $entity);
}
return $pattern;
}
/**
* @param $pattern
* @param $invoice
* @return mixed
*/
private function getClientInvoiceNumber($pattern, $invoice)
{
if (!$invoice->client) {
return $pattern;
}
$search = [
'{$custom1}',
'{$custom2}',
];
$replace = [
$invoice->client->custom_value1,
$invoice->client->custom_value2,
];
return str_replace($search, $replace, $pattern);
}
/**
* @param $entityType
* @return mixed
*/
public function getCounter($entityType)
{
if ($entityType == ENTITY_CLIENT) {
return $this->client_number_counter;
} elseif ($entityType == ENTITY_QUOTE && ! $this->share_counter) {
return $this->quote_number_counter;
} else {
return $this->invoice_number_counter;
}
}
/**
* @param $entityType
* @return mixed|string
*/
public function previewNextInvoiceNumber($entityType = ENTITY_INVOICE)
{
$invoice = $this->createInvoice($entityType);
return $this->getNextNumber($invoice);
}
/**
* @param $entity
*/
public function incrementCounter($entity)
{
if ($entity->isEntityType(ENTITY_CLIENT)) {
if ($this->client_number_counter) {
$this->client_number_counter += 1;
}
} elseif ($entity->isType(INVOICE_TYPE_QUOTE) && ! $this->share_counter) {
$this->quote_number_counter += 1;
} else {
$this->invoice_number_counter += 1;
}
$this->save();
}
public function clientNumbersEnabled()
{
return $this->hasFeature(FEATURE_INVOICE_SETTINGS) && $this->client_number_counter;
}
}

View File

@ -96,6 +96,7 @@ trait PresentsInvoice
'client.address1',
'client.address2',
'client.city_state_postal',
'client.postal_city_state',
'client.country',
'client.email',
'client.phone',
@ -114,6 +115,7 @@ trait PresentsInvoice
'account.address1',
'account.address2',
'account.city_state_postal',
'account.postal_city_state',
'account.country',
'account.custom_value1',
'account.custom_value2',
@ -196,6 +198,7 @@ trait PresentsInvoice
'id_number',
'vat_number',
'city_state_postal',
'postal_city_state',
'country',
'email',
'contact_name',
@ -203,6 +206,11 @@ trait PresentsInvoice
'website',
'phone',
'blank',
'adjustment',
'tax_invoice',
'tax_quote',
'statement',
'statement_date',
];
foreach ($fields as $field) {

View File

@ -0,0 +1,24 @@
<?php namespace App\Models\Traits;
use Utils;
use App\Constants\Domain;
/**
* Class SendsEmails
*/
trait SendsEmails
{
public function getBccEmail()
{
return $this->isPro() ? $this->bcc_email : false;
}
public function getFromEmail()
{
if ( ! $this->isPro() || ! Utils::isNinjaProd() || Utils::isReseller()) {
return false;
}
return Domain::getEmailFromId($this->domain_id);
}
}

View File

@ -7,12 +7,20 @@ use App\Events\UserSettingsChanged;
use App\Events\UserSignedUp;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laracasts\Presenter\PresentableTrait;
/**
* Class User
*/
class User extends Authenticatable
{
use PresentableTrait;
/**
* @var string
*/
protected $presenter = 'App\Ninja\Presenters\UserPresenter';
/**
* @var array
*/
@ -130,15 +138,6 @@ class User extends Authenticatable
return $this->account->isTrial();
}
/**
* @param null $plan
* @return mixed
*/
public function isEligibleForTrial($plan = null)
{
return $this->account->isEligibleForTrial($plan);
}
/**
* @return int
*/

View File

@ -267,6 +267,14 @@ class Vendor extends EntityModel
return 'vendor';
}
/**
* @return bool
*/
public function showMap()
{
return $this->hasAddress() && env('GOOGLE_MAPS_ENABLED') !== false;
}
/**
* @return bool
*/
@ -321,12 +329,14 @@ class Vendor extends EntityModel
/**
* @return float|int
*/
public function getTotalExpense()
public function getTotalExpenses()
{
return DB::table('expenses')
->where('vendor_id', '=', $this->id)
->whereNull('deleted_at')
->sum('amount');
->select('expense_currency_id', DB::raw('SUM(amount) as amount'))
->whereVendorId($this->id)
->whereIsDeleted(false)
->groupBy('expense_currency_id')
->get();
}
}

View File

@ -32,7 +32,13 @@ class ActivityDatatable extends EntityDatatable
'expense' => $model->expense_public_id ? link_to('/expenses/' . $model->expense_public_id, substr($model->expense_public_notes, 0, 30).'...') : null,
];
return trans("texts.activity_{$model->activity_type_id}", $data);
$str = trans("texts.activity_{$model->activity_type_id}", $data);
if ($model->notes) {
$str .= ' - ' . trans("texts.notes_{$model->notes}");
}
return $str;
}
],
[

View File

@ -64,4 +64,30 @@ class EntityDatatable
return $data;
}
public function rightAlignIndices()
{
return $this->alignIndices(['amount', 'balance', 'cost']);
}
public function centerAlignIndices()
{
return $this->alignIndices(['status']);
}
public function alignIndices($fields)
{
$columns = $this->columnFields();
$indices = [];
foreach ($columns as $index => $column) {
if (in_array($column, $fields)) {
$indices[] = $index + 1;
}
}
return $indices;
}
}

View File

@ -3,6 +3,7 @@
use Utils;
use URL;
use Auth;
use App\Models\Expense;
class ExpenseDatatable extends EntityDatatable
{
@ -123,24 +124,10 @@ class ExpenseDatatable extends EntityDatatable
];
}
private function getStatusLabel($invoiceId, $shouldBeInvoiced, $balance)
{
if ($invoiceId) {
if (floatval($balance)) {
$label = trans('texts.invoiced');
$class = 'default';
} else {
$label = trans('texts.paid');
$class = 'success';
}
} elseif ($shouldBeInvoiced) {
$label = trans('texts.pending');
$class = 'warning';
} else {
$label = trans('texts.logged');
$class = 'primary';
}
$label = Expense::calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance);
$class = Expense::calcStatusClass($shouldBeInvoiced, $invoiceId, $balance);
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
}

View File

@ -3,6 +3,7 @@
use Utils;
use URL;
use Auth;
use App\Models\Invoice;
class InvoiceDatatable extends EntityDatatable
{
@ -167,35 +168,8 @@ class InvoiceDatatable extends EntityDatatable
private function getStatusLabel($model)
{
$entityType = $this->entityType;
// check if invoice is overdue
if (Utils::parseFloat($model->balance) && $model->due_date && $model->due_date != '0000-00-00') {
if (\DateTime::createFromFormat('Y-m-d', $model->due_date) < new \DateTime('now')) {
$label = $entityType == ENTITY_INVOICE ? trans('texts.overdue') : trans('texts.expired');
return '<h4><div class="label label-danger">' . $label . '</div></h4>';
}
}
$label = trans('texts.status_' . strtolower($model->invoice_status_name));
$class = 'default';
switch ($model->invoice_status_id) {
case INVOICE_STATUS_SENT:
$class = 'info';
break;
case INVOICE_STATUS_VIEWED:
$class = 'warning';
break;
case INVOICE_STATUS_APPROVED:
$class = 'success';
break;
case INVOICE_STATUS_PARTIAL:
$class = 'primary';
break;
case INVOICE_STATUS_PAID:
$class = 'success';
break;
}
$class = Invoice::calcStatusClass($model->invoice_status_id, $model->balance, $model->due_date);
$label = Invoice::calcStatusLabel($model->invoice_status_name, $class, $this->entityType, $model->quote_invoice_id);
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
}
@ -206,6 +180,10 @@ class InvoiceDatatable extends EntityDatatable
if ($this->entityType == ENTITY_INVOICE || $this->entityType == ENTITY_QUOTE) {
$actions[] = \DropdownButton::DIVIDER;
$actions[] = [
'label' => mtrans($this->entityType, 'email_' . $this->entityType),
'url' => 'javascript:submitForm_'.$this->entityType.'("emailInvoice")',
];
$actions[] = [
'label' => mtrans($this->entityType, 'mark_sent'),
'url' => 'javascript:submitForm_'.$this->entityType.'("markSent")',

View File

@ -3,6 +3,7 @@
use Utils;
use URL;
use Auth;
use App\Models\Payment;
use App\Models\PaymentMethod;
class PaymentDatatable extends EntityDatatable
@ -98,7 +99,7 @@ class PaymentDatatable extends EntityDatatable
}
],
[
'payment_status_name',
'status',
function ($model) {
return self::getStatusLabel($model);
}
@ -130,9 +131,7 @@ class PaymentDatatable extends EntityDatatable
function ($model) {
return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id])
&& $model->payment_status_id >= PAYMENT_STATUS_COMPLETED
&& $model->refunded < $model->amount
&& $model->transaction_reference
&& in_array($model->gateway_id , static::$refundableGateways);
&& $model->refunded < $model->amount;
}
]
];
@ -140,29 +139,10 @@ class PaymentDatatable extends EntityDatatable
private function getStatusLabel($model)
{
$label = trans('texts.status_' . strtolower($model->payment_status_name));
$class = 'default';
switch ($model->payment_status_id) {
case PAYMENT_STATUS_PENDING:
$class = 'info';
break;
case PAYMENT_STATUS_COMPLETED:
$class = 'success';
break;
case PAYMENT_STATUS_FAILED:
$class = 'danger';
break;
case PAYMENT_STATUS_PARTIALLY_REFUNDED:
$label = trans('texts.status_partially_refunded_amount', [
'amount' => Utils::formatMoney($model->refunded, $model->currency_id, $model->country_id),
]);
$class = 'primary';
break;
case PAYMENT_STATUS_VOIDED:
case PAYMENT_STATUS_REFUNDED:
$class = 'default';
break;
}
$amount = Utils::formatMoney($model->refunded, $model->currency_id, $model->country_id);
$label = Payment::calcStatusLabel($model->payment_status_id, $model->status, $amount);
$class = Payment::calcStatusClass($model->payment_status_id);
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
}
}

View File

@ -32,6 +32,12 @@ class RecurringInvoiceDatatable extends EntityDatatable
return Utils::fromSqlDate($model->start_date);
}
],
[
'last_sent',
function ($model) {
return Utils::fromSqlDate($model->last_sent_date);
}
],
[
'end_date',
function ($model) {

View File

@ -108,21 +108,8 @@ class TaskDatatable extends EntityDatatable
private function getStatusLabel($model)
{
if ($model->invoice_number) {
if (floatval($model->balance)) {
$label = trans('texts.invoiced');
$class = 'default';
} else {
$class = 'success';
$label = trans('texts.paid');
}
} elseif ($model->is_running) {
$class = 'primary';
$label = trans('texts.running');
} else {
$class = 'warning';
$label = trans('texts.logged');
}
$label = Task::calcStatusLabel($model->is_running, $model->balance, $model->invoice_number);
$class = Task::calcStatusClass($model->is_running, $model->balance, $model->invoice_number);
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
}

View File

@ -20,6 +20,12 @@ class TaxRateDatatable extends EntityDatatable
function ($model) {
return $model->rate . '%';
}
],
[
'type',
function ($model) {
return $model->is_inclusive ? trans('texts.inclusive') : trans('texts.exclusive');
}
]
];
}

View File

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Import;
use Carbon;
use Utils;
use DateTime;
use League\Fractal\TransformerAbstract;
@ -27,7 +28,7 @@ class BaseTransformer extends TransformerAbstract
* @param $name
* @return bool
*/
protected function hasClient($name)
public function hasClient($name)
{
$name = trim(strtolower($name));
return isset($this->maps[ENTITY_CLIENT][$name]);
@ -37,7 +38,7 @@ class BaseTransformer extends TransformerAbstract
* @param $key
* @return bool
*/
protected function hasProduct($key)
public function hasProduct($key)
{
$key = trim(strtolower($key));
return isset($this->maps[ENTITY_PRODUCT][$key]);
@ -48,7 +49,7 @@ class BaseTransformer extends TransformerAbstract
* @param $field
* @return string
*/
protected function getString($data, $field)
public function getString($data, $field)
{
return (isset($data->$field) && $data->$field) ? $data->$field : '';
}
@ -58,18 +59,28 @@ class BaseTransformer extends TransformerAbstract
* @param $field
* @return int
*/
protected function getNumber($data, $field)
public function getNumber($data, $field)
{
return (isset($data->$field) && $data->$field) ? $data->$field : 0;
}
/**
* @param $data
* @param $field
* @return float
*/
public function getFloat($data, $field)
{
return (isset($data->$field) && $data->$field) ? Utils::parseFloat($data->$field) : 0;
}
/**
* @param $name
* @return null
*/
protected function getClientId($name)
public function getClientId($name)
{
$name = strtolower($name);
$name = strtolower(trim($name));
return isset($this->maps[ENTITY_CLIENT][$name]) ? $this->maps[ENTITY_CLIENT][$name] : null;
}
@ -77,9 +88,9 @@ class BaseTransformer extends TransformerAbstract
* @param $name
* @return null
*/
protected function getProductId($name)
public function getProductId($name)
{
$name = strtolower($name);
$name = strtolower(trim($name));
return isset($this->maps[ENTITY_PRODUCT][$name]) ? $this->maps[ENTITY_PRODUCT][$name] : null;
}
@ -87,9 +98,9 @@ class BaseTransformer extends TransformerAbstract
* @param $name
* @return null
*/
protected function getCountryId($name)
public function getCountryId($name)
{
$name = strtolower($name);
$name = strtolower(trim($name));
return isset($this->maps['countries'][$name]) ? $this->maps['countries'][$name] : null;
}
@ -97,9 +108,9 @@ class BaseTransformer extends TransformerAbstract
* @param $name
* @return null
*/
protected function getCountryIdBy2($name)
public function getCountryIdBy2($name)
{
$name = strtolower($name);
$name = strtolower(trim($name));
return isset($this->maps['countries2'][$name]) ? $this->maps['countries2'][$name] : null;
}
@ -107,7 +118,7 @@ class BaseTransformer extends TransformerAbstract
* @param $name
* @return mixed
*/
protected function getFirstName($name)
public function getFirstName($name)
{
$name = Utils::splitName($name);
return $name[0];
@ -118,10 +129,14 @@ class BaseTransformer extends TransformerAbstract
* @param string $format
* @return null
*/
protected function getDate($date, $format = 'Y-m-d')
public function getDate($data, $field)
{
if ( ! $date instanceof DateTime) {
$date = DateTime::createFromFormat($format, $date);
if ($date = data_get($data, $field)) {
try {
$date = new Carbon($date);
} catch (Exception $e) {
// if we fail to parse return blank
}
}
return $date ? $date->format('Y-m-d') : null;
@ -131,7 +146,7 @@ class BaseTransformer extends TransformerAbstract
* @param $name
* @return mixed
*/
protected function getLastName($name)
public function getLastName($name)
{
$name = Utils::splitName($name);
return $name[1];
@ -141,7 +156,7 @@ class BaseTransformer extends TransformerAbstract
* @param $number
* @return string
*/
protected function getInvoiceNumber($number)
public function getInvoiceNumber($number)
{
return str_pad(trim($number), 4, '0', STR_PAD_LEFT);
}
@ -150,7 +165,7 @@ class BaseTransformer extends TransformerAbstract
* @param $invoiceNumber
* @return null
*/
protected function getInvoiceId($invoiceNumber)
public function getInvoiceId($invoiceNumber)
{
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
@ -161,7 +176,7 @@ class BaseTransformer extends TransformerAbstract
* @param $invoiceNumber
* @return bool
*/
protected function hasInvoice($invoiceNumber)
public function hasInvoice($invoiceNumber)
{
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
@ -172,7 +187,7 @@ class BaseTransformer extends TransformerAbstract
* @param $invoiceNumber
* @return null
*/
protected function getInvoiceClientId($invoiceNumber)
public function getInvoiceClientId($invoiceNumber)
{
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
@ -186,7 +201,7 @@ class BaseTransformer extends TransformerAbstract
*/
public function getVendorId($name)
{
$name = strtolower($name);
$name = strtolower(trim($name));
return isset($this->maps[ENTITY_VENDOR][$name]) ? $this->maps[ENTITY_VENDOR][$name] : null;
}
@ -197,7 +212,7 @@ class BaseTransformer extends TransformerAbstract
*/
public function getExpenseCategoryId($name)
{
$name = strtolower($name);
$name = strtolower(trim($name));
return isset($this->maps[ENTITY_EXPENSE_CATEGORY][$name]) ? $this->maps[ENTITY_EXPENSE_CATEGORY][$name] : null;
}

View File

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Import\CSV;
use Utils;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
@ -16,7 +17,7 @@ class ExpenseTransformer extends BaseTransformer
{
return new Item($data, function ($data) {
return [
'amount' => isset($data->amount) ? (float) $data->amount : null,
'amount' => $this->getFloat($data, 'amount'),
'vendor_id' => isset($data->vendor) ? $this->getVendorId($data->vendor) : null,
'client_id' => isset($data->client) ? $this->getClientId($data->client) : null,
'expense_date' => isset($data->expense_date) ? date('Y-m-d', strtotime($data->expense_date)) : null,

View File

@ -26,20 +26,21 @@ class InvoiceTransformer extends BaseTransformer
return [
'client_id' => $this->getClientId($data->name),
'invoice_number' => isset($data->invoice_number) ? $this->getInvoiceNumber($data->invoice_number) : null,
'paid' => isset($data->paid) ? (float) $data->paid : null,
'paid' => $this->getFloat($data, 'paid'),
'po_number' => $this->getString($data, 'po_number'),
'terms' => $this->getString($data, 'terms'),
'public_notes' => $this->getString($data, 'public_notes'),
'invoice_date_sql' => isset($data->invoice_date) ? $data->invoice_date : null,
'invoice_date_sql' => $this->getDate($data, 'invoice_date'),
'due_date_sql' => $this->getDate($data, 'due_date'),
'invoice_items' => [
[
'product_key' => '',
'notes' => $this->getString($data, 'notes'),
'cost' => isset($data->amount) ? (float) $data->amount : null,
'cost' => $this->getFloat($data, 'amount'),
'qty' => 1,
]
],
];
});
}
}
}

View File

@ -16,11 +16,11 @@ class PaymentTransformer extends BaseTransformer
{
return new Item($data, function ($data) {
return [
'amount' => $data->paid,
'amount' => $this->getFloat($data, 'paid'),
'payment_date_sql' => isset($data->invoice_date) ? $data->invoice_date : null,
'client_id' => $data->client_id,
'invoice_id' => $data->invoice_id,
];
});
}
}
}

View File

@ -22,7 +22,7 @@ class ProductTransformer extends BaseTransformer
return [
'product_key' => $this->getString($data, 'product_key'),
'notes' => $this->getString($data, 'notes'),
'cost' => $this->getNumber($data, 'cost'),
'cost' => $this->getFloat($data, 'cost'),
];
});
}

View File

@ -28,7 +28,7 @@ class InvoiceTransformer extends BaseTransformer
'invoice_number' => $this->getInvoiceNumber($data->id),
'paid' => (float) $data->paid_amount,
'po_number' => $this->getString($data, 'po_number'),
'invoice_date_sql' => $this->getDate($data->issue_date, 'm/d/Y'),
'invoice_date_sql' => $this->getDate($data, 'issue_date'),
'invoice_items' => [
[
'product_key' => '',
@ -40,4 +40,4 @@ class InvoiceTransformer extends BaseTransformer
];
});
}
}
}

View File

@ -17,10 +17,10 @@ class PaymentTransformer extends BaseTransformer
return new Item($data, function ($data) {
return [
'amount' => $data->paid_amount,
'payment_date_sql' => $this->getDate($data->last_payment_date, 'm/d/Y'),
'payment_date_sql' => $this->getDate($data, 'last_payment_date'),
'client_id' => $data->client_id,
'invoice_id' => $data->invoice_id,
];
});
}
}
}

View File

@ -27,8 +27,8 @@ class InvoiceTransformer extends BaseTransformer
'client_id' => $this->getClientId($data->client),
'invoice_number' => $this->getInvoiceNumber($data->statement_no),
'paid' => (float) $data->paid_total,
'invoice_date_sql' => $this->getDate($data->date),
'due_date_sql' => $this->getDate($data->due_date),
'invoice_date_sql' => $this->getDate($data, 'date'),
'due_date_sql' => $this->getDate($data, 'due_date'),
'invoice_items' => [
[
'product_key' => '',
@ -40,4 +40,4 @@ class InvoiceTransformer extends BaseTransformer
];
});
}
}
}

View File

@ -17,7 +17,7 @@ class PaymentTransformer extends BaseTransformer
return new Item($data, function ($data) {
return [
'amount' => $data->paid_total,
'payment_date_sql' => $this->getDate($data->last_paid_on),
'payment_date_sql' => $this->getDate($data, 'last_paid_on'),
'client_id' => $data->client_id,
'invoice_id' => $data->invoice_id,
];

View File

@ -30,8 +30,8 @@ class InvoiceTransformer extends BaseTransformer
'po_number' => $this->getString($data, 'purchase_order'),
'terms' => $this->getString($data, 'terms'),
'public_notes' => $this->getString($data, 'notes'),
'invoice_date_sql' => $this->getDate($data->date),
'due_date_sql' => $this->getDate($data->due_date),
'invoice_date_sql' => $this->getDate($data, 'date'),
'due_date_sql' => $this->getDate($data, 'due_date'),
'invoice_items' => [
[
'product_key' => '',
@ -43,4 +43,4 @@ class InvoiceTransformer extends BaseTransformer
];
});
}
}
}

View File

@ -17,7 +17,7 @@ class PaymentTransformer extends BaseTransformer
return new Item($data, function ($data) {
return [
'amount' => (float) $data->paid_to_date,
'payment_date_sql' => $this->getDate($data->date),
'payment_date_sql' => $this->getDate($data, 'date'),
'client_id' => $data->client_id,
'invoice_id' => $data->invoice_id,
];

View File

@ -27,8 +27,8 @@ class InvoiceTransformer extends BaseTransformer
'client_id' => $this->getClientId($data->customer),
'invoice_number' => $this->getInvoiceNumber($data->invoice_num),
'po_number' => $this->getString($data, 'po_so'),
'invoice_date_sql' => $this->getDate($data->invoice_date),
'due_date_sql' => $this->getDate($data->due_date),
'invoice_date_sql' => $this->getDate($data, 'invoice_date'),
'due_date_sql' => $this->getDate($data, 'due_date'),
'paid' => 0,
'invoice_items' => [
[
@ -41,4 +41,4 @@ class InvoiceTransformer extends BaseTransformer
];
});
}
}
}

View File

@ -21,7 +21,7 @@ class PaymentTransformer extends BaseTransformer
return new Item($data, function ($data) {
return [
'amount' => (float) $data->amount,
'payment_date_sql' => $this->getDate($data->payment_date),
'payment_date_sql' => $this->getDate($data, 'payment_date'),
'client_id' => $this->getInvoiceClientId($data->invoice_num),
'invoice_id' => $this->getInvoiceId($data->invoice_num),
];

View File

@ -23,22 +23,45 @@ class InvoiceTransformer extends BaseTransformer
}
return new Item($data, function ($data) {
return [
$invoice = [
'client_id' => $this->getClientId($data->customer_name),
'invoice_number' => $this->getInvoiceNumber($data->invoice_number),
'paid' => (float) $data->total - (float) $data->balance,
'po_number' => $this->getString($data, 'purchaseorder'),
'due_date_sql' => $data->due_date,
'invoice_date_sql' => $data->invoice_date,
'custom_value1' => (float) $data->latefee_amount + (float) $data->adjustment + (float) $data->shipping_charge,
'custom_taxes1' => false,
'invoice_items' => [
[
'product_key' => '',
'product_key' => $this->getString($data, 'item_name'),
'notes' => $this->getString($data, 'item_desc'),
'cost' => (float) $data->total,
'qty' => 1,
'cost' => (float) $data->item_price,
'qty' => (float) $data->quantity,
'tax_name1' => (float) $data->item_tax1 ? trans('texts.tax') : '',
'tax_rate1' => (float) $data->item_tax1,
'tax_name2' => (float) $data->item_tax2 ? trans('texts.tax') : '',
'tax_rate2' => (float) $data->item_tax2,
]
],
];
// we don't support line item discounts so we need to include
// the discount as a separate line item
if ((float) $data->discount_amount) {
$invoice['invoice_items'][] = [
'product_key' => '',
'notes' => trans('texts.discount'),
'cost' => (float) $data->discount_amount * -1,
'qty' => 1,
'tax_name1' => (float) $data->item_tax1 ? trans('texts.tax') : '',
'tax_rate1' => (float) $data->item_tax1,
'tax_name2' => (float) $data->item_tax2 ? trans('texts.tax') : '',
'tax_rate2' => (float) $data->item_tax2,
];
}
return $invoice;
});
}
}
}

Some files were not shown because too many files have changed in this diff Show More