diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60ac1176aa10..c271968668be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,4 +7,4 @@ We welcome contributions! We'll improve this guide over time... Guidelines * Try to follow [PSR-2 guidlines](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) * Create pull requests against the develop branch -* Submit translations through [Transifex](https://www.transifex.com/invoice-ninja/) +* Submit translations through [Transifex](https://www.transifex.com/invoice-ninja/invoice-ninja/) diff --git a/README.md b/README.md index 03362f3a6e6f..a3133c4c1010 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ ### Pull Requests * Please create pull requests against the develop branch -* Submit translations through [Transifex](https://www.transifex.com/invoice-ninja/) +* Submit translations through [Transifex](https://www.transifex.com/invoice-ninja/invoice-ninja/) ### Contributors * [Troels Liebe Bentsen](https://github.com/tlbdk) diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index a602ae7ecb85..b6f779b9379f 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -268,7 +268,12 @@ class AppController extends BaseController Artisan::call('migrate', array('--force' => true)); Artisan::call('db:seed', array('--force' => true, '--class' => "UpdateSeeder")); Event::fire(new UserSettingsChanged()); - Session::flash('message', trans('texts.processed_updates')); + + // show message with link to Trello board + $message = trans('texts.see_whats_new', ['version' => NINJA_VERSION]); + $message = link_to(RELEASES_URL, $message, ['target' => '_blank']); + $message = sprintf('%s - %s', trans('texts.processed_updates'), $message); + Session::flash('warning', $message); } catch (Exception $e) { Utils::logError($e); return Response::make($e->getMessage(), 500); diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index e5b6fd432b1a..d2a0633e5c73 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -72,7 +72,10 @@ class ClientController extends BaseController public function getDatatable() { - return $this->clientService->getDatatable(Input::get('sSearch')); + $search = Input::get('sSearch'); + $userId = Auth::user()->filterId(); + + return $this->clientService->getDatatable($search, $userId); } /** @@ -97,8 +100,8 @@ class ClientController extends BaseController */ public function show(ClientRequest $request) { - $client = $request->entity(); - + $client = $request->entity(); + $user = Auth::user(); Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT); @@ -109,19 +112,19 @@ class ClientController extends BaseController if (Utils::hasFeature(FEATURE_QUOTES) && $user->can('create', ENTITY_INVOICE)) { $actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => URL::to('/quotes/create/'.$client->public_id)]; } - + if(!empty($actionLinks)){ $actionLinks[] = \DropdownButton::DIVIDER; } - + if($user->can('create', ENTITY_PAYMENT)){ $actionLinks[] = ['label' => trans('texts.enter_payment'), 'url' => URL::to('/payments/create/'.$client->public_id)]; } - + if($user->can('create', ENTITY_CREDIT)){ $actionLinks[] = ['label' => trans('texts.enter_credit'), 'url' => URL::to('/credits/create/'.$client->public_id)]; } - + if($user->can('create', ENTITY_EXPENSE)){ $actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => URL::to('/expenses/create/0/'.$client->public_id)]; } @@ -174,7 +177,7 @@ class ClientController extends BaseController public function edit(ClientRequest $request) { $client = $request->entity(); - + $data = [ 'client' => $client, 'method' => 'PUT', diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/ClientPortalController.php similarity index 99% rename from app/Http/Controllers/PublicClientController.php rename to app/Http/Controllers/ClientPortalController.php index 79e236d70fc6..56dd183b19fe 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/ClientPortalController.php @@ -26,7 +26,7 @@ use App\Events\QuoteInvitationWasViewed; use App\Services\PaymentService; use Barracuda\ArchiveStream\ZipArchive; -class PublicClientController extends BaseController +class ClientPortalController extends BaseController { private $invoiceRepo; private $paymentRepo; @@ -323,7 +323,7 @@ class PublicClientController extends BaseController ->addColumn('activity_type_id', function ($model) { $data = [ 'client' => Utils::getClientDisplayName($model), - 'user' => $model->is_system ? ('' . trans('texts.system') . '') : ($model->user_first_name . ' ' . $model->user_last_name), + 'user' => $model->is_system ? ('' . trans('texts.system') . '') : ($model->account_name), 'invoice' => $model->invoice, 'contact' => Utils::getClientDisplayName($model), 'payment' => $model->payment ? ' ' . $model->payment : '', diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index dc837adb9a94..1170812b8356 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -80,8 +80,13 @@ class DashboardApiController extends BaseAPIController ->where('accounts.id', '=', Auth::user()->account_id) ->where('clients.is_deleted', '=', false) ->groupBy('accounts.id') - ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) - ->get(); + ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')); + + if (!$view_all) { + $balances->where('clients.user_id', '=', $user_id); + } + + $balances = $balances->get(); $pastDue = DB::table('invoices') ->leftJoin('clients', 'clients.id', '=', 'invoices.client_id') diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 5daa8827883a..718d73c67d42 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -13,7 +13,7 @@ class DashboardController extends BaseController { $view_all = Auth::user()->hasPermission('view_all'); $user_id = Auth::user()->id; - + // total_income, billed_clients, invoice_sent and active_clients $select = DB::raw('COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) billed_clients, SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent, @@ -27,17 +27,17 @@ class DashboardController extends BaseController ->where('invoices.is_deleted', '=', false) ->where('invoices.is_recurring', '=', false) ->where('invoices.is_quote', '=', false); - + if(!$view_all){ $metrics = $metrics->where(function($query) use($user_id){ $query->where('invoices.user_id', '=', $user_id); $query->orwhere(function($query) use($user_id){ - $query->where('invoices.user_id', '=', null); + $query->where('invoices.user_id', '=', null); $query->where('clients.user_id', '=', $user_id); }); }); } - + $metrics = $metrics->groupBy('accounts.id') ->first(); @@ -47,11 +47,11 @@ class DashboardController extends BaseController ->leftJoin('clients', 'accounts.id', '=', 'clients.account_id') ->where('accounts.id', '=', Auth::user()->account_id) ->where('clients.is_deleted', '=', false); - + if(!$view_all){ $paidToDate = $paidToDate->where('clients.user_id', '=', $user_id); } - + $paidToDate = $paidToDate->groupBy('accounts.id') ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) ->get(); @@ -66,11 +66,11 @@ class DashboardController extends BaseController ->where('invoices.is_deleted', '=', false) ->where('invoices.is_quote', '=', false) ->where('invoices.is_recurring', '=', false); - + if(!$view_all){ $averageInvoice = $averageInvoice->where('invoices.user_id', '=', $user_id); } - + $averageInvoice = $averageInvoice->groupBy('accounts.id') ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) ->get(); @@ -82,16 +82,21 @@ class DashboardController extends BaseController ->where('accounts.id', '=', Auth::user()->account_id) ->where('clients.is_deleted', '=', false) ->groupBy('accounts.id') - ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) - ->get(); + ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')); + + if (!$view_all) { + $balances->where('clients.user_id', '=', $user_id); + } + + $balances = $balances->get(); $activities = Activity::where('activities.account_id', '=', Auth::user()->account_id) ->where('activities.activity_type_id', '>', 0); - + if(!$view_all){ $activities = $activities->where('activities.user_id', '=', $user_id); } - + $activities = $activities->orderBy('activities.created_at', 'desc') ->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account') ->take(50) @@ -111,11 +116,11 @@ class DashboardController extends BaseController ->where('invoices.deleted_at', '=', null) ->where('contacts.is_primary', '=', true) ->where('invoices.due_date', '<', date('Y-m-d')); - + if(!$view_all){ $pastDue = $pastDue->where('invoices.user_id', '=', $user_id); } - + $pastDue = $pastDue->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) ->orderBy('invoices.due_date', 'asc') ->take(50) @@ -136,11 +141,11 @@ class DashboardController extends BaseController ->where('contacts.is_primary', '=', true) ->where('invoices.due_date', '>=', date('Y-m-d')) ->orderBy('invoices.due_date', 'asc'); - + if(!$view_all){ $upcoming = $upcoming->where('invoices.user_id', '=', $user_id); } - + $upcoming = $upcoming->take(50) ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) ->get(); @@ -155,11 +160,11 @@ class DashboardController extends BaseController ->where('clients.is_deleted', '=', false) ->where('contacts.deleted_at', '=', null) ->where('contacts.is_primary', '=', true); - + if(!$view_all){ $payments = $payments->where('payments.user_id', '=', $user_id); } - + $payments = $payments->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id']) ->orderBy('payments.payment_date', 'desc') ->take(50) diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index ebaaa1f6c306..c7f30b03be29 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -84,13 +84,14 @@ class ExportController extends BaseController if ($key === 'account' || $key === 'title' || $key === 'multiUser') { continue; } + if ($key === 'recurringInvoices') { + $key = 'recurring_invoices'; + } $label = trans("texts.{$key}"); $excel->sheet($label, function($sheet) use ($key, $data) { if ($key === 'quotes') { $key = 'invoices'; $data['entityType'] = ENTITY_QUOTE; - } elseif ($key === 'recurringInvoices') { - $key = 'recurring_invoices'; } $sheet->loadView("export.{$key}", $data); }); @@ -107,7 +108,7 @@ class ExportController extends BaseController 'title' => 'Invoice Ninja v' . NINJA_VERSION . ' - ' . $account->formatDateTime($account->getDateTime()), 'multiUser' => $account->users->count() > 1 ]; - + if ($request->input(ENTITY_CLIENT)) { $data['clients'] = Client::scope() ->with('user', 'contacts', 'country') @@ -123,14 +124,14 @@ class ExportController extends BaseController ->with('user', 'client.contacts') ->get(); } - + if ($request->input(ENTITY_TASK)) { $data['tasks'] = Task::scope() ->with('user', 'client.contacts') ->withArchived() ->get(); } - + if ($request->input(ENTITY_INVOICE)) { $data['invoices'] = Invoice::scope() ->with('user', 'client.contacts', 'invoice_status') @@ -138,7 +139,7 @@ class ExportController extends BaseController ->where('is_quote', '=', false) ->where('is_recurring', '=', false) ->get(); - + $data['quotes'] = Invoice::scope() ->with('user', 'client.contacts', 'invoice_status') ->withArchived() @@ -153,7 +154,7 @@ class ExportController extends BaseController ->where('is_recurring', '=', true) ->get(); } - + if ($request->input(ENTITY_PAYMENT)) { $data['payments'] = Payment::scope() ->withArchived() @@ -161,7 +162,7 @@ class ExportController extends BaseController ->get(); } - + if ($request->input(ENTITY_VENDOR)) { $data['clients'] = Vendor::scope() ->with('user', 'vendor_contacts', 'country') @@ -172,14 +173,14 @@ class ExportController extends BaseController ->with('user', 'vendor.vendor_contacts') ->withTrashed() ->get(); - + /* $data['expenses'] = Credit::scope() ->with('user', 'client.contacts') ->get(); */ } - + return $data; } -} \ No newline at end of file +} diff --git a/app/Http/routes.php b/app/Http/routes.php index e4e06f76617e..8b52f1fc2ae6 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -37,36 +37,36 @@ Route::post('/get_started', 'AccountController@getStarted'); // Client visible pages Route::group(['middleware' => 'auth:client'], function() { - Route::get('view/{invitation_key}', 'PublicClientController@view'); - Route::get('download/{invitation_key}', 'PublicClientController@download'); + Route::get('view/{invitation_key}', 'ClientPortalController@view'); + Route::get('download/{invitation_key}', 'ClientPortalController@download'); Route::get('view', 'HomeController@viewLogo'); Route::get('approve/{invitation_key}', 'QuoteController@approve'); Route::get('payment/{invitation_key}/{payment_type?}/{source_id?}', 'PaymentController@show_payment'); Route::post('payment/{invitation_key}', 'PaymentController@do_payment'); Route::match(['GET', 'POST'], 'complete', 'PaymentController@offsite_payment'); - Route::get('client/paymentmethods', 'PublicClientController@paymentMethods'); - Route::post('client/paymentmethods/verify', 'PublicClientController@verifyPaymentMethod'); - Route::get('client/paymentmethods/add/{payment_type}/{source_id?}', 'PublicClientController@addPaymentMethod'); - Route::post('client/paymentmethods/add/{payment_type}', 'PublicClientController@postAddPaymentMethod'); - Route::post('client/paymentmethods/default', 'PublicClientController@setDefaultPaymentMethod'); - Route::post('client/paymentmethods/{source_id}/remove', 'PublicClientController@removePaymentMethod'); - Route::get('client/quotes', 'PublicClientController@quoteIndex'); - Route::get('client/invoices', 'PublicClientController@invoiceIndex'); - Route::get('client/invoices/recurring', 'PublicClientController@recurringInvoiceIndex'); - Route::post('client/invoices/auto_bill', 'PublicClientController@setAutoBill'); - Route::get('client/documents', 'PublicClientController@documentIndex'); - Route::get('client/payments', 'PublicClientController@paymentIndex'); - Route::get('client/dashboard', 'PublicClientController@dashboard'); - Route::get('client/documents/js/{documents}/{filename}', 'PublicClientController@getDocumentVFSJS'); - Route::get('client/documents/{invitation_key}/{documents}/{filename?}', 'PublicClientController@getDocument'); - Route::get('client/documents/{invitation_key}/{filename?}', 'PublicClientController@getInvoiceDocumentsZip'); + Route::get('client/paymentmethods', 'ClientPortalController@paymentMethods'); + Route::post('client/paymentmethods/verify', 'ClientPortalController@verifyPaymentMethod'); + Route::get('client/paymentmethods/add/{payment_type}/{source_id?}', 'ClientPortalController@addPaymentMethod'); + Route::post('client/paymentmethods/add/{payment_type}', 'ClientPortalController@postAddPaymentMethod'); + Route::post('client/paymentmethods/default', 'ClientPortalController@setDefaultPaymentMethod'); + Route::post('client/paymentmethods/{source_id}/remove', 'ClientPortalController@removePaymentMethod'); + Route::get('client/quotes', 'ClientPortalController@quoteIndex'); + Route::get('client/invoices', 'ClientPortalController@invoiceIndex'); + Route::get('client/invoices/recurring', 'ClientPortalController@recurringInvoiceIndex'); + Route::post('client/invoices/auto_bill', 'ClientPortalController@setAutoBill'); + Route::get('client/documents', 'ClientPortalController@documentIndex'); + Route::get('client/payments', 'ClientPortalController@paymentIndex'); + Route::get('client/dashboard', 'ClientPortalController@dashboard'); + Route::get('client/documents/js/{documents}/{filename}', 'ClientPortalController@getDocumentVFSJS'); + Route::get('client/documents/{invitation_key}/{documents}/{filename?}', 'ClientPortalController@getDocument'); + Route::get('client/documents/{invitation_key}/{filename?}', 'ClientPortalController@getInvoiceDocumentsZip'); - Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable')); - Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable')); - Route::get('api/client.recurring_invoices', array('as'=>'api.client.recurring_invoices', 'uses'=>'PublicClientController@recurringInvoiceDatatable')); - Route::get('api/client.documents', array('as'=>'api.client.documents', 'uses'=>'PublicClientController@documentDatatable')); - Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable')); - Route::get('api/client.activity', array('as'=>'api.client.activity', 'uses'=>'PublicClientController@activityDatatable')); + Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'ClientPortalController@quoteDatatable')); + Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'ClientPortalController@invoiceDatatable')); + Route::get('api/client.recurring_invoices', array('as'=>'api.client.recurring_invoices', 'uses'=>'ClientPortalController@recurringInvoiceDatatable')); + Route::get('api/client.documents', array('as'=>'api.client.documents', 'uses'=>'ClientPortalController@documentDatatable')); + Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'ClientPortalController@paymentDatatable')); + Route::get('api/client.activity', array('as'=>'api.client.activity', 'uses'=>'ClientPortalController@activityDatatable')); }); @@ -190,7 +190,7 @@ Route::group(['middleware' => 'auth:user'], function() { Route::resource('expenses', 'ExpenseController'); Route::get('expenses/create/{vendor_id?}/{client_id?}', 'ExpenseController@create'); Route::get('api/expense', array('as'=>'api.expenses', 'uses'=>'ExpenseController@getDatatable')); - Route::get('api/expenseVendor/{id}', array('as'=>'api.expense', 'uses'=>'ExpenseController@getDatatableVendor')); + Route::get('api/vendor_expense/{id}', array('as'=>'api.expense', 'uses'=>'ExpenseController@getDatatableVendor')); Route::post('expenses/bulk', 'ExpenseController@bulk'); }); diff --git a/app/Models/DateFormat.php b/app/Models/DateFormat.php index b343fd422657..47afafb4cc76 100644 --- a/app/Models/DateFormat.php +++ b/app/Models/DateFormat.php @@ -5,4 +5,11 @@ use Eloquent; class DateFormat extends Eloquent { public $timestamps = false; + + public function __toString() + { + $date = mktime(0, 0, 0, 12, 31, date('Y')); + + return date($this->format, $date); + } } diff --git a/app/Models/DatetimeFormat.php b/app/Models/DatetimeFormat.php index 1d7ba8b93657..6b7edf85ec63 100644 --- a/app/Models/DatetimeFormat.php +++ b/app/Models/DatetimeFormat.php @@ -5,4 +5,11 @@ use Eloquent; class DatetimeFormat extends Eloquent { public $timestamps = false; + + public function __toString() + { + $date = mktime(0, 0, 0, 12, 31, date('Y')); + + return date($this->format, $date); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 71069d25821c..9dab5e759a15 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -20,8 +20,8 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac 'create_all' => 0b0001, 'view_all' => 0b0010, 'edit_all' => 0b0100, - ); - + ); + use Authenticatable, Authorizable, CanResetPassword; /** @@ -168,7 +168,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac { return Session::get(SESSION_COUNTER, 0); } - + public function afterSave($success = true, $forced = false) { if ($this->email) { @@ -199,8 +199,8 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac return MAX_NUM_VENDORS; } - - + + public function getRememberToken() { return $this->remember_token; @@ -265,9 +265,9 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac && $this->email != $this->getOriginal('email') && $this->getOriginal('confirmed'); } - - - + + + /** * Set the permissions attribute on the model. * @@ -277,7 +277,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac protected function setPermissionsAttribute($value){ if(empty($value)) { $this->attributes['permissions'] = 0; - } else { + } else { $bitmask = 0; foreach($value as $permission){ $bitmask = $bitmask | static::$all_permissions[$permission]; @@ -285,10 +285,10 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac $this->attributes['permissions'] = $bitmask; } - + return $this; } - + /** * Expands the value of the permissions attribute * @@ -302,10 +302,10 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac $permissions[$permission] = $permission; } } - + return $permissions; } - + /** * Checks to see if the user has the required permission * @@ -325,13 +325,17 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac return count(array_intersect($permission, $this->permissions)) > 0; } } - + return false; } - + public function owns($entity) { return !empty($entity->user_id) && $entity->user_id == $this->id; } + + public function filterId() { + return $this->hasPermission('view_all') ? false : $this->id; + } } User::updating(function ($user) { diff --git a/app/Ninja/Datatables/AccountGatewayDatatable.php b/app/Ninja/Datatables/AccountGatewayDatatable.php new file mode 100644 index 000000000000..ad16fc39878a --- /dev/null +++ b/app/Ninja/Datatables/AccountGatewayDatatable.php @@ -0,0 +1,110 @@ +deleted_at) { + return $model->name; + } elseif ($model->gateway_id != GATEWAY_WEPAY) { + return link_to("gateways/{$model->public_id}/edit", $model->name)->toHtml(); + } else { + $accountGateway = AccountGateway::find($model->id); + $config = $accountGateway->getConfig(); + $endpoint = WEPAY_ENVIRONMENT == WEPAY_STAGE ? 'https://stage.wepay.com/' : 'https://www.wepay.com/'; + $wepayAccountId = $config->accountId; + $wepayState = isset($config->state)?$config->state:null; + $linkText = $model->name; + $url = $endpoint.'account/'.$wepayAccountId; + $wepay = \Utils::setupWepay($accountGateway); + $html = link_to($url, $linkText, array('target'=>'_blank'))->toHtml(); + + try { + if ($wepayState == 'action_required') { + $updateUri = $wepay->request('/account/get_update_uri', array( + 'account_id' => $wepayAccountId, + 'redirect_uri' => URL::to('gateways'), + )); + + $linkText .= ' ('.trans('texts.action_required').')'; + $url = $updateUri->uri; + $html = "{$linkText}"; + $model->setupUrl = $url; + } elseif ($wepayState == 'pending') { + $linkText .= ' ('.trans('texts.resend_confirmation_email').')'; + $model->resendConfirmationUrl = $url = URL::to("gateways/{$accountGateway->public_id}/resend_confirmation"); + $html = link_to($url, $linkText)->toHtml(); + } + } catch(\WePayException $ex){} + + return $html; + } + } + ], + [ + 'payment_type', + function ($model) { + return Gateway::getPrettyPaymentType($model->gateway_id); + } + ], + ]; + } + + public function actions() + { + return [ + [ + uctrans('texts.resend_confirmation_email'), + function ($model) { + return $model->resendConfirmationUrl; + }, + function($model) { + return !$model->deleted_at && $model->gateway_id == GATEWAY_WEPAY && !empty($model->resendConfirmationUrl); + } + ], [ + uctrans('texts.finish_setup'), + function ($model) { + return $model->setupUrl; + }, + function($model) { + return !$model->deleted_at && $model->gateway_id == GATEWAY_WEPAY && !empty($model->setupUrl); + } + ] , [ + uctrans('texts.edit_gateway'), + function ($model) { + return URL::to("gateways/{$model->public_id}/edit"); + }, + function($model) { + return !$model->deleted_at; + } + ], [ + uctrans('texts.manage_wepay_account'), + function ($model) { + $accountGateway = AccountGateway::find($model->id); + $endpoint = WEPAY_ENVIRONMENT == WEPAY_STAGE ? 'https://stage.wepay.com/' : 'https://www.wepay.com/'; + return array( + 'url' => $endpoint.'account/'.$accountGateway->getConfig()->accountId, + 'attributes' => 'target="_blank"' + ); + }, + function($model) { + return !$model->deleted_at && $model->gateway_id == GATEWAY_WEPAY; + } + ] + ]; + } + +} diff --git a/app/Ninja/Datatables/ActivityDatatable.php b/app/Ninja/Datatables/ActivityDatatable.php new file mode 100644 index 000000000000..5f74bbbe54fb --- /dev/null +++ b/app/Ninja/Datatables/ActivityDatatable.php @@ -0,0 +1,52 @@ +created_at)); + } + ], + [ + 'activity_type_id', + function ($model) { + $data = [ + 'client' => link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model))->toHtml(), + 'user' => $model->is_system ? '' . trans('texts.system') . '' : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), + 'invoice' => $model->invoice ? link_to('/invoices/' . $model->invoice_public_id, $model->is_recurring ? trans('texts.recurring_invoice') : $model->invoice)->toHtml() : null, + 'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice)->toHtml() : null, + 'contact' => $model->contact_id ? link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model))->toHtml() : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), + 'payment' => $model->payment ?: '', + 'credit' => $model->payment_amount ? Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) : '', + 'payment_amount' => $model->payment_amount ? Utils::formatMoney($model->payment_amount, $model->currency_id, $model->country_id) : null, + 'adjustment' => $model->adjustment ? Utils::formatMoney($model->adjustment, $model->currency_id, $model->country_id) : null + ]; + + return trans("texts.activity_{$model->activity_type_id}", $data); + } + ], + [ + 'balance', + function ($model) { + return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); + } + ], + [ + 'adjustment', + function ($model) { + return $model->adjustment != 0 ? Utils::wrapAdjustment($model->adjustment, $model->currency_id, $model->country_id) : ''; + } + ] + ]; + } +} diff --git a/app/Ninja/Datatables/BankAccountDatatable.php b/app/Ninja/Datatables/BankAccountDatatable.php new file mode 100644 index 000000000000..48bc2672f1c9 --- /dev/null +++ b/app/Ninja/Datatables/BankAccountDatatable.php @@ -0,0 +1,42 @@ +public_id}/edit", $model->bank_name)->toHtml(); + }, + ], + [ + 'bank_library_id', + function ($model) { + return 'OFX'; + } + ], + ]; + } + + public function actions() + { + return [ + [ + uctrans('texts.edit_bank_account'), + function ($model) { + return URL::to("bank_accounts/{$model->public_id}/edit"); + }, + ] + ]; + } + + +} diff --git a/app/Ninja/Datatables/ClientDatatable.php b/app/Ninja/Datatables/ClientDatatable.php new file mode 100644 index 000000000000..4b0ca68b0544 --- /dev/null +++ b/app/Ninja/Datatables/ClientDatatable.php @@ -0,0 +1,136 @@ +public_id}", $model->name ?: '')->toHtml(); + } + ], + [ + 'first_name', + function ($model) { + return link_to("clients/{$model->public_id}", $model->first_name.' '.$model->last_name)->toHtml(); + } + ], + [ + 'email', + function ($model) { + return link_to("clients/{$model->public_id}", $model->email ?: '')->toHtml(); + } + ], + [ + 'clients.created_at', + function ($model) { + return Utils::timestampToDateString(strtotime($model->created_at)); + } + ], + [ + 'last_login', + function ($model) { + return Utils::timestampToDateString(strtotime($model->last_login)); + } + ], + [ + 'balance', + function ($model) { + return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); + } + ] + ]; + } + + public function actions() + { + return [ + [ + trans('texts.edit_client'), + function ($model) { + return URL::to("clients/{$model->public_id}/edit"); + }, + function ($model) { + return Auth::user()->can('editByOwner', [ENTITY_CLIENT, $model->user_id]); + } + ], + [ + '--divider--', function(){return false;}, + function ($model) { + $user = Auth::user(); + return $user->can('editByOwner', [ENTITY_CLIENT, $model->user_id]) && ($user->can('create', ENTITY_TASK) || $user->can('create', ENTITY_INVOICE)); + } + ], + [ + trans('texts.new_task'), + function ($model) { + return URL::to("tasks/create/{$model->public_id}"); + }, + function ($model) { + return Auth::user()->can('create', ENTITY_TASK); + } + ], + [ + trans('texts.new_invoice'), + function ($model) { + return URL::to("invoices/create/{$model->public_id}"); + }, + function ($model) { + return Auth::user()->can('create', ENTITY_INVOICE); + } + ], + [ + trans('texts.new_quote'), + function ($model) { + return URL::to("quotes/create/{$model->public_id}"); + }, + function ($model) { + return Auth::user()->hasFeature(FEATURE_QUOTES) && Auth::user()->can('create', ENTITY_INVOICE); + } + ], + [ + '--divider--', function(){return false;}, + function ($model) { + $user = Auth::user(); + return ($user->can('create', ENTITY_TASK) || $user->can('create', ENTITY_INVOICE)) && ($user->can('create', ENTITY_PAYMENT) || $user->can('create', ENTITY_CREDIT) || $user->can('create', ENTITY_EXPENSE)); + } + ], + [ + trans('texts.enter_payment'), + function ($model) { + return URL::to("payments/create/{$model->public_id}"); + }, + function ($model) { + return Auth::user()->can('create', ENTITY_PAYMENT); + } + ], + [ + trans('texts.enter_credit'), + function ($model) { + return URL::to("credits/create/{$model->public_id}"); + }, + function ($model) { + return Auth::user()->can('create', ENTITY_CREDIT); + } + ], + [ + trans('texts.enter_expense'), + function ($model) { + return URL::to("expenses/create/0/{$model->public_id}"); + }, + function ($model) { + return Auth::user()->can('create', ENTITY_EXPENSE); + } + ] + ]; + } + +} diff --git a/app/Ninja/Datatables/CreditDatatable.php b/app/Ninja/Datatables/CreditDatatable.php new file mode 100644 index 000000000000..7f258e377d1d --- /dev/null +++ b/app/Ninja/Datatables/CreditDatatable.php @@ -0,0 +1,66 @@ +can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){ + return Utils::getClientDisplayName($model); + } + + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; + }, + ! $this->hideClient + ], + [ + 'amount', + function ($model) { + return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id) . ''; + } + ], + [ + 'balance', + function ($model) { + return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); + } + ], + [ + 'credit_date', + function ($model) { + return Utils::fromSqlDate($model->credit_date); + } + ], + [ + 'private_notes', + function ($model) { + return $model->private_notes; + } + ] + ]; + } + + public function actions() + { + return [ + [ + trans('texts.apply_credit'), + function ($model) { + return URL::to("payments/create/{$model->client_public_id}") . '?paymentTypeId=1'; + }, + function ($model) { + return Auth::user()->can('create', ENTITY_PAYMENT); + } + ] + ]; + } +} diff --git a/app/Ninja/Datatables/EntityDatatable.php b/app/Ninja/Datatables/EntityDatatable.php new file mode 100644 index 000000000000..085645ff2069 --- /dev/null +++ b/app/Ninja/Datatables/EntityDatatable.php @@ -0,0 +1,24 @@ +isBulkEdit = $isBulkEdit; + $this->hideClient = $hideClient; + } + + public function columns() + { + return []; + } + + public function actions() + { + return []; + } +} diff --git a/app/Ninja/Datatables/ExpenseDatatable.php b/app/Ninja/Datatables/ExpenseDatatable.php new file mode 100644 index 000000000000..29bbbe0c087d --- /dev/null +++ b/app/Ninja/Datatables/ExpenseDatatable.php @@ -0,0 +1,134 @@ +vendor_public_id) { + if(!Auth::user()->can('viewByOwner', [ENTITY_VENDOR, $model->vendor_user_id])){ + return $model->vendor_name; + } + + return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name)->toHtml(); + } else { + return ''; + } + }, + ! $this->hideClient + ], + [ + 'client_name', + function ($model) + { + if ($model->client_public_id) { + if(!Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){ + return Utils::getClientDisplayName($model); + } + + return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); + } else { + return ''; + } + }, + ! $this->hideClient + ], + [ + 'expense_date', + function ($model) { + if(!Auth::user()->can('editByOwner', [ENTITY_EXPENSE, $model->user_id])){ + return Utils::fromSqlDate($model->expense_date); + } + + return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date))->toHtml(); + } + ], + [ + 'amount', + function ($model) { + // show both the amount and the converted amount + if ($model->exchange_rate != 1) { + $converted = round($model->amount * $model->exchange_rate, 2); + return Utils::formatMoney($model->amount, $model->expense_currency_id) . ' | ' . + Utils::formatMoney($converted, $model->invoice_currency_id); + } else { + return Utils::formatMoney($model->amount, $model->expense_currency_id); + } + } + ], + [ + 'public_notes', + function ($model) { + return $model->public_notes != null ? substr($model->public_notes, 0, 100) : ''; + } + ], + [ + 'expense_status_id', + function ($model) { + return self::getStatusLabel($model->invoice_id, $model->should_be_invoiced); + } + ], + ]; + } + + public function actions() + { + return [ + [ + trans('texts.edit_expense'), + function ($model) { + return URL::to("expenses/{$model->public_id}/edit") ; + }, + function ($model) { + return Auth::user()->can('editByOwner', [ENTITY_EXPENSE, $model->user_id]); + } + ], + [ + trans('texts.view_invoice'), + function ($model) { + return URL::to("/invoices/{$model->invoice_public_id}/edit"); + }, + function ($model) { + return $model->invoice_public_id && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->invoice_user_id]); + } + ], + [ + trans('texts.invoice_expense'), + function ($model) { + return "javascript:invoiceEntity({$model->public_id})"; + }, + function ($model) { + return ! $model->invoice_id && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Auth::user()->can('create', ENTITY_INVOICE); + } + ], + ]; + } + + + private function getStatusLabel($invoiceId, $shouldBeInvoiced) + { + if ($invoiceId) { + $label = trans('texts.invoiced'); + $class = 'success'; + } elseif ($shouldBeInvoiced) { + $label = trans('texts.pending'); + $class = 'warning'; + } else { + $label = trans('texts.logged'); + $class = 'primary'; + } + + return "

$label

"; + } + +} diff --git a/app/Ninja/Datatables/InvoiceDatatable.php b/app/Ninja/Datatables/InvoiceDatatable.php new file mode 100644 index 000000000000..27b3343561fd --- /dev/null +++ b/app/Ninja/Datatables/InvoiceDatatable.php @@ -0,0 +1,193 @@ +entityType; + + return [ + [ + 'invoice_number', + function ($model) use ($entityType) { + if(!Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id])){ + return $model->invoice_number; + } + + return link_to("{$entityType}s/{$model->public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml(); + } + ], + [ + 'client_name', + function ($model) { + if(!Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){ + return Utils::getClientDisplayName($model); + } + return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); + }, + ! $this->hideClient + ], + [ + 'invoice_date', + function ($model) { + return Utils::fromSqlDate($model->invoice_date); + } + ], + [ + 'amount', + function ($model) { + return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); + } + ], + [ + 'balance', + function ($model) { + return $model->partial > 0 ? + trans('texts.partial_remaining', [ + 'partial' => Utils::formatMoney($model->partial, $model->currency_id, $model->country_id), + 'balance' => Utils::formatMoney($model->balance, $model->currency_id, $model->country_id)] + ) : + Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); + }, + $entityType == ENTITY_INVOICE + ], + [ + 'due_date', + function ($model) { + return Utils::fromSqlDate($model->due_date); + }, + ], + [ + 'invoice_status_name', + function ($model) use ($entityType) { + return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted'))->toHtml() : self::getStatusLabel($model); + } + ] + ]; + } + + public function actions() + { + $entityType = $this->entityType; + + return [ + [ + trans("texts.edit_{$entityType}"), + function ($model) use ($entityType) { + return URL::to("{$entityType}s/{$model->public_id}/edit"); + }, + function ($model) { + return Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); + } + ], + [ + trans("texts.clone_{$entityType}"), + function ($model) use ($entityType) { + return URL::to("{$entityType}s/{$model->public_id}/clone"); + }, + function ($model) { + return Auth::user()->can('create', ENTITY_INVOICE); + } + ], + [ + trans("texts.view_history"), + function ($model) use ($entityType) { + return URL::to("{$entityType}s/{$entityType}_history/{$model->public_id}"); + } + ], + [ + '--divider--', function(){return false;}, + function ($model) { + return Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]) || Auth::user()->can('create', ENTITY_PAYMENT); + } + ], + [ + trans("texts.mark_sent"), + function ($model) { + return "javascript:markEntity({$model->public_id})"; + }, + function ($model) { + return $model->invoice_status_id < INVOICE_STATUS_SENT && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); + } + ], + [ + trans('texts.enter_payment'), + function ($model) { + return URL::to("payments/create/{$model->client_public_id}/{$model->public_id}"); + }, + function ($model) use ($entityType) { + return $entityType == ENTITY_INVOICE && $model->balance > 0 && Auth::user()->can('create', ENTITY_PAYMENT); + } + ], + [ + trans("texts.view_quote"), + function ($model) { + return URL::to("quotes/{$model->quote_id}/edit"); + }, + function ($model) use ($entityType) { + return $entityType == ENTITY_INVOICE && $model->quote_id && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); + } + ], + [ + trans("texts.view_invoice"), + function ($model) { + return URL::to("invoices/{$model->quote_invoice_id}/edit"); + }, + function ($model) use ($entityType) { + return $entityType == ENTITY_QUOTE && $model->quote_invoice_id && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); + } + ], + [ + trans("texts.convert_to_invoice"), + function ($model) { + return "javascript:convertEntity({$model->public_id})"; + }, + function ($model) use ($entityType) { + return $entityType == ENTITY_QUOTE && ! $model->quote_invoice_id && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); + } + ] + ]; + } + + 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 "

" . $label . "

"; + } + } + + $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; + } + + return "

$label

"; + } + +} diff --git a/app/Ninja/Datatables/PaymentDatatable.php b/app/Ninja/Datatables/PaymentDatatable.php new file mode 100644 index 000000000000..22006bf742e1 --- /dev/null +++ b/app/Ninja/Datatables/PaymentDatatable.php @@ -0,0 +1,158 @@ +can('editByOwner', [ENTITY_INVOICE, $model->invoice_user_id])){ + return $model->invoice_number; + } + + return link_to("invoices/{$model->invoice_public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml(); + } + ], + [ + 'client_name', + function ($model) { + if(!Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){ + return Utils::getClientDisplayName($model); + } + + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; + }, + ! $this->hideClient + ], + [ + 'transaction_reference', + function ($model) { + return $model->transaction_reference ? $model->transaction_reference : 'Manual entry'; + } + ], + [ + 'payment_type', + function ($model) { + return ($model->payment_type && !$model->last4) ? $model->payment_type : ($model->account_gateway_id ? $model->gateway_name : ''); + } + ], + [ + 'source', + function ($model) { + $code = str_replace(' ', '', strtolower($model->payment_type)); + $card_type = trans("texts.card_" . $code); + if ($model->payment_type_id != PAYMENT_TYPE_ACH) { + if($model->last4) { + $expiration = trans('texts.card_expiration', array('expires' => Utils::fromSqlDate($model->expiration, false)->format('m/y'))); + return '' . htmlentities($card_type) . '  •••' . $model->last4 . ' ' . $expiration; + } elseif ($model->email) { + return $model->email; + } + } elseif ($model->last4) { + $bankData = PaymentMethod::lookupBankData($model->routing_number); + if (is_object($bankData)) { + return $bankData->name.'  •••' . $model->last4; + } elseif($model->last4) { + return '' . htmlentities($card_type) . '  •••' . $model->last4; + } + } + } + ], + [ + 'amount', + function ($model) { + return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); + } + ], + [ + 'payment_date', + function ($model) { + return Utils::dateToString($model->payment_date); + } + ], + [ + 'payment_status_name', + function ($model) { + return self::getStatusLabel($model); + } + ] + ]; + } + + + public function actions() + { + return [ + [ + trans('texts.edit_payment'), + function ($model) { + return URL::to("payments/{$model->public_id}/edit"); + }, + function ($model) { + return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id]); + } + ], + [ + trans('texts.refund_payment'), + function ($model) { + $max_refund = number_format($model->amount - $model->refunded, 2); + $formatted = Utils::formatMoney($max_refund, $model->currency_id, $model->country_id); + $symbol = Utils::getFromCache($model->currency_id ? $model->currency_id : 1, 'currencies')->symbol ; + return "javascript:showRefundModal({$model->public_id}, '{$max_refund}', '{$formatted}', '{$symbol}')"; + }, + 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->payment_type_id == PAYMENT_TYPE_CREDIT + ); + } + ] + ]; + } + + 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; + } + return "

$label

"; + } +} diff --git a/app/Ninja/Datatables/ProductDatatable.php b/app/Ninja/Datatables/ProductDatatable.php new file mode 100644 index 000000000000..a5b3cbfc218d --- /dev/null +++ b/app/Ninja/Datatables/ProductDatatable.php @@ -0,0 +1,55 @@ +public_id.'/edit', $model->product_key)->toHtml(); + } + ], + [ + 'notes', + function ($model) { + return nl2br(Str::limit($model->notes, 100)); + } + ], + [ + 'cost', + function ($model) { + return Utils::formatMoney($model->cost); + } + ], + [ + 'tax_rate', + function ($model) { + return $model->tax_rate ? ($model->tax_name . ' ' . $model->tax_rate . '%') : ''; + }, + Auth::user()->account->invoice_item_taxes + ] + ]; + } + + public function actions() + { + return [ + [ + uctrans('texts.edit_product'), + function ($model) { + return URL::to("products/{$model->public_id}/edit"); + } + ] + ]; + } + +} diff --git a/app/Ninja/Datatables/RecurringInvoiceDatatable.php b/app/Ninja/Datatables/RecurringInvoiceDatatable.php new file mode 100644 index 000000000000..7b5365814eca --- /dev/null +++ b/app/Ninja/Datatables/RecurringInvoiceDatatable.php @@ -0,0 +1,63 @@ +public_id}", $model->frequency)->toHtml(); + } + ], + [ + 'client_name', + function ($model) { + return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); + }, + ! $this->hideClient + ], + [ + 'start_date', + function ($model) { + return Utils::fromSqlDate($model->start_date); + } + ], + [ + 'end_date', + function ($model) { + return Utils::fromSqlDate($model->end_date); + } + ], + [ + 'amount', + function ($model) { + return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); + } + ] + ]; + } + + public function actions() + { + return [ + [ + trans('texts.edit_invoice'), + function ($model) { + return URL::to("invoices/{$model->public_id}/edit"); + }, + function ($model) { + return Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); + } + ] + ]; + } + +} diff --git a/app/Ninja/Datatables/TaskDatatable.php b/app/Ninja/Datatables/TaskDatatable.php new file mode 100644 index 000000000000..6f460b418e86 --- /dev/null +++ b/app/Ninja/Datatables/TaskDatatable.php @@ -0,0 +1,113 @@ +can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){ + return Utils::getClientDisplayName($model); + } + + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; + }, + ! $this->hideClient + ], + [ + 'created_at', + function ($model) { + return link_to("tasks/{$model->public_id}/edit", Task::calcStartTime($model))->toHtml(); + } + ], + [ + 'time_log', + function($model) { + return Utils::formatTime(Task::calcDuration($model)); + } + ], + [ + 'description', + function ($model) { + return $model->description; + } + ], + [ + 'invoice_number', + function ($model) { + return self::getStatusLabel($model); + } + ] + ]; + } + + public function actions() + { + return [ + [ + trans('texts.edit_task'), + function ($model) { + return URL::to('tasks/'.$model->public_id.'/edit'); + }, + function ($model) { + return (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Auth::user()->can('editByOwner', [ENTITY_TASK, $model->user_id]); + } + ], + [ + trans('texts.view_invoice'), + function ($model) { + return URL::to("/invoices/{$model->invoice_public_id}/edit"); + }, + function ($model) { + return $model->invoice_number && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->invoice_user_id]); + } + ], + [ + trans('texts.stop_task'), + function ($model) { + return "javascript:stopTask({$model->public_id})"; + }, + function ($model) { + return $model->is_running && Auth::user()->can('editByOwner', [ENTITY_TASK, $model->user_id]); + } + ], + [ + trans('texts.invoice_task'), + function ($model) { + return "javascript:invoiceEntity({$model->public_id})"; + }, + function ($model) { + return ! $model->invoice_number && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Auth::user()->can('create', ENTITY_INVOICE); + } + ] + ]; + } + + private function getStatusLabel($model) + { + if ($model->invoice_number) { + $class = 'success'; + $label = trans('texts.invoiced'); + } elseif ($model->is_running) { + $class = 'primary'; + $label = trans('texts.running'); + } else { + $class = 'default'; + $label = trans('texts.logged'); + } + + return "

$label

"; + } + + +} diff --git a/app/Ninja/Datatables/TaxRateDatatable.php b/app/Ninja/Datatables/TaxRateDatatable.php new file mode 100644 index 000000000000..d6cb0cb54f03 --- /dev/null +++ b/app/Ninja/Datatables/TaxRateDatatable.php @@ -0,0 +1,41 @@ +public_id}/edit", $model->name)->toHtml(); + } + ], + [ + 'rate', + function ($model) { + return $model->rate . '%'; + } + ] + ]; + } + + public function actions() + { + return [ + [ + uctrans('texts.edit_tax_rate'), + function ($model) { + return URL::to("tax_rates/{$model->public_id}/edit"); + } + ] + ]; + } + +} diff --git a/app/Ninja/Datatables/TokenDatatable.php b/app/Ninja/Datatables/TokenDatatable.php new file mode 100644 index 000000000000..e0e248f15cee --- /dev/null +++ b/app/Ninja/Datatables/TokenDatatable.php @@ -0,0 +1,41 @@ +public_id}/edit", $model->name)->toHtml(); + } + ], + [ + 'token', + function ($model) { + return $model->token; + } + ] + ]; + } + + public function actions() + { + return [ + [ + uctrans('texts.edit_token'), + function ($model) { + return URL::to("tokens/{$model->public_id}/edit"); + } + ] + ]; + } + +} diff --git a/app/Ninja/Datatables/UserDatatable.php b/app/Ninja/Datatables/UserDatatable.php new file mode 100644 index 000000000000..a6f267fdbd00 --- /dev/null +++ b/app/Ninja/Datatables/UserDatatable.php @@ -0,0 +1,96 @@ +public_id ? link_to('users/'.$model->public_id.'/edit', $model->first_name.' '.$model->last_name)->toHtml() : ($model->first_name.' '.$model->last_name); + } + ], + [ + 'email', + function ($model) { + return $model->email; + } + ], + [ + 'confirmed', + function ($model) { + if (!$model->public_id) { + return self::getStatusLabel(USER_STATE_OWNER); + } elseif ($model->deleted_at) { + return self::getStatusLabel(USER_STATE_DISABLED); + } elseif ($model->confirmed) { + if($model->is_admin){ + return self::getStatusLabel(USER_STATE_ADMIN); + } else { + return self::getStatusLabel(USER_STATE_ACTIVE); + } + } else { + return self::getStatusLabel(USER_STATE_PENDING); + } + } + ], + ]; + } + + public function actions() + { + return [ + [ + uctrans('texts.edit_user'), + function ($model) { + return URL::to("users/{$model->public_id}/edit"); + }, + function ($model) { + return $model->public_id; + } + ], + [ + uctrans('texts.send_invite'), + function ($model) { + return URL::to("send_confirmation/{$model->public_id}"); + }, + function ($model) { + return $model->public_id && ! $model->confirmed; + } + ] + ]; + } + + private function getStatusLabel($state) + { + $label = trans("texts.{$state}"); + $class = 'default'; + switch ($state) { + case USER_STATE_PENDING: + $class = 'default'; + break; + case USER_STATE_ACTIVE: + $class = 'info'; + break; + case USER_STATE_DISABLED: + $class = 'warning'; + break; + case USER_STATE_OWNER: + $class = 'success'; + break; + case USER_STATE_ADMIN: + $class = 'primary'; + break; + } + return "

$label

"; + } + + +} diff --git a/app/Ninja/Datatables/VendorDatatable.php b/app/Ninja/Datatables/VendorDatatable.php new file mode 100644 index 000000000000..93e059b6ae55 --- /dev/null +++ b/app/Ninja/Datatables/VendorDatatable.php @@ -0,0 +1,79 @@ +public_id}", $model->name ?: '')->toHtml(); + } + ], + [ + 'city', + function ($model) { + return $model->city; + } + ], + [ + 'work_phone', + function ($model) { + return $model->work_phone; + } + ], + [ + 'email', + function ($model) { + return link_to("vendors/{$model->public_id}", $model->email ?: '')->toHtml(); + } + ], + [ + 'vendors.created_at', + function ($model) { + return Utils::timestampToDateString(strtotime($model->created_at)); + } + ], + ]; + } + + public function actions() + { + return [ + [ + trans('texts.edit_vendor'), + function ($model) { + return URL::to("vendors/{$model->public_id}/edit"); + }, + function ($model) { + return Auth::user()->can('editByOwner', [ENTITY_VENDOR, $model->user_id]); + } + ], + [ + '--divider--', function(){return false;}, + function ($model) { + return Auth::user()->can('editByOwner', [ENTITY_VENDOR, $model->user_id]) && Auth::user()->can('create', ENTITY_EXPENSE); + } + + ], + [ + trans('texts.enter_expense'), + function ($model) { + return URL::to("expenses/create/{$model->public_id}"); + }, + function ($model) { + return Auth::user()->can('create', ENTITY_EXPENSE); + } + ] + ]; + } + + +} diff --git a/app/Ninja/Presenters/ClientPresenter.php b/app/Ninja/Presenters/ClientPresenter.php index bb32a8b62fb0..22ed376be6c6 100644 --- a/app/Ninja/Presenters/ClientPresenter.php +++ b/app/Ninja/Presenters/ClientPresenter.php @@ -2,9 +2,8 @@ use URL; use Utils; -use Laracasts\Presenter\Presenter; -class ClientPresenter extends Presenter { +class ClientPresenter extends EntityPresenter { public function country() { @@ -28,14 +27,4 @@ class ClientPresenter extends Presenter { return "{$text}"; } - - public function url() - { - return URL::to('/clients/' . $this->entity->public_id); - } - - public function link() - { - return link_to('/clients/' . $this->entity->public_id, $this->entity->getDisplayName()); - } -} \ No newline at end of file +} diff --git a/app/Ninja/Presenters/CreditPresenter.php b/app/Ninja/Presenters/CreditPresenter.php index 7e38205b1067..96c0e5b6f4d0 100644 --- a/app/Ninja/Presenters/CreditPresenter.php +++ b/app/Ninja/Presenters/CreditPresenter.php @@ -1,9 +1,8 @@ entity->credit_date); } -} \ No newline at end of file +} diff --git a/app/Ninja/Presenters/EntityPresenter.php b/app/Ninja/Presenters/EntityPresenter.php new file mode 100644 index 000000000000..b1e16acbd620 --- /dev/null +++ b/app/Ninja/Presenters/EntityPresenter.php @@ -0,0 +1,25 @@ +entity->getEntityType(); + $id = $this->entity->public_id; + $link = sprintf('/%ss/%s', $type, $id); + + return URL::to($link); + } + + public function link() + { + $name = $this->entity->getDisplayName(); + $link = $this->url(); + + return link_to($link, $name)->toHtml(); + } + +} diff --git a/app/Ninja/Presenters/ExpensePresenter.php b/app/Ninja/Presenters/ExpensePresenter.php index 1980480a2f53..275d4e657b09 100644 --- a/app/Ninja/Presenters/ExpensePresenter.php +++ b/app/Ninja/Presenters/ExpensePresenter.php @@ -1,9 +1,8 @@ entity->invoice_id ? $this->entity->convertedAmount() : 0; } - - public function link() - { - return link_to('/expenses/' . $this->entity->public_id, $this->entity->name); - } -} \ No newline at end of file + +} diff --git a/app/Ninja/Presenters/InvoicePresenter.php b/app/Ninja/Presenters/InvoicePresenter.php index 36a2ce2acbd6..827b171f4208 100644 --- a/app/Ninja/Presenters/InvoicePresenter.php +++ b/app/Ninja/Presenters/InvoicePresenter.php @@ -2,9 +2,8 @@ use URL; use Utils; -use Laracasts\Presenter\Presenter; -class InvoicePresenter extends Presenter { +class InvoicePresenter extends EntityPresenter { public function client() { @@ -45,6 +44,8 @@ class InvoicePresenter extends Presenter { return trans('texts.deleted'); } elseif ($this->entity->trashed()) { return trans('texts.archived'); + } elseif ($this->entity->is_recurring) { + return trans('texts.active'); } else { $status = $this->entity->invoice_status ? $this->entity->invoice_status->name : 'draft'; $status = strtolower($status); @@ -67,19 +68,9 @@ class InvoicePresenter extends Presenter { return $this->entity->frequency ? $this->entity->frequency->name : ''; } - public function url() - { - return URL::to('/invoices/' . $this->entity->public_id); - } - - public function link() - { - return link_to('/invoices/' . $this->entity->public_id, $this->entity->invoice_number); - } - public function email() { $client = $this->entity->client; return count($client->contacts) ? $client->contacts[0]->email : ''; } -} \ No newline at end of file +} diff --git a/app/Ninja/Presenters/PaymentPresenter.php b/app/Ninja/Presenters/PaymentPresenter.php index a1c3692991fe..9b464b4c8a2a 100644 --- a/app/Ninja/Presenters/PaymentPresenter.php +++ b/app/Ninja/Presenters/PaymentPresenter.php @@ -2,9 +2,8 @@ use URL; use Utils; -use Laracasts\Presenter\Presenter; -class PaymentPresenter extends Presenter { +class PaymentPresenter extends EntityPresenter { public function client() { @@ -25,14 +24,4 @@ class PaymentPresenter extends Presenter { } } - public function url() - { - return URL::to('/payments/' . $this->entity->public_id . '/edit'); - } - - public function link() - { - return link_to('/payments/' . $this->entity->public_id . '/edit', $this->entity->getDisplayName()); - } - -} \ No newline at end of file +} diff --git a/app/Ninja/Presenters/TaskPresenter.php b/app/Ninja/Presenters/TaskPresenter.php index 367e849ca797..5e8eee222c95 100644 --- a/app/Ninja/Presenters/TaskPresenter.php +++ b/app/Ninja/Presenters/TaskPresenter.php @@ -1,9 +1,6 @@ {$text}
"; } -} \ No newline at end of file +} diff --git a/app/Ninja/Presenters/VendorPresenter.php b/app/Ninja/Presenters/VendorPresenter.php index d0bef4e0c828..2dd535cac6cd 100644 --- a/app/Ninja/Presenters/VendorPresenter.php +++ b/app/Ninja/Presenters/VendorPresenter.php @@ -1,17 +1,10 @@ entity->country ? $this->entity->country->name : ''; } - - public function link() - { - return link_to('/vendors/' . $this->entity->public_id, $this->entity->name); - } -} \ No newline at end of file + +} diff --git a/app/Ninja/Repositories/ActivityRepository.php b/app/Ninja/Repositories/ActivityRepository.php index 60474dc9580d..d0ea33981117 100644 --- a/app/Ninja/Repositories/ActivityRepository.php +++ b/app/Ninja/Repositories/ActivityRepository.php @@ -91,6 +91,7 @@ class ActivityRepository 'invoices.public_id as invoice_public_id', 'invoices.is_recurring', 'clients.name as client_name', + 'accounts.name as account_name', 'clients.public_id as client_public_id', 'contacts.id as contact', 'contacts.first_name as first_name', @@ -102,4 +103,4 @@ class ActivityRepository ); } -} \ No newline at end of file +} diff --git a/app/Ninja/Repositories/ClientRepository.php b/app/Ninja/Repositories/ClientRepository.php index 83f08cd97a59..bb56d944e6fc 100644 --- a/app/Ninja/Repositories/ClientRepository.php +++ b/app/Ninja/Repositories/ClientRepository.php @@ -25,7 +25,7 @@ class ClientRepository extends BaseRepository ->get(); } - public function find($filter = null) + public function find($filter = null, $userId = false) { $query = DB::table('clients') ->join('accounts', 'accounts.id', '=', 'clients.account_id') @@ -63,9 +63,13 @@ class ClientRepository extends BaseRepository }); } + if ($userId) { + $query->where('clients.user_id', '=', $userId); + } + return $query; } - + public function save($data, $client = null) { $publicId = isset($data['public_id']) ? $data['public_id'] : false; @@ -78,7 +82,7 @@ class ClientRepository extends BaseRepository $client = Client::scope($publicId)->with('contacts')->firstOrFail(); \Log::warning('Entity not set in client repo save'); } - + // convert currency code to id if (isset($data['currency_code'])) { $currencyCode = strtolower($data['currency_code']); @@ -98,7 +102,7 @@ class ClientRepository extends BaseRepository return $client; } */ - + $first = true; $contacts = isset($data['contact']) ? [$data['contact']] : $data['contacts']; $contactIds = []; @@ -107,7 +111,7 @@ class ClientRepository extends BaseRepository usort($contacts, function ($left, $right) { return (isset($right['is_primary']) ? $right['is_primary'] : 1) - (isset($left['is_primary']) ? $left['is_primary'] : 0); }); - + foreach ($contacts as $contact) { $contact = $client->addContact($contact, $first); $contactIds[] = $contact->public_id; diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php index 0ce0e155cf50..2d782298a2e7 100644 --- a/app/Ninja/Repositories/ExpenseRepository.php +++ b/app/Ninja/Repositories/ExpenseRepository.php @@ -36,22 +36,8 @@ class ExpenseRepository extends BaseRepository public function findVendor($vendorPublicId) { $vendorId = Vendor::getPrivateId($vendorPublicId); - $accountid = \Auth::user()->account_id; - $query = DB::table('expenses') - ->join('accounts', 'accounts.id', '=', 'expenses.account_id') - ->where('expenses.account_id', '=', $accountid) - ->where('expenses.vendor_id', '=', $vendorId) - ->select( - 'expenses.id', - 'expenses.expense_date', - 'expenses.amount', - 'expenses.public_notes', - 'expenses.public_id', - 'expenses.deleted_at', - 'expenses.should_be_invoiced', - 'expenses.created_at', - 'expenses.user_id' - ); + + $query = $this->find()->where('expenses.vendor_id', '=', $vendorId); return $query; } diff --git a/app/Services/AccountGatewayService.php b/app/Services/AccountGatewayService.php index 88ee177321ec..00e3f672e602 100644 --- a/app/Services/AccountGatewayService.php +++ b/app/Services/AccountGatewayService.php @@ -1,10 +1,9 @@ accountGatewayRepo; } - /* - public function save() - { - return null; - } - */ - public function getDatatable($accountId) { $query = $this->accountGatewayRepo->find($accountId); - return $this->createDatatable(ENTITY_ACCOUNT_GATEWAY, $query, false); + return $this->datatableService->createDatatable(new AccountGatewayDatatable(false), $query); } - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'name', - function ($model) { - if ($model->deleted_at) { - return $model->name; - } elseif ($model->gateway_id != GATEWAY_WEPAY) { - return link_to("gateways/{$model->public_id}/edit", $model->name)->toHtml(); - } else { - $accountGateway = AccountGateway::find($model->id); - $config = $accountGateway->getConfig(); - $endpoint = WEPAY_ENVIRONMENT == WEPAY_STAGE ? 'https://stage.wepay.com/' : 'https://www.wepay.com/'; - $wepayAccountId = $config->accountId; - $wepayState = isset($config->state)?$config->state:null; - $linkText = $model->name; - $url = $endpoint.'account/'.$wepayAccountId; - $wepay = \Utils::setupWepay($accountGateway); - $html = link_to($url, $linkText, array('target'=>'_blank'))->toHtml(); - - try { - if ($wepayState == 'action_required') { - $updateUri = $wepay->request('/account/get_update_uri', array( - 'account_id' => $wepayAccountId, - 'redirect_uri' => URL::to('gateways'), - )); - - $linkText .= ' ('.trans('texts.action_required').')'; - $url = $updateUri->uri; - $html = "{$linkText}"; - $model->setupUrl = $url; - } elseif ($wepayState == 'pending') { - $linkText .= ' ('.trans('texts.resend_confirmation_email').')'; - $model->resendConfirmationUrl = $url = URL::to("gateways/{$accountGateway->public_id}/resend_confirmation"); - $html = link_to($url, $linkText)->toHtml(); - } - } catch(\WePayException $ex){} - - return $html; - } - } - ], - [ - 'payment_type', - function ($model) { - return Gateway::getPrettyPaymentType($model->gateway_id); - } - ], - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - uctrans('texts.resend_confirmation_email'), - function ($model) { - return $model->resendConfirmationUrl; - }, - function($model) { - return !$model->deleted_at && $model->gateway_id == GATEWAY_WEPAY && !empty($model->resendConfirmationUrl); - } - ], [ - uctrans('texts.finish_setup'), - function ($model) { - return $model->setupUrl; - }, - function($model) { - return !$model->deleted_at && $model->gateway_id == GATEWAY_WEPAY && !empty($model->setupUrl); - } - ] , [ - uctrans('texts.edit_gateway'), - function ($model) { - return URL::to("gateways/{$model->public_id}/edit"); - }, - function($model) { - return !$model->deleted_at; - } - ], [ - uctrans('texts.manage_wepay_account'), - function ($model) { - $accountGateway = AccountGateway::find($model->id); - $endpoint = WEPAY_ENVIRONMENT == WEPAY_STAGE ? 'https://stage.wepay.com/' : 'https://www.wepay.com/'; - return array( - 'url' => $endpoint.'account/'.$accountGateway->getConfig()->accountId, - 'attributes' => 'target="_blank"' - ); - }, - function($model) { - return !$model->deleted_at && $model->gateway_id == GATEWAY_WEPAY; - } - ] - ]; - } - -} \ No newline at end of file +} diff --git a/app/Services/ActivityService.php b/app/Services/ActivityService.php index 6f6ef4b35956..6b06fce73a8b 100644 --- a/app/Services/ActivityService.php +++ b/app/Services/ActivityService.php @@ -4,6 +4,7 @@ use Utils; use App\Models\Client; use App\Services\BaseService; use App\Ninja\Repositories\ActivityRepository; +use App\Ninja\Datatables\ActivityDatatable; class ActivityService extends BaseService { @@ -22,48 +23,7 @@ class ActivityService extends BaseService $query = $this->activityRepo->findByClientId($clientId); - return $this->createDatatable(ENTITY_ACTIVITY, $query); + return $this->datatableService->createDatatable(new ActivityDatatable(false), $query); } - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'activities.id', - function ($model) { - return Utils::timestampToDateTimeString(strtotime($model->created_at)); - } - ], - [ - 'activity_type_id', - function ($model) { - $data = [ - 'client' => link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model))->toHtml(), - 'user' => $model->is_system ? '' . trans('texts.system') . '' : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), - 'invoice' => $model->invoice ? link_to('/invoices/' . $model->invoice_public_id, $model->is_recurring ? trans('texts.recurring_invoice') : $model->invoice)->toHtml() : null, - 'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice)->toHtml() : null, - 'contact' => $model->contact_id ? link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model))->toHtml() : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), - 'payment' => $model->payment ?: '', - 'credit' => $model->payment_amount ? Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) : '', - 'payment_amount' => $model->payment_amount ? Utils::formatMoney($model->payment_amount, $model->currency_id, $model->country_id) : null, - 'adjustment' => $model->adjustment ? Utils::formatMoney($model->adjustment, $model->currency_id, $model->country_id) : null - ]; - - return trans("texts.activity_{$model->activity_type_id}", $data); - } - ], - [ - 'balance', - function ($model) { - return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); - } - ], - [ - 'adjustment', - function ($model) { - return $model->adjustment != 0 ? Utils::wrapAdjustment($model->adjustment, $model->currency_id, $model->country_id) : ''; - } - ] - ]; - } -} \ No newline at end of file +} diff --git a/app/Services/BankAccountService.php b/app/Services/BankAccountService.php index 688bd866d3ad..9291a36342c3 100644 --- a/app/Services/BankAccountService.php +++ b/app/Services/BankAccountService.php @@ -11,6 +11,7 @@ use App\Services\BaseService; use App\Ninja\Repositories\BankAccountRepository; use App\Ninja\Repositories\ExpenseRepository; use App\Ninja\Repositories\VendorRepository; +use App\Ninja\Datatables\BankAccountDatatable; use App\Libraries\Finance; use App\Libraries\Login; @@ -206,7 +207,7 @@ class BankAccountService extends BaseService $vendorMap[$transaction['vendor_orig']] = $vendor; $countVendors++; } - + // create the expense record $this->expenseRepo->save([ 'vendor_id' => $vendor->id, @@ -241,36 +242,6 @@ class BankAccountService extends BaseService { $query = $this->bankAccountRepo->find($accountId); - return $this->createDatatable(ENTITY_BANK_ACCOUNT, $query, false); - } - - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'bank_name', - function ($model) { - return link_to("bank_accounts/{$model->public_id}/edit", $model->bank_name)->toHtml(); - }, - ], - [ - 'bank_library_id', - function ($model) { - return 'OFX'; - } - ], - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - uctrans('texts.edit_bank_account'), - function ($model) { - return URL::to("bank_accounts/{$model->public_id}/edit"); - }, - ] - ]; + return $this->datatableService->createDatatable(new BankAccountDatatable(false), $query); } } diff --git a/app/Services/BaseService.php b/app/Services/BaseService.php index afd6ed1ea1eb..e6dad06334bd 100644 --- a/app/Services/BaseService.php +++ b/app/Services/BaseService.php @@ -30,21 +30,4 @@ class BaseService return count($entities); } - public function createDatatable($entityType, $query, $showCheckbox = true, $hideClient = false, $orderColumns = []) - { - $columns = $this->getDatatableColumns($entityType, !$showCheckbox); - $actions = $this->getDatatableActions($entityType); - - return $this->datatableService->createDatatable($entityType, $query, $columns, $actions, $showCheckbox, $orderColumns); - } - - protected function getDatatableColumns($entityType, $hideClient) - { - return []; - } - - protected function getDatatableActions($entityType) - { - return []; - } } diff --git a/app/Services/ClientService.php b/app/Services/ClientService.php index 96357991c519..065258d05abb 100644 --- a/app/Services/ClientService.php +++ b/app/Services/ClientService.php @@ -12,6 +12,7 @@ use App\Models\Payment; use App\Models\Task; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\NinjaRepository; +use App\Ninja\Datatables\ClientDatatable; class ClientService extends BaseService { @@ -39,139 +40,13 @@ class ClientService extends BaseService return $this->clientRepo->save($data, $client); } - public function getDatatable($search) + public function getDatatable($search, $userId) { - $query = $this->clientRepo->find($search); + $datatable = new ClientDatatable(); - if(!Utils::hasPermission('view_all')){ - $query->where('clients.user_id', '=', Auth::user()->id); - } + $query = $this->clientRepo->find($search, $userId); - return $this->createDatatable(ENTITY_CLIENT, $query); + return $this->datatableService->createDatatable($datatable, $query); } - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'name', - function ($model) { - return link_to("clients/{$model->public_id}", $model->name ?: '')->toHtml(); - } - ], - [ - 'first_name', - function ($model) { - return link_to("clients/{$model->public_id}", $model->first_name.' '.$model->last_name)->toHtml(); - } - ], - [ - 'email', - function ($model) { - return link_to("clients/{$model->public_id}", $model->email ?: '')->toHtml(); - } - ], - [ - 'clients.created_at', - function ($model) { - return Utils::timestampToDateString(strtotime($model->created_at)); - } - ], - [ - 'last_login', - function ($model) { - return Utils::timestampToDateString(strtotime($model->last_login)); - } - ], - [ - 'balance', - function ($model) { - return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); - } - ] - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - trans('texts.edit_client'), - function ($model) { - return URL::to("clients/{$model->public_id}/edit"); - }, - function ($model) { - return Auth::user()->can('editByOwner', [ENTITY_CLIENT, $model->user_id]); - } - ], - [ - '--divider--', function(){return false;}, - function ($model) { - $user = Auth::user(); - return $user->can('editByOwner', [ENTITY_CLIENT, $model->user_id]) && ($user->can('create', ENTITY_TASK) || $user->can('create', ENTITY_INVOICE)); - } - ], - [ - trans('texts.new_task'), - function ($model) { - return URL::to("tasks/create/{$model->public_id}"); - }, - function ($model) { - return Auth::user()->can('create', ENTITY_TASK); - } - ], - [ - trans('texts.new_invoice'), - function ($model) { - return URL::to("invoices/create/{$model->public_id}"); - }, - function ($model) { - return Auth::user()->can('create', ENTITY_INVOICE); - } - ], - [ - trans('texts.new_quote'), - function ($model) { - return URL::to("quotes/create/{$model->public_id}"); - }, - function ($model) { - return Auth::user()->hasFeature(FEATURE_QUOTES) && Auth::user()->can('create', ENTITY_INVOICE); - } - ], - [ - '--divider--', function(){return false;}, - function ($model) { - $user = Auth::user(); - return ($user->can('create', ENTITY_TASK) || $user->can('create', ENTITY_INVOICE)) && ($user->can('create', ENTITY_PAYMENT) || $user->can('create', ENTITY_CREDIT) || $user->can('create', ENTITY_EXPENSE)); - } - ], - [ - trans('texts.enter_payment'), - function ($model) { - return URL::to("payments/create/{$model->public_id}"); - }, - function ($model) { - return Auth::user()->can('create', ENTITY_PAYMENT); - } - ], - [ - trans('texts.enter_credit'), - function ($model) { - return URL::to("credits/create/{$model->public_id}"); - }, - function ($model) { - return Auth::user()->can('create', ENTITY_CREDIT); - } - ], - [ - trans('texts.enter_expense'), - function ($model) { - return URL::to("expenses/create/0/{$model->public_id}"); - }, - function ($model) { - return Auth::user()->can('create', ENTITY_EXPENSE); - } - ] - ]; - } } diff --git a/app/Services/CreditService.php b/app/Services/CreditService.php index 54ef659f05f9..a1e1a5db40ab 100644 --- a/app/Services/CreditService.php +++ b/app/Services/CreditService.php @@ -7,7 +7,7 @@ use App\Services\BaseService; use App\Models\Client; use App\Models\Payment; use App\Ninja\Repositories\CreditRepository; - +use App\Ninja\Datatables\CreditDatatable; class CreditService extends BaseService { @@ -32,68 +32,14 @@ class CreditService extends BaseService public function getDatatable($clientPublicId, $search) { + // we don't support bulk edit and hide the client on the individual client page + $datatable = new CreditDatatable( ! $clientPublicId, $clientPublicId); $query = $this->creditRepo->find($clientPublicId, $search); - + if(!Utils::hasPermission('view_all')){ $query->where('credits.user_id', '=', Auth::user()->id); } - return $this->createDatatable(ENTITY_CREDIT, $query, !$clientPublicId); + return $this->datatableService->createDatatable($datatable, $query); } - - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'client_name', - function ($model) { - if(!Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){ - return Utils::getClientDisplayName($model); - } - - return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; - }, - ! $hideClient - ], - [ - 'amount', - function ($model) { - return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id) . ''; - } - ], - [ - 'balance', - function ($model) { - return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); - } - ], - [ - 'credit_date', - function ($model) { - return Utils::fromSqlDate($model->credit_date); - } - ], - [ - 'private_notes', - function ($model) { - return $model->private_notes; - } - ] - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - trans('texts.apply_credit'), - function ($model) { - return URL::to("payments/create/{$model->client_public_id}") . '?paymentTypeId=1'; - }, - function ($model) { - return Auth::user()->can('create', ENTITY_PAYMENT); - } - ] - ]; - } -} \ No newline at end of file +} diff --git a/app/Services/DatatableService.php b/app/Services/DatatableService.php index df4c9d93d55d..8fa49df5f7df 100644 --- a/app/Services/DatatableService.php +++ b/app/Services/DatatableService.php @@ -4,24 +4,25 @@ use HtmlString; use Utils; use Datatable; use Auth; +use App\Ninja\Datatables\EntityDatatable; class DatatableService { - public function createDatatable($entityType, $query, $columns, $actions = null, $showCheckbox = true, $orderColumns = []) + public function createDatatable(EntityDatatable $datatable, $query) { $table = Datatable::query($query); - $calculateOrderColumns = empty($orderColumns); - - if ($actions && $showCheckbox) { + //$calculateOrderColumns = empty($orderColumns); + + if ($datatable->isBulkEdit) { $table->addColumn('checkbox', function ($model) { $can_edit = Auth::user()->hasPermission('edit_all') || (isset($model->user_id) && Auth::user()->id == $model->user_id); - + return !$can_edit?'':''; }); } - foreach ($columns as $column) { + foreach ($datatable->columns() as $column) { // set visible to true by default if (count($column) == 2) { $column[] = true; @@ -31,27 +32,31 @@ class DatatableService if ($visible) { $table->addColumn($field, $value); + $orderColumns[] = $field; + /* if ($calculateOrderColumns) { $orderColumns[] = $field; } + */ } } - if ($actions) { - $this->createDropdown($entityType, $table, $actions); + if (count($datatable->actions())) { + $this->createDropdown($datatable, $table); } return $table->orderColumns($orderColumns)->make(); } - private function createDropdown($entityType, $table, $actions) + //private function createDropdown($entityType, $table, $actions) + private function createDropdown(EntityDatatable $datatable, $table) { - $table->addColumn('dropdown', function ($model) use ($entityType, $actions) { + $table->addColumn('dropdown', function ($model) use ($datatable) { $hasAction = false; $str = '
'; $can_edit = Auth::user()->hasPermission('edit_all') || (isset($model->user_id) && Auth::user()->id == $model->user_id); - + if (property_exists($model, 'is_deleted') && $model->is_deleted) { $str .= ''; } elseif ($model->deleted_at && $model->deleted_at !== '0000-00-00') { @@ -64,7 +69,7 @@ class DatatableService $lastIsDivider = false; if (!$model->deleted_at || $model->deleted_at == '0000-00-00') { - foreach ($actions as $action) { + foreach ($datatable->actions() as $action) { if (count($action)) { if (count($action) == 2) { $action[] = function() { @@ -104,20 +109,20 @@ class DatatableService $dropdown_contents .= "
  • "; } - if (($entityType != ENTITY_USER || $model->public_id) && $can_edit) { + if (($datatable->entityType != ENTITY_USER || $model->public_id) && $can_edit) { $dropdown_contents .= "
  • public_id})\">" - . trans("texts.archive_{$entityType}") . "
  • "; + . trans("texts.archive_{$datatable->entityType}") . ""; } } else if($can_edit) { - if ($entityType != ENTITY_ACCOUNT_GATEWAY || Auth::user()->account->canAddGateway(\App\Models\Gateway::getPaymentType($model->gateway_id))) { + if ($datatable->entityType != ENTITY_ACCOUNT_GATEWAY || Auth::user()->account->canAddGateway(\App\Models\Gateway::getPaymentType($model->gateway_id))) { $dropdown_contents .= "
  • public_id})\">" - . trans("texts.restore_{$entityType}") . "
  • "; + . trans("texts.restore_{$datatable->entityType}") . ""; } } if (property_exists($model, 'is_deleted') && !$model->is_deleted && $can_edit) { $dropdown_contents .= "
  • public_id})\">" - . trans("texts.delete_{$entityType}") . "
  • "; + . trans("texts.delete_{$datatable->entityType}") . ""; } if (!empty($dropdown_contents)) { @@ -132,5 +137,4 @@ class DatatableService return $str.'
    '; }); } - -} \ No newline at end of file +} diff --git a/app/Services/ExpenseService.php b/app/Services/ExpenseService.php index 671648ea32a5..a16bff7dd3f1 100644 --- a/app/Services/ExpenseService.php +++ b/app/Services/ExpenseService.php @@ -10,6 +10,7 @@ use App\Models\Expense; use App\Models\Invoice; use App\Models\Client; use App\Models\Vendor; +use App\Ninja\Datatables\ExpenseDatatable; class ExpenseService extends BaseService { @@ -49,89 +50,22 @@ class ExpenseService extends BaseService $query->where('expenses.user_id', '=', Auth::user()->id); } - return $this->createDatatable(ENTITY_EXPENSE, $query); + return $this->datatableService->createDatatable(new ExpenseDatatable(), $query); } public function getDatatableVendor($vendorPublicId) { + $datatable = new ExpenseDatatable(false, true); + $query = $this->expenseRepo->findVendor($vendorPublicId); - return $this->datatableService->createDatatable(ENTITY_EXPENSE, - $query, - $this->getDatatableColumnsVendor(ENTITY_EXPENSE,false), - $this->getDatatableActionsVendor(ENTITY_EXPENSE), - false); + + if(!Utils::hasPermission('view_all')){ + $query->where('expenses.user_id', '=', Auth::user()->id); + } + + return $this->datatableService->createDatatable($datatable, $query); } - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'vendor_name', - function ($model) - { - if ($model->vendor_public_id) { - if(!Auth::user()->can('viewByOwner', [ENTITY_VENDOR, $model->vendor_user_id])){ - return $model->vendor_name; - } - - return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name)->toHtml(); - } else { - return ''; - } - } - ], - [ - 'client_name', - function ($model) - { - if ($model->client_public_id) { - if(!Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){ - return Utils::getClientDisplayName($model); - } - - return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); - } else { - return ''; - } - } - ], - [ - 'expense_date', - function ($model) { - if(!Auth::user()->can('editByOwner', [ENTITY_EXPENSE, $model->user_id])){ - return Utils::fromSqlDate($model->expense_date); - } - - return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date))->toHtml(); - } - ], - [ - 'amount', - function ($model) { - // show both the amount and the converted amount - if ($model->exchange_rate != 1) { - $converted = round($model->amount * $model->exchange_rate, 2); - return Utils::formatMoney($model->amount, $model->expense_currency_id) . ' | ' . - Utils::formatMoney($converted, $model->invoice_currency_id); - } else { - return Utils::formatMoney($model->amount, $model->expense_currency_id); - } - } - ], - [ - 'public_notes', - function ($model) { - return $model->public_notes != null ? substr($model->public_notes, 0, 100) : ''; - } - ], - [ - 'expense_status_id', - function ($model) { - return self::getStatusLabel($model->invoice_id, $model->should_be_invoiced); - } - ], - ]; - } protected function getDatatableColumnsVendor($entityType, $hideClient) { @@ -163,58 +97,9 @@ class ExpenseService extends BaseService ]; } - protected function getDatatableActions($entityType) - { - return [ - [ - trans('texts.edit_expense'), - function ($model) { - return URL::to("expenses/{$model->public_id}/edit") ; - }, - function ($model) { - return Auth::user()->can('editByOwner', [ENTITY_EXPENSE, $model->user_id]); - } - ], - [ - trans('texts.view_invoice'), - function ($model) { - return URL::to("/invoices/{$model->invoice_public_id}/edit"); - }, - function ($model) { - return $model->invoice_public_id && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->invoice_user_id]); - } - ], - [ - trans('texts.invoice_expense'), - function ($model) { - return "javascript:invoiceEntity({$model->public_id})"; - }, - function ($model) { - return ! $model->invoice_id && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Auth::user()->can('create', ENTITY_INVOICE); - } - ], - ]; - } - protected function getDatatableActionsVendor($entityType) { return []; } - private function getStatusLabel($invoiceId, $shouldBeInvoiced) - { - if ($invoiceId) { - $label = trans('texts.invoiced'); - $class = 'success'; - } elseif ($shouldBeInvoiced) { - $label = trans('texts.pending'); - $class = 'warning'; - } else { - $label = trans('texts.logged'); - $class = 'primary'; - } - - return "

    $label

    "; - } - } diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index edbee8caf84c..950c8bb95fcb 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -11,6 +11,7 @@ use App\Models\Invitation; use App\Models\Invoice; use App\Models\Client; use App\Models\Payment; +use App\Ninja\Datatables\InvoiceDatatable; class InvoiceService extends BaseService { @@ -34,12 +35,12 @@ class InvoiceService extends BaseService { if (isset($data['client'])) { $canSaveClient = false; - $clientPublicId = array_get($data, 'client.public_id') ?: array_get($data, 'client.id'); + $clientPublicId = array_get($data, 'client.public_id') ?: array_get($data, 'client.id'); if (empty($clientPublicId) || $clientPublicId == '-1') { $canSaveClient = Auth::user()->can('create', ENTITY_CLIENT); } else { $canSaveClient = Auth::user()->can('edit', Client::scope($clientPublicId)->first()); - } + } if ($canSaveClient) { $client = $this->clientRepo->save($data['client']); $data['client_id'] = $client->id; @@ -92,7 +93,7 @@ class InvoiceService extends BaseService public function approveQuote($quote, $invitation = null) { $account = $quote->account; - + if (!$quote->is_quote || $quote->quote_invoice_id) { return null; } @@ -118,189 +119,15 @@ class InvoiceService extends BaseService public function getDatatable($accountId, $clientPublicId = null, $entityType, $search) { + $datatable = new InvoiceDatatable( ! $clientPublicId, $clientPublicId); $query = $this->invoiceRepo->getInvoices($accountId, $clientPublicId, $entityType, $search) ->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE ? true : false); if(!Utils::hasPermission('view_all')){ $query->where('invoices.user_id', '=', Auth::user()->id); } - - return $this->createDatatable($entityType, $query, !$clientPublicId); - } - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'invoice_number', - function ($model) use ($entityType) { - if(!Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id])){ - return $model->invoice_number; - } - - return link_to("{$entityType}s/{$model->public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml(); - } - ], - [ - 'client_name', - function ($model) { - if(!Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){ - return Utils::getClientDisplayName($model); - } - return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); - }, - ! $hideClient - ], - [ - 'invoice_date', - function ($model) { - return Utils::fromSqlDate($model->invoice_date); - } - ], - [ - 'amount', - function ($model) { - return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); - } - ], - [ - 'balance', - function ($model) { - return $model->partial > 0 ? - trans('texts.partial_remaining', [ - 'partial' => Utils::formatMoney($model->partial, $model->currency_id, $model->country_id), - 'balance' => Utils::formatMoney($model->balance, $model->currency_id, $model->country_id)] - ) : - Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); - }, - $entityType == ENTITY_INVOICE - ], - [ - 'due_date', - function ($model) { - return Utils::fromSqlDate($model->due_date); - }, - ], - [ - 'invoice_status_name', - function ($model) use ($entityType) { - return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted'))->toHtml() : self::getStatusLabel($entityType, $model); - } - ] - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - trans("texts.edit_{$entityType}"), - function ($model) use ($entityType) { - return URL::to("{$entityType}s/{$model->public_id}/edit"); - }, - function ($model) { - return Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); - } - ], - [ - trans("texts.clone_{$entityType}"), - function ($model) use ($entityType) { - return URL::to("{$entityType}s/{$model->public_id}/clone"); - }, - function ($model) { - return Auth::user()->can('create', ENTITY_INVOICE); - } - ], - [ - trans("texts.view_history"), - function ($model) use ($entityType) { - return URL::to("{$entityType}s/{$entityType}_history/{$model->public_id}"); - } - ], - [ - '--divider--', function(){return false;}, - function ($model) { - return Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]) || Auth::user()->can('create', ENTITY_PAYMENT); - } - ], - [ - trans("texts.mark_sent"), - function ($model) { - return "javascript:markEntity({$model->public_id})"; - }, - function ($model) { - return $model->invoice_status_id < INVOICE_STATUS_SENT && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); - } - ], - [ - trans('texts.enter_payment'), - function ($model) { - return URL::to("payments/create/{$model->client_public_id}/{$model->public_id}"); - }, - function ($model) use ($entityType) { - return $entityType == ENTITY_INVOICE && $model->balance > 0 && Auth::user()->can('create', ENTITY_PAYMENT); - } - ], - [ - trans("texts.view_quote"), - function ($model) { - return URL::to("quotes/{$model->quote_id}/edit"); - }, - function ($model) use ($entityType) { - return $entityType == ENTITY_INVOICE && $model->quote_id && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); - } - ], - [ - trans("texts.view_invoice"), - function ($model) { - return URL::to("invoices/{$model->quote_invoice_id}/edit"); - }, - function ($model) use ($entityType) { - return $entityType == ENTITY_QUOTE && $model->quote_invoice_id && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); - } - ], - [ - trans("texts.convert_to_invoice"), - function ($model) { - return "javascript:convertEntity({$model->public_id})"; - }, - function ($model) use ($entityType) { - return $entityType == ENTITY_QUOTE && ! $model->quote_invoice_id && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); - } - ] - ]; - } - - private function getStatusLabel($entityType, $model) - { - // 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 "

    " . $label . "

    "; - } - } - - $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; - } - return "

    $label

    "; + return $this->datatableService->createDatatable($datatable, $query); } } diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 51cbccc57177..b227b466413c 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -23,18 +23,13 @@ use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\AccountRepository; use App\Services\BaseService; use App\Events\PaymentWasCreated; +use App\Ninja\Datatables\PaymentDatatable; class PaymentService extends BaseService { public $lastError; protected $datatableService; - protected static $refundableGateways = array( - GATEWAY_STRIPE, - GATEWAY_BRAINTREE, - GATEWAY_WEPAY, - ); - public function __construct(PaymentRepository $paymentRepo, AccountRepository $accountRepo, DatatableService $datatableService) { $this->datatableService = $datatableService; @@ -524,7 +519,7 @@ class PaymentService extends BaseService return $paymentMethod; } - + public function convertPaymentMethodFromGatewayResponse($gatewayResponse, $accountGateway, $accountGatewayToken = null, $contactId = null, $existingPaymentMethod = null) { if ($accountGateway->gateway_id == GATEWAY_STRIPE) { $data = $gatewayResponse->getData(); @@ -642,7 +637,7 @@ class PaymentService extends BaseService $payment->contact_id = $invitation->contact_id; $payment->transaction_reference = $ref; $payment->payment_date = date_create()->format('Y-m-d'); - + if (!empty($paymentDetails['card'])) { $card = $paymentDetails['card']; $payment->last4 = $card->getNumberLastFour(); @@ -707,10 +702,10 @@ class PaymentService extends BaseService $pending_monthly = true; } } - - if (!empty($plan)) { + + if (!empty($plan)) { $account = Account::with('users')->find($invoice->client->public_id); - + if( $account->company->plan != $plan || DateTime::createFromFormat('Y-m-d', $account->company->plan_expires) >= date_create('-7 days') @@ -719,10 +714,10 @@ class PaymentService extends BaseService // Reset any grandfathering $account->company->plan_started = date_create()->format('Y-m-d'); } - + if ( $account->company->plan == $plan - && $account->company->plan_term == $term + && $account->company->plan_term == $term && DateTime::createFromFormat('Y-m-d', $account->company->plan_expires) >= date_create() ) { // This is a renewal; mark it paid as of when this term expires @@ -730,13 +725,13 @@ class PaymentService extends BaseService } else { $account->company->plan_paid = date_create()->format('Y-m-d'); } - + $account->company->payment_id = $payment->id; $account->company->plan = $plan; $account->company->plan_term = $term; $account->company->plan_expires = DateTime::createFromFormat('Y-m-d', $account->company->plan_paid) ->modify($term == PLAN_TERM_MONTHLY ? '+1 month' : '+1 year')->format('Y-m-d'); - + if (!empty($pending_monthly)) { $account->company->pending_plan = $plan; $account->company->pending_term = PLAN_TERM_MONTHLY; @@ -744,7 +739,7 @@ class PaymentService extends BaseService $account->company->pending_plan = null; $account->company->pending_term = null; } - + $account->company->save(); } } @@ -783,7 +778,7 @@ class PaymentService extends BaseService return PAYMENT_TYPE_CREDIT_CARD_OTHER; } } - + private function detectCardType($number) { if (preg_match('/^3[47][0-9]{13}$/',$number)) { @@ -856,127 +851,17 @@ class PaymentService extends BaseService public function getDatatable($clientPublicId, $search) { + $datatable = new PaymentDatatable( ! $clientPublicId, $clientPublicId); $query = $this->paymentRepo->find($clientPublicId, $search); if(!Utils::hasPermission('view_all')){ $query->where('payments.user_id', '=', Auth::user()->id); } - return $this->createDatatable(ENTITY_PAYMENT, $query, !$clientPublicId, false, - ['invoice_number', 'transaction_reference', 'payment_type', 'amount', 'payment_date']); + return $this->datatableService->createDatatable($datatable, $query); } - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'invoice_number', - function ($model) { - if(!Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->invoice_user_id])){ - return $model->invoice_number; - } - - return link_to("invoices/{$model->invoice_public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml(); - } - ], - [ - 'client_name', - function ($model) { - if(!Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){ - return Utils::getClientDisplayName($model); - } - - return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; - }, - ! $hideClient - ], - [ - 'transaction_reference', - function ($model) { - return $model->transaction_reference ? $model->transaction_reference : 'Manual entry'; - } - ], - [ - 'payment_type', - function ($model) { - return ($model->payment_type && !$model->last4) ? $model->payment_type : ($model->account_gateway_id ? $model->gateway_name : ''); - } - ], - [ - 'source', - function ($model) { - $code = str_replace(' ', '', strtolower($model->payment_type)); - $card_type = trans("texts.card_" . $code); - if ($model->payment_type_id != PAYMENT_TYPE_ACH) { - if($model->last4) { - $expiration = trans('texts.card_expiration', array('expires' => Utils::fromSqlDate($model->expiration, false)->format('m/y'))); - return '' . htmlentities($card_type) . '  •••' . $model->last4 . ' ' . $expiration; - } elseif ($model->email) { - return $model->email; - } - } elseif ($model->last4) { - $bankData = PaymentMethod::lookupBankData($model->routing_number); - if (is_object($bankData)) { - return $bankData->name.'  •••' . $model->last4; - } elseif($model->last4) { - return '' . htmlentities($card_type) . '  •••' . $model->last4; - } - } - } - ], - [ - 'amount', - function ($model) { - return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); - } - ], - [ - 'payment_date', - function ($model) { - return Utils::dateToString($model->payment_date); - } - ], - [ - 'payment_status_name', - function ($model) use ($entityType) { - return self::getStatusLabel($entityType, $model); - } - ] - ]; - } - protected function getDatatableActions($entityType) - { - return [ - [ - trans('texts.edit_payment'), - function ($model) { - return URL::to("payments/{$model->public_id}/edit"); - }, - function ($model) { - return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id]); - } - ], - [ - trans('texts.refund_payment'), - function ($model) { - $max_refund = number_format($model->amount - $model->refunded, 2); - $formatted = Utils::formatMoney($max_refund, $model->currency_id, $model->country_id); - $symbol = Utils::getFromCache($model->currency_id ? $model->currency_id : 1, 'currencies')->symbol ; - return "javascript:showRefundModal({$model->public_id}, '{$max_refund}', '{$formatted}', '{$symbol}')"; - }, - 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->payment_type_id == PAYMENT_TYPE_CREDIT - ); - } - ] - ]; - } - public function bulk($ids, $action, $params = array()) { if ($action == 'refund') { @@ -1001,50 +886,22 @@ class PaymentService extends BaseService return parent::bulk($ids, $action); } } - - private function getStatusLabel($entityType, $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; - } - return "

    $label

    "; - } - + public function refund($payment, $amount = null) { if ($amount) { $amount = min($amount, $payment->amount - $payment->refunded); } $accountGateway = $payment->account_gateway; - + if (!$accountGateway) { $accountGateway = AccountGateway::withTrashed()->find($payment->account_gateway_id); } - + if (!$amount || !$accountGateway) { return; } - + if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) { $gateway = $this->createGateway($accountGateway); diff --git a/app/Services/PaymentTermService.php b/app/Services/PaymentTermService.php index 1371d20c2c43..08e2a84caf4b 100644 --- a/app/Services/PaymentTermService.php +++ b/app/Services/PaymentTermService.php @@ -25,10 +25,10 @@ class PaymentTermService extends BaseService { $query = $this->paymentTermRepo->find(); - return $this->createDatatable(ENTITY_PAYMENT_TERM, $query, false); + return $this->datatableService->createDatatable(ENTITY_PAYMENT_TERM, $query, false); } - protected function getDatatableColumns($entityType, $hideClient) + public function columns($entityType, $hideClient) { return [ [ @@ -46,7 +46,7 @@ class PaymentTermService extends BaseService ]; } - protected function getDatatableActions($entityType) + public function actions($entityType) { return [ [ @@ -57,4 +57,4 @@ class PaymentTermService extends BaseService ] ]; } -} \ No newline at end of file +} diff --git a/app/Services/ProductService.php b/app/Services/ProductService.php index f8ec6e1131d5..fc7fc6cceed4 100644 --- a/app/Services/ProductService.php +++ b/app/Services/ProductService.php @@ -1,12 +1,12 @@ productRepo->find($accountId); - return $this->createDatatable(ENTITY_PRODUCT, $query, false); + return $this->datatableService->createDatatable($datatable, $query); } - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'product_key', - function ($model) { - return link_to('products/'.$model->public_id.'/edit', $model->product_key)->toHtml(); - } - ], - [ - 'notes', - function ($model) { - return nl2br(Str::limit($model->notes, 100)); - } - ], - [ - 'cost', - function ($model) { - return Utils::formatMoney($model->cost); - } - ], - [ - 'tax_rate', - function ($model) { - return $model->tax_rate ? ($model->tax_name . ' ' . $model->tax_rate . '%') : ''; - }, - Auth::user()->account->invoice_item_taxes - ] - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - uctrans('texts.edit_product'), - function ($model) { - return URL::to("products/{$model->public_id}/edit"); - } - ] - ]; - } - -} \ No newline at end of file +} diff --git a/app/Services/RecurringInvoiceService.php b/app/Services/RecurringInvoiceService.php index b003455abd6a..bc6daa1f0178 100644 --- a/app/Services/RecurringInvoiceService.php +++ b/app/Services/RecurringInvoiceService.php @@ -5,6 +5,7 @@ use Auth; use Utils; use App\Models\Invoice; use App\Ninja\Repositories\InvoiceRepository; +use App\Ninja\Datatables\RecurringInvoiceDatatable; class RecurringInvoiceService extends BaseService { @@ -19,64 +20,14 @@ class RecurringInvoiceService extends BaseService public function getDatatable($accountId, $clientPublicId = null, $entityType, $search) { + $datatable = new RecurringInvoiceDatatable( ! $clientPublicId, $clientPublicId); $query = $this->invoiceRepo->getRecurringInvoices($accountId, $clientPublicId, $search); if(!Utils::hasPermission('view_all')){ $query->where('invoices.user_id', '=', Auth::user()->id); } - - return $this->createDatatable(ENTITY_RECURRING_INVOICE, $query, !$clientPublicId); + + return $this->datatableService->createDatatable($datatable, $query); } - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'frequency', - function ($model) { - return link_to("invoices/{$model->public_id}", $model->frequency)->toHtml(); - } - ], - [ - 'client_name', - function ($model) { - return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); - }, - ! $hideClient - ], - [ - 'start_date', - function ($model) { - return Utils::fromSqlDate($model->start_date); - } - ], - [ - 'end_date', - function ($model) { - return Utils::fromSqlDate($model->end_date); - } - ], - [ - 'amount', - function ($model) { - return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); - } - ] - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - trans('texts.edit_invoice'), - function ($model) { - return URL::to("invoices/{$model->public_id}/edit"); - }, - function ($model) { - return Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); - } - ] - ]; - } -} \ No newline at end of file +} diff --git a/app/Services/TaskService.php b/app/Services/TaskService.php index e07793b2f85c..bfb25708d0d5 100644 --- a/app/Services/TaskService.php +++ b/app/Services/TaskService.php @@ -8,6 +8,7 @@ use App\Models\Invoice; use App\Models\Client; use App\Ninja\Repositories\TaskRepository; use App\Services\BaseService; +use App\Ninja\Datatables\TaskDatatable; class TaskService extends BaseService { @@ -34,112 +35,14 @@ class TaskService extends BaseService public function getDatatable($clientPublicId, $search) { + $datatable = new TaskDatatable( ! $clientPublicId, $clientPublicId); $query = $this->taskRepo->find($clientPublicId, $search); if(!Utils::hasPermission('view_all')){ $query->where('tasks.user_id', '=', Auth::user()->id); } - return $this->createDatatable(ENTITY_TASK, $query, !$clientPublicId); + return $this->datatableService->createDatatable($datatable, $query); } - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'client_name', - function ($model) { - if(!Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){ - return Utils::getClientDisplayName($model); - } - - return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; - }, - ! $hideClient - ], - [ - 'created_at', - function ($model) { - return link_to("tasks/{$model->public_id}/edit", Task::calcStartTime($model))->toHtml(); - } - ], - [ - 'time_log', - function($model) { - return Utils::formatTime(Task::calcDuration($model)); - } - ], - [ - 'description', - function ($model) { - return $model->description; - } - ], - [ - 'invoice_number', - function ($model) { - return self::getStatusLabel($model); - } - ] - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - trans('texts.edit_task'), - function ($model) { - return URL::to('tasks/'.$model->public_id.'/edit'); - }, - function ($model) { - return (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Auth::user()->can('editByOwner', [ENTITY_TASK, $model->user_id]); - } - ], - [ - trans('texts.view_invoice'), - function ($model) { - return URL::to("/invoices/{$model->invoice_public_id}/edit"); - }, - function ($model) { - return $model->invoice_number && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->invoice_user_id]); - } - ], - [ - trans('texts.stop_task'), - function ($model) { - return "javascript:stopTask({$model->public_id})"; - }, - function ($model) { - return $model->is_running && Auth::user()->can('editByOwner', [ENTITY_TASK, $model->user_id]); - } - ], - [ - trans('texts.invoice_task'), - function ($model) { - return "javascript:invoiceEntity({$model->public_id})"; - }, - function ($model) { - return ! $model->invoice_number && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Auth::user()->can('create', ENTITY_INVOICE); - } - ] - ]; - } - - private function getStatusLabel($model) - { - if ($model->invoice_number) { - $class = 'success'; - $label = trans('texts.invoiced'); - } elseif ($model->is_running) { - $class = 'primary'; - $label = trans('texts.running'); - } else { - $class = 'default'; - $label = trans('texts.logged'); - } - - return "

    $label

    "; - } - -} \ No newline at end of file +} diff --git a/app/Services/TaxRateService.php b/app/Services/TaxRateService.php index db5b041395f0..330477149462 100644 --- a/app/Services/TaxRateService.php +++ b/app/Services/TaxRateService.php @@ -4,6 +4,7 @@ use URL; use Auth; use App\Services\BaseService; use App\Ninja\Repositories\TaxRateRepository; +use App\Ninja\Datatables\TaxRateDatatable; class TaxRateService extends BaseService { @@ -21,48 +22,12 @@ class TaxRateService extends BaseService return $this->taxRateRepo; } - /* - public function save() - { - return null; - } - */ - public function getDatatable($accountId) { + $datatable = new TaxRateDatatable(false); $query = $this->taxRateRepo->find($accountId); - return $this->createDatatable(ENTITY_TAX_RATE, $query, false); + return $this->datatableService->createDatatable($datatable, $query); } - - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'name', - function ($model) { - return link_to("tax_rates/{$model->public_id}/edit", $model->name)->toHtml(); - } - ], - [ - 'rate', - function ($model) { - return $model->rate . '%'; - } - ] - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - uctrans('texts.edit_tax_rate'), - function ($model) { - return URL::to("tax_rates/{$model->public_id}/edit"); - } - ] - ]; - } - -} \ No newline at end of file + +} diff --git a/app/Services/TokenService.php b/app/Services/TokenService.php index 092f3995d3d7..5d5b29e3de6c 100644 --- a/app/Services/TokenService.php +++ b/app/Services/TokenService.php @@ -3,6 +3,7 @@ use URL; use App\Services\BaseService; use App\Ninja\Repositories\TokenRepository; +use App\Ninja\Datatables\TokenDatatable; class TokenService extends BaseService { @@ -20,48 +21,12 @@ class TokenService extends BaseService return $this->tokenRepo; } - /* - public function save() - { - return null; - } - */ - public function getDatatable($userId) { + $datatable = new TokenDatatable(false); $query = $this->tokenRepo->find($userId); - return $this->createDatatable(ENTITY_TOKEN, $query, false); + return $this->datatableService->createDatatable($datatable, $query); } - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'name', - function ($model) { - return link_to("tokens/{$model->public_id}/edit", $model->name)->toHtml(); - } - ], - [ - 'token', - function ($model) { - return $model->token; - } - ] - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - uctrans('texts.edit_token'), - function ($model) { - return URL::to("tokens/{$model->public_id}/edit"); - } - ] - ]; - } - -} \ No newline at end of file +} diff --git a/app/Services/UserService.php b/app/Services/UserService.php index 8e31b4c30d2d..3aa7a60c8851 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -3,6 +3,7 @@ use URL; use App\Services\BaseService; use App\Ninja\Repositories\UserRepository; +use App\Ninja\Datatables\UserDatatable; class UserService extends BaseService { @@ -20,102 +21,12 @@ class UserService extends BaseService return $this->userRepo; } - /* - public function save() - { - return null; - } - */ - public function getDatatable($accountId) { + $datatable = new UserDatatable(false); $query = $this->userRepo->find($accountId); - return $this->createDatatable(ENTITY_USER, $query, false); + return $this->datatableService->createDatatable($datatable, $query); } - protected function getDatatableColumns($entityType, $hideClient) - { - return [ - [ - 'first_name', - function ($model) { - return $model->public_id ? link_to('users/'.$model->public_id.'/edit', $model->first_name.' '.$model->last_name)->toHtml() : ($model->first_name.' '.$model->last_name); - } - ], - [ - 'email', - function ($model) { - return $model->email; - } - ], - [ - 'confirmed', - function ($model) { - if (!$model->public_id) { - return self::getStatusLabel(USER_STATE_OWNER); - } elseif ($model->deleted_at) { - return self::getStatusLabel(USER_STATE_DISABLED); - } elseif ($model->confirmed) { - if($model->is_admin){ - return self::getStatusLabel(USER_STATE_ADMIN); - } else { - return self::getStatusLabel(USER_STATE_ACTIVE); - } - } else { - return self::getStatusLabel(USER_STATE_PENDING); - } - } - ], - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - uctrans('texts.edit_user'), - function ($model) { - return URL::to("users/{$model->public_id}/edit"); - }, - function ($model) { - return $model->public_id; - } - ], - [ - uctrans('texts.send_invite'), - function ($model) { - return URL::to("send_confirmation/{$model->public_id}"); - }, - function ($model) { - return $model->public_id && ! $model->confirmed; - } - ] - ]; - } - - private function getStatusLabel($state) - { - $label = trans("texts.{$state}"); - $class = 'default'; - switch ($state) { - case USER_STATE_PENDING: - $class = 'default'; - break; - case USER_STATE_ACTIVE: - $class = 'info'; - break; - case USER_STATE_DISABLED: - $class = 'warning'; - break; - case USER_STATE_OWNER: - $class = 'success'; - break; - case USER_STATE_ADMIN: - $class = 'primary'; - break; - } - return "

    $label

    "; - } - -} \ No newline at end of file +} diff --git a/app/Services/VendorService.php b/app/Services/VendorService.php index 41f5fd4664bb..0940be420cb4 100644 --- a/app/Services/VendorService.php +++ b/app/Services/VendorService.php @@ -8,6 +8,7 @@ use App\Models\Expense; use App\Services\BaseService; use App\Ninja\Repositories\VendorRepository; use App\Ninja\Repositories\NinjaRepository; +use App\Ninja\Datatables\VendorDatatable; class VendorService extends BaseService { @@ -37,79 +38,14 @@ class VendorService extends BaseService public function getDatatable($search) { + $datatable = new VendorDatatable(); $query = $this->vendorRepo->find($search); - + if(!Utils::hasPermission('view_all')){ $query->where('vendors.user_id', '=', Auth::user()->id); } - return $this->createDatatable(ENTITY_VENDOR, $query); + return $this->datatableService->createDatatable($datatable, $query); } - protected function getDatatableColumns($entityType, $hideVendor) - { - return [ - [ - 'name', - function ($model) { - return link_to("vendors/{$model->public_id}", $model->name ?: '')->toHtml(); - } - ], - [ - 'city', - function ($model) { - return $model->city; - } - ], - [ - 'work_phone', - function ($model) { - return $model->work_phone; - } - ], - [ - 'email', - function ($model) { - return link_to("vendors/{$model->public_id}", $model->email ?: '')->toHtml(); - } - ], - [ - 'vendors.created_at', - function ($model) { - return Utils::timestampToDateString(strtotime($model->created_at)); - } - ], - ]; - } - - protected function getDatatableActions($entityType) - { - return [ - [ - trans('texts.edit_vendor'), - function ($model) { - return URL::to("vendors/{$model->public_id}/edit"); - }, - function ($model) { - return Auth::user()->can('editByOwner', [ENTITY_VENDOR, $model->user_id]); - } - ], - [ - '--divider--', function(){return false;}, - function ($model) { - return Auth::user()->can('editByOwner', [ENTITY_VENDOR, $model->user_id]) && Auth::user()->can('create', ENTITY_EXPENSE); - } - - ], - [ - trans('texts.enter_expense'), - function ($model) { - return URL::to("expenses/create/{$model->public_id}"); - }, - function ($model) { - return Auth::user()->can('create', ENTITY_EXPENSE); - } - ] - ]; - } } diff --git a/database/seeds/DateFormatsSeeder.php b/database/seeds/DateFormatsSeeder.php index 40568ca3000a..c55028b6840c 100644 --- a/database/seeds/DateFormatsSeeder.php +++ b/database/seeds/DateFormatsSeeder.php @@ -11,22 +11,24 @@ class DateFormatsSeeder extends Seeder // Date formats $formats = [ - ['format' => 'd/M/Y', 'picker_format' => 'dd/M/yyyy', 'label' => '10/Mar/2013'], - ['format' => 'd-M-Y', 'picker_format' => 'dd-M-yyyy', 'label' => '10-Mar-2013'], - ['format' => 'd/F/Y', 'picker_format' => 'dd/MM/yyyy', 'label' => '10/March/2013'], - ['format' => 'd-F-Y', 'picker_format' => 'dd-MM-yyyy', 'label' => '10-March-2013'], - ['format' => 'M j, Y', 'picker_format' => 'M d, yyyy', 'label' => 'Mar 10, 2013'], - ['format' => 'F j, Y', 'picker_format' => 'MM d, yyyy', 'label' => 'March 10, 2013'], - ['format' => 'D M j, Y', 'picker_format' => 'D MM d, yyyy', 'label' => 'Mon March 10, 2013'], - ['format' => 'Y-m-d', 'picker_format' => 'yyyy-mm-dd', 'label' => '2013-03-10'], - ['format' => 'd-m-Y', 'picker_format' => 'dd-mm-yyyy', 'label' => '20-03-2013'], - ['format' => 'm/d/Y', 'picker_format' => 'mm/dd/yyyy', 'label' => '03/20/2013'] + ['format' => 'd/M/Y', 'picker_format' => 'dd/M/yyyy'], + ['format' => 'd-M-Y', 'picker_format' => 'dd-M-yyyy'], + ['format' => 'd/F/Y', 'picker_format' => 'dd/MM/yyyy'], + ['format' => 'd-F-Y', 'picker_format' => 'dd-MM-yyyy'], + ['format' => 'M j, Y', 'picker_format' => 'M d, yyyy'], + ['format' => 'F j, Y', 'picker_format' => 'MM d, yyyy'], + ['format' => 'D M j, Y', 'picker_format' => 'D MM d, yyyy'], + ['format' => 'Y-m-d', 'picker_format' => 'yyyy-mm-dd'], + ['format' => 'd-m-Y', 'picker_format' => 'dd-mm-yyyy'], + ['format' => 'm/d/Y', 'picker_format' => 'mm/dd/yyyy'], + ['format' => 'd.m.Y', 'picker_format' => 'dd.mm.yyyy'], + ['format' => 'j. M. Y', 'picker_format' => 'd. M. yyyy'], + ['format' => 'j. F Y', 'picker_format' => 'd. MM yyyy'] ]; - + foreach ($formats as $format) { - $record = DateFormat::whereLabel($format['label'])->first(); + $record = DateFormat::whereFormat($format['format'])->first(); if ($record) { - $record->format = $format['format']; $record->picker_format = $format['picker_format']; $record->save(); } else { @@ -36,62 +38,24 @@ class DateFormatsSeeder extends Seeder // Date/time formats $formats = [ - [ - 'format' => 'd/M/Y g:i a', - 'format_moment' => 'DD/MMM/YYYY h:mm:ss a', - 'label' => '10/Mar/2013' - ], - [ - 'format' => 'd-M-Y g:i a', - 'format_moment' => 'DD-MMM-YYYY h:mm:ss a', - 'label' => '10-Mar-2013' - ], - [ - 'format' => 'd/F/Y g:i a', - 'format_moment' => 'DD/MMMM/YYYY h:mm:ss a', - 'label' => '10/March/2013' - ], - [ - 'format' => 'd-F-Y g:i a', - 'format_moment' => 'DD-MMMM-YYYY h:mm:ss a', - 'label' => '10-March-2013' - ], - [ - 'format' => 'M j, Y g:i a', - 'format_moment' => 'MMM D, YYYY h:mm:ss a', - 'label' => 'Mar 10, 2013 6:15 pm' - ], - [ - 'format' => 'F j, Y g:i a', - 'format_moment' => 'MMMM D, YYYY h:mm:ss a', - 'label' => 'March 10, 2013 6:15 pm' - ], - [ - 'format' => 'D M jS, Y g:i a', - 'format_moment' => 'ddd MMM Do, YYYY h:mm:ss a', - 'label' => 'Mon March 10th, 2013 6:15 pm' - ], - [ - 'format' => 'Y-m-d g:i a', - 'format_moment' => 'YYYY-MMM-DD h:mm:ss a', - 'label' => '2013-03-10 6:15 pm' - ], - [ - 'format' => 'd-m-Y g:i a', - 'format_moment' => 'DD-MM-YYYY h:mm:ss a', - 'label' => '20-03-2013 6:15 pm' - ], - [ - 'format' => 'm/d/Y g:i a', - 'format_moment' => 'MM/DD/YYYY h:mm:ss a', - 'label' => '03/20/2013 6:15 pm' - ] + ['format' => 'd/M/Y g:i a', 'format_moment' => 'DD/MMM/YYYY h:mm:ss a'], + ['format' => 'd-M-Y g:i a', 'format_moment' => 'DD-MMM-YYYY h:mm:ss a'], + ['format' => 'd/F/Y g:i a', 'format_moment' => 'DD/MMMM/YYYY h:mm:ss a'], + ['format' => 'd-F-Y g:i a', 'format_moment' => 'DD-MMMM-YYYY h:mm:ss a'], + ['format' => 'M j, Y g:i a', 'format_moment' => 'MMM D, YYYY h:mm:ss a'], + ['format' => 'F j, Y g:i a', 'format_moment' => 'MMMM D, YYYY h:mm:ss a'], + ['format' => 'D M jS, Y g:i a', 'format_moment' => 'ddd MMM Do, YYYY h:mm:ss a'], + ['format' => 'Y-m-d g:i a', 'format_moment' => 'YYYY-MMM-DD h:mm:ss a'], + ['format' => 'd-m-Y g:i a', 'format_moment' => 'DD-MM-YYYY h:mm:ss a'], + ['format' => 'm/d/Y g:i a', 'format_moment' => 'MM/DD/YYYY h:mm:ss a'], + ['format' => 'd.m.Y g:i a', 'format_moment' => 'D.MM.YYYY h:mm:ss a'], + ['format' => 'j. M. Y g:i a', 'format_moment' => 'DD. MMM. YYYY h:mm:ss a'], + ['format' => 'j. F Y g:i a', 'format_moment' => 'DD. MMMM YYYY h:mm:ss a'] ]; - + foreach ($formats as $format) { - $record = DatetimeFormat::whereLabel($format['label'])->first(); + $record = DatetimeFormat::whereFormat($format['format'])->first(); if ($record) { - $record->format = $format['format']; $record->format_moment = $format['format_moment']; $record->save(); } else { diff --git a/database/seeds/UserTableSeeder.php b/database/seeds/UserTableSeeder.php index dbaba660b75c..00a081cb5412 100644 --- a/database/seeds/UserTableSeeder.php +++ b/database/seeds/UserTableSeeder.php @@ -20,7 +20,7 @@ class UserTableSeeder extends Seeder $faker = Faker\Factory::create(); $company = Company::create(); - + $account = Account::create([ 'name' => $faker->name, 'address1' => $faker->streetAddress, @@ -28,12 +28,12 @@ class UserTableSeeder extends Seeder 'city' => $faker->city, 'state' => $faker->state, 'postal_code' => $faker->postcode, - 'country_id' => Country::all()->random()->id, + 'country_id' => Country::all()->random()->id, 'account_key' => str_random(RANDOM_KEY_LENGTH), 'invoice_terms' => $faker->text($faker->numberBetween(50, 300)), 'work_phone' => $faker->phoneNumber, 'work_email' => $faker->safeEmail, - 'invoice_design_id' => min(InvoiceDesign::all()->random()->id, 10), + 'invoice_design_id' => InvoiceDesign::where('id', '<', CUSTOM_DESIGN)->get()->random()->id, 'header_font_id' => min(Font::all()->random()->id, 17), 'body_font_id' => min(Font::all()->random()->id, 17), 'primary_color' => $faker->hexcolor, @@ -55,7 +55,7 @@ class UserTableSeeder extends Seeder Affiliate::create([ 'affiliate_key' => SELF_HOST_AFFILIATE_KEY ]); - + } -} \ No newline at end of file +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index f73e936f5fb5..2b9f2e5f4aec 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1305,6 +1305,12 @@ $LANG = array( 'canada' => 'Canada', 'accept_debit_cards' => 'Accept Debit Cards', 'debit_cards' => 'Debit Cards', + + 'warn_start_date_changed' => 'The next invoice will be sent on the new start date.', + 'original_start_date' => 'Original start date', + 'new_start_date' => 'New start date', + 'security' => 'Security', + 'see_whats_new' => 'See what\'s new in v:version', ); return $LANG; diff --git a/resources/views/accounts/client_portal.blade.php b/resources/views/accounts/client_portal.blade.php index 1df146123fa3..14883901831c 100644 --- a/resources/views/accounts/client_portal.blade.php +++ b/resources/views/accounts/client_portal.blade.php @@ -30,9 +30,10 @@
    +
    -

    {!! trans('texts.client_portal') !!}

    +

    {!! trans('texts.navigation') !!}

    @@ -45,20 +46,29 @@ ->text(trans('texts.enable')) ->help(trans('texts.enable_client_portal_dashboard_help')) !!}
    +
    +
    + +
    +
    +

    {!! trans('texts.security') !!}

    +
    +
    {!! Former::checkbox('enable_portal_password') - ->text(trans('texts.enable_portal_password')) + ->text(trans('texts.enable')) ->help(trans('texts.enable_portal_password_help')) - ->label(' ') !!} + ->label(trans('texts.enable_portal_password')) !!}
    {!! Former::checkbox('send_portal_password') - ->text(trans('texts.send_portal_password')) + ->text(trans('texts.enable')) ->help(trans('texts.send_portal_password_help')) - ->label(' ') !!} + ->label(trans('texts.send_portal_password')) !!}
    + @if (Utils::hasFeature(FEATURE_CLIENT_PORTAL_CSS))
    diff --git a/resources/views/accounts/localization.blade.php b/resources/views/accounts/localization.blade.php index b921c8710567..ccca9f3c4e20 100644 --- a/resources/views/accounts/localization.blade.php +++ b/resources/views/accounts/localization.blade.php @@ -1,6 +1,6 @@ @extends('header') -@section('content') +@section('content') @parent {!! Former::open_for_files()->addClass('warn-on-exit') !!} @@ -11,7 +11,7 @@ @include('accounts.nav', ['selected' => ACCOUNT_LOCALIZATION])
    - +
    @@ -21,15 +21,15 @@
    {!! Former::select('currency_id')->addOption('','') - ->fromQuery($currencies, 'name', 'id') !!} + ->fromQuery($currencies, 'name', 'id') !!} {!! Former::select('language_id')->addOption('','') - ->fromQuery($languages, 'name', 'id') !!} + ->fromQuery($languages, 'name', 'id') !!} {!! Former::select('timezone_id')->addOption('','') ->fromQuery($timezones, 'location', 'id') !!} {!! Former::select('date_format_id')->addOption('','') - ->fromQuery($dateFormats, 'label', 'id') !!} + ->fromQuery($dateFormats) !!} {!! Former::select('datetime_format_id')->addOption('','') - ->fromQuery($datetimeFormats, 'label', 'id') !!} + ->fromQuery($datetimeFormats) !!} {!! Former::checkbox('military_time')->text(trans('texts.enable')) !!} {{-- Former::checkbox('show_currency_code')->text(trans('texts.enable')) --}} @@ -48,4 +48,4 @@ @section('onReady') $('#currency_id').focus(); -@stop \ No newline at end of file +@stop diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index b6eb65f763c1..41af3f1e801e 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -364,6 +364,12 @@ @if (Auth::user()->account->hasFeature(FEATURE_DOCUMENTS)) function handleDocumentAdded(file){ + // open document when clicked + if (file.url) { + file.previewElement.addEventListener("click", function() { + window.open(file.url, '_blank'); + }); + } if(file.mock)return; file.index = model.documents().length; model.addDocument({name:file.name, size:file.size, type:file.type}); diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 9deb5322452a..3ca6acafba59 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -527,7 +527,9 @@ {!! Former::select('invoice_design_id')->style('display:inline;width:150px;background-color:white !important')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id") !!} @endif - {!! Button::primary(trans('texts.download_pdf'))->withAttributes(array('onclick' => 'onDownloadClick()'))->appendIcon(Icon::create('download-alt')) !!} + @if ( ! $invoice->is_recurring) + {!! Button::primary(trans('texts.download_pdf'))->withAttributes(array('onclick' => 'onDownloadClick()'))->appendIcon(Icon::create('download-alt')) !!} + @endif @if ($invoice->isClientTrashed()) @@ -807,6 +809,7 @@ var invoice = {!! $invoice !!}; ko.mapping.fromJS(invoice, model.invoice().mapping, model.invoice); model.invoice().is_recurring({{ $invoice->is_recurring ? '1' : '0' }}); + model.invoice().start_date_orig(model.invoice().start_date()); @if ($invoice->id) var invitationContactIds = {!! json_encode($invitationContactIds) !!}; @@ -1182,22 +1185,41 @@ if (!isEmailValid()) { alert("{!! trans('texts.provide_email') !!}"); return; - } +8 } if (confirm('{!! trans("texts.confirm_email_$entityType") !!}' + '\n\n' + getSendToEmails())) { - preparePdfData('email'); + var accountLanguageId = parseInt({{ $account->language_id ?: '0' }}); + var clientLanguageId = parseInt(model.invoice().client().language_id()) || 0; + // if the client's language is different then we can't use the browser version of the PDF + if (clientLanguageId && clientLanguageId != accountLanguageId) { + submitAction('email'); + } else { + preparePdfData('email'); + } } } function onSaveClick() { - if (model.invoice().is_recurring() && {{ $invoice ? 'false' : 'true' }}) { - if (confirm("{!! trans("texts.confirm_recurring_email_$entityType") !!}" + '\n\n' + getSendToEmails() + '\n' + "{!! trans("texts.confirm_recurring_timing") !!}")) { - submitAction(''); - } - } else { - preparePdfData(''); - } - } + if (model.invoice().is_recurring()) { + // warn invoice will be emailed when saving new recurring invoice + if ({{ $invoice->exists() ? 'false' : 'true' }}) { + if (confirm("{!! trans("texts.confirm_recurring_email_$entityType") !!}" + '\n\n' + getSendToEmails() + '\n' + "{!! trans("texts.confirm_recurring_timing") !!}")) { + submitAction(''); + } + return; + // warn invoice will be emailed again if start date is changed + } else if (model.invoice().start_date() != model.invoice().start_date_orig()) { + if (confirm("{!! trans("texts.warn_start_date_changed") !!}" + '\n\n' + + "{!! trans("texts.original_start_date") !!}: " + model.invoice().start_date_orig() + '\n' + + "{!! trans("texts.new_start_date") !!}: " + model.invoice().start_date())) { + submitAction(''); + } + return; + } + } + + preparePdfData(''); + } function getSendToEmails() { var client = model.invoice().client(); @@ -1403,6 +1425,12 @@ @if ($account->hasFeature(FEATURE_DOCUMENTS)) function handleDocumentAdded(file){ + // open document when clicked + if (file.url) { + file.previewElement.addEventListener("click", function() { + window.open(file.url, '_blank'); + }); + } if(file.mock)return; file.index = model.invoice().documents().length; model.invoice().addDocument({name:file.name, size:file.size, type:file.type}); diff --git a/resources/views/invoices/knockout.blade.php b/resources/views/invoices/knockout.blade.php index f92055b5df34..ab2e892c25f2 100644 --- a/resources/views/invoices/knockout.blade.php +++ b/resources/views/invoices/knockout.blade.php @@ -55,7 +55,7 @@ function ViewModel(data) { if (self.invoice().tax_name1() || self.invoice().tax_name2()) { return true; } - + return self.invoice_taxes() && {{ count($taxRateOptions) ? 'true' : 'false' }}; }); @@ -100,11 +100,11 @@ function ViewModel(data) { $('input.client-email').each(function(item, value) { var $email = $(value); var email = $(value).val(); - + // Trim whitespace email = (email || '').trim(); $email.val(email); - + if (!firstName && (!email || !isValidEmailAddress(email))) { isValid = false; } @@ -180,6 +180,7 @@ function InvoiceModel(data) { self.due_date = ko.observable(''); self.recurring_due_date = ko.observable(''); self.start_date = ko.observable(''); + self.start_date_orig = ko.observable(''); self.end_date = ko.observable(''); self.last_sent_date = ko.observable(''); self.tax_name1 = ko.observable(); @@ -240,13 +241,13 @@ function InvoiceModel(data) { applyComboboxListeners(); return itemModel; } - + self.addDocument = function() { var documentModel = new DocumentModel(); self.documents.push(documentModel); return documentModel; } - + self.removeDocument = function(doc) { var public_id = doc.public_id?doc.public_id():doc; self.documents.remove(function(document) { @@ -291,7 +292,7 @@ function InvoiceModel(data) { self.tax_rate2(rate); } }) - + self.wrapped_terms = ko.computed({ read: function() { return this.terms(); @@ -386,7 +387,7 @@ function InvoiceModel(data) { var taxRate2 = parseFloat(self.tax_rate2()); var tax2 = roundToTwo(total * (taxRate2/100)); - + return self.formatMoney(tax1 + tax2); }); @@ -403,7 +404,7 @@ function InvoiceModel(data) { lineTotal -= roundToTwo(lineTotal * (self.discount()/100)); } } - + var taxAmount = roundToTwo(lineTotal * item.tax_rate1() / 100); if (taxAmount) { var key = item.tax_name1() + item.tax_rate1(); @@ -664,12 +665,12 @@ function ContactModel(data) { return str; }); - + self.info_color = ko.computed(function() { if (self.invitation_viewed()) { return '#57D172'; } else if (self.invitation_openend()) { - return '#FFCC00'; + return '#FFCC00'; } else { return '#B1B5BA'; } @@ -780,7 +781,7 @@ function ItemModel(data) { this.onSelect = function() {} } - + function DocumentModel(data) { var self = this; self.public_id = ko.observable(0); @@ -788,16 +789,16 @@ function DocumentModel(data) { self.name = ko.observable(''); self.type = ko.observable(''); self.url = ko.observable(''); - + self.update = function(data){ ko.mapping.fromJS(data, {}, this); } - + if (data) { self.update(data); - } + } } - + var ExpenseModel = function(data) { var self = this; @@ -808,7 +809,7 @@ var ExpenseModel = function(data) { } } } - + self.description = ko.observable(''); self.qty = ko.observable(0); self.public_id = ko.observable(0); @@ -825,7 +826,7 @@ ko.bindingHandlers.typeahead = { init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var $element = $(element); var allBindings = allBindingsAccessor(); - + $element.typeahead({ highlight: true, minLength: 0, @@ -875,4 +876,4 @@ ko.bindingHandlers.typeahead = { } }; - \ No newline at end of file + diff --git a/resources/views/vendors/show.blade.php b/resources/views/vendors/show.blade.php index 37572ee4c5bc..eca43f233662 100644 --- a/resources/views/vendors/show.blade.php +++ b/resources/views/vendors/show.blade.php @@ -152,7 +152,7 @@ trans('texts.expense_date'), trans('texts.amount'), trans('texts.public_notes')) - ->setUrl(url('api/expenseVendor/' . $vendor->public_id)) + ->setUrl(url('api/vendor_expense/' . $vendor->public_id)) ->setCustomValues('entityType', 'expenses') ->setOptions('sPaginationType', 'bootstrap') ->setOptions('bFilter', false)