diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 50535beb84..5947273710 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -13,12 +13,10 @@ use View; use stdClass; use Cache; use Response; -use parseCSV; use Request; use App\Models\Affiliate; use App\Models\License; use App\Models\User; -use App\Models\Client; use App\Models\Contact; use App\Models\Invoice; use App\Models\InvoiceItem; @@ -38,7 +36,6 @@ use App\Models\Industry; use App\Models\InvoiceDesign; use App\Models\TaxRate; use App\Ninja\Repositories\AccountRepository; -use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ReferralRepository; use App\Ninja\Mailers\UserMailer; use App\Ninja\Mailers\ContactMailer; @@ -56,7 +53,7 @@ class AccountController extends BaseController protected $contactMailer; protected $referralRepository; - public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository, ClientRepository $clientRepository) + public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository) { parent::__construct(); @@ -64,7 +61,6 @@ class AccountController extends BaseController $this->userMailer = $userMailer; $this->contactMailer = $contactMailer; $this->referralRepository = $referralRepository; - $this->clientRepository = $clientRepository; } public function demo() @@ -413,10 +409,6 @@ class AccountController extends BaseController return AccountController::saveUserDetails(); } elseif ($section === ACCOUNT_LOCALIZATION) { return AccountController::saveLocalization(); - } elseif ($section === ACCOUNT_IMPORT_EXPORT) { - return AccountController::importFile(); - } elseif ($section === ACCOUNT_MAP) { - return AccountController::mapFile(); } elseif ($section === ACCOUNT_NOTIFICATIONS) { return AccountController::saveNotifications(); } elseif ($section === ACCOUNT_EXPORT) { @@ -625,216 +617,6 @@ class AccountController extends BaseController return Redirect::to('settings/' . ACCOUNT_INVOICE_DESIGN); } - private function export() - { - $output = fopen('php://output', 'w') or Utils::fatalError(); - header('Content-Type:application/csv'); - header('Content-Disposition:attachment;filename=export.csv'); - - $clients = Client::scope()->get(); - Utils::exportData($output, $clients->toArray()); - - $contacts = Contact::scope()->get(); - Utils::exportData($output, $contacts->toArray()); - - $invoices = Invoice::scope()->get(); - Utils::exportData($output, $invoices->toArray()); - - $invoiceItems = InvoiceItem::scope()->get(); - Utils::exportData($output, $invoiceItems->toArray()); - - $payments = Payment::scope()->get(); - Utils::exportData($output, $payments->toArray()); - - $credits = Credit::scope()->get(); - Utils::exportData($output, $credits->toArray()); - - fclose($output); - exit; - } - - private function importFile() - { - $data = Session::get('data'); - Session::forget('data'); - - $map = Input::get('map'); - $count = 0; - $hasHeaders = Input::get('header_checkbox'); - - $countries = Cache::get('countries'); - $countryMap = []; - - foreach ($countries as $country) { - $countryMap[strtolower($country->name)] = $country->id; - } - - foreach ($data as $row) { - if ($hasHeaders) { - $hasHeaders = false; - continue; - } - - $data = [ - 'contacts' => [[]] - ]; - - foreach ($row as $index => $value) { - $field = $map[$index]; - if ( ! $value = trim($value)) { - continue; - } - - if ($field == Client::$fieldName) { - $data['name'] = $value; - } elseif ($field == Client::$fieldPhone) { - $data['work_phone'] = $value; - } elseif ($field == Client::$fieldAddress1) { - $data['address1'] = $value; - } elseif ($field == Client::$fieldAddress2) { - $data['address2'] = $value; - } elseif ($field == Client::$fieldCity) { - $data['city'] = $value; - } elseif ($field == Client::$fieldState) { - $data['state'] = $value; - } elseif ($field == Client::$fieldPostalCode) { - $data['postal_code'] = $value; - } elseif ($field == Client::$fieldCountry) { - $value = strtolower($value); - $data['country_id'] = isset($countryMap[$value]) ? $countryMap[$value] : null; - } elseif ($field == Client::$fieldNotes) { - $data['private_notes'] = $value; - } elseif ($field == Contact::$fieldFirstName) { - $data['contacts'][0]['first_name'] = $value; - } elseif ($field == Contact::$fieldLastName) { - $data['contacts'][0]['last_name'] = $value; - } elseif ($field == Contact::$fieldPhone) { - $data['contacts'][0]['phone'] = $value; - } elseif ($field == Contact::$fieldEmail) { - $data['contacts'][0]['email'] = strtolower($value); - } - } - - $rules = [ - 'contacts' => 'valid_contacts', - ]; - $validator = Validator::make($data, $rules); - if ($validator->fails()) { - continue; - } - - $this->clientRepository->save($data); - $count++; - } - - $message = Utils::pluralize('created_client', $count); - Session::flash('message', $message); - - return Redirect::to('clients'); - } - - private function mapFile() - { - $file = Input::file('file'); - - if ($file == null) { - Session::flash('error', trans('texts.select_file')); - - return Redirect::to('settings/' . ACCOUNT_IMPORT_EXPORT); - } - - $name = $file->getRealPath(); - - require_once app_path().'/Includes/parsecsv.lib.php'; - $csv = new parseCSV(); - $csv->heading = false; - $csv->auto($name); - - if (count($csv->data) + Client::scope()->count() > Auth::user()->getMaxNumClients()) { - $message = trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()]); - Session::flash('error', $message); - - return Redirect::to('settings/' . ACCOUNT_IMPORT_EXPORT); - } - - Session::put('data', $csv->data); - - $headers = false; - $hasHeaders = false; - $mapped = array(); - $columns = array('', - Client::$fieldName, - Client::$fieldPhone, - Client::$fieldAddress1, - Client::$fieldAddress2, - Client::$fieldCity, - Client::$fieldState, - Client::$fieldPostalCode, - Client::$fieldCountry, - Client::$fieldNotes, - Contact::$fieldFirstName, - Contact::$fieldLastName, - Contact::$fieldPhone, - Contact::$fieldEmail, - ); - - if (count($csv->data) > 0) { - $headers = $csv->data[0]; - foreach ($headers as $title) { - if (strpos(strtolower($title), 'name') > 0) { - $hasHeaders = true; - break; - } - } - - for ($i = 0; $i Contact::$fieldFirstName, - 'last' => Contact::$fieldLastName, - 'email' => Contact::$fieldEmail, - 'mobile' => Contact::$fieldPhone, - 'phone' => Client::$fieldPhone, - 'name|organization' => Client::$fieldName, - 'street|address|address1' => Client::$fieldAddress1, - 'street2|address2' => Client::$fieldAddress2, - 'city' => Client::$fieldCity, - 'state|province' => Client::$fieldState, - 'zip|postal|code' => Client::$fieldPostalCode, - 'country' => Client::$fieldCountry, - 'note' => Client::$fieldNotes, - ); - - foreach ($map as $search => $column) { - foreach (explode("|", $search) as $string) { - if (strpos($title, 'sec') === 0) { - continue; - } - - if (strpos($title, $string) !== false) { - $mapped[$i] = $column; - break(2); - } - } - } - } - } - } - - $data = array( - 'data' => $csv->data, - 'headers' => $headers, - 'hasHeaders' => $hasHeaders, - 'columns' => $columns, - 'mapped' => $mapped, - ); - - return View::make('accounts.import_map', $data); - } - private function saveNotifications() { $user = Auth::user(); diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index fd8eb46a28..7573e72e70 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -1,5 +1,7 @@ getRealPath(); + $data = $this->importService->mapFile($filename); + + return View::make('accounts.import_map', $data); + } else { $files = []; foreach (ImportService::$entityTypes as $entityType) { if (Input::file("{$entityType}_file")) { $files[$entityType] = Input::file("{$entityType}_file")->getRealPath(); } } - $imported_files = $this->importService->import(Input::get('source'), $files); - Session::flash('message', trans('texts.imported_file').' - '.$imported_files); + + try { + $result = $this->importService->import($source, $files); + Session::flash('message', trans('texts.imported_file') . ' - ' . $result); + } catch (Exception $exception) { + Session::flash('error', $exception->getMessage()); + } + + return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT); + } + } + + public function doImportCSV() + { + $map = Input::get('map'); + $hasHeaders = Input::get('header_checkbox'); + + try { + $count = $this->importService->importCSV($map, $hasHeaders); + $message = Utils::pluralize('created_client', $count); + + Session::flash('message', $message); } catch (Exception $exception) { Session::flash('error', $exception->getMessage()); } - return Redirect::to('/settings/'.ACCOUNT_IMPORT_EXPORT); + return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT); } } diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index ada578daf0..b5f92d9a0e 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -111,7 +111,8 @@ class InvoiceApiController extends Controller if (!$client) { $validator = Validator::make(['email'=>$email], ['email' => 'email']); if ($validator->fails()) { - return $validator->message(); + $messages = $validator->messages(); + return $messages->first(); } $clientData = ['contact' => ['email' => $email]]; diff --git a/app/Http/routes.php b/app/Http/routes.php index b6f4c0efba..729f292c99 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -134,6 +134,7 @@ Route::group(['middleware' => 'auth'], function() { Route::post('/export', 'ExportController@doExport'); Route::post('/import', 'ImportController@doImport'); + Route::post('/import_csv', 'ImportController@doImportCSV'); Route::resource('gateways', 'AccountGatewayController'); Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable')); diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php index 8ee0887bae..ebab4b8780 100644 --- a/app/Services/ImportService.php +++ b/app/Services/ImportService.php @@ -4,12 +4,17 @@ use Excel; use Cache; use Exception; use Auth; +use Utils; +use parsecsv; +use Session; +use Validator; use League\Fractal\Manager; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Serializers\ArraySerializer; use App\Models\Client; +use App\Models\Contact; class ImportService { @@ -73,6 +78,11 @@ class ImportService $reader->each(function($row) use ($source, $entityType, $transformer, $maps) { if ($resource = $transformer->transform($row, $maps)) { $data = $this->fractal->createData($resource)->toArray(); + + if ($this->validate($data, $entityType) !== true) { + return; + } + $entity = $this->{"{$entityType}Repo"}->save($data); // if the invoice is paid we'll also create a payment record @@ -91,6 +101,33 @@ class ImportService }); } + // looking for a better solution... + // http://stackoverflow.com/questions/33781567/how-can-i-re-use-the-validation-code-in-my-laravel-formrequest-classes + private function validate($data, $entityType) + { + if ($entityType === ENTITY_CLIENT) { + $rules = [ + 'contacts' => 'valid_contacts', + ]; + } if ($entityType === ENTITY_INVOICE) { + $rules = [ + 'client.contacts' => 'valid_contacts', + 'invoice_items' => 'valid_invoice_items', + 'invoice_number' => 'required|unique:invoices,invoice_number,,id,account_id,'.Auth::user()->account_id, + 'discount' => 'positive', + ]; + } + + $validator = Validator::make($data, $rules); + + if ($validator->fails()) { + $messages = $validator->messages(); + return $messages->first(); + } else { + return true; + } + } + private function createMaps() { $clientMap = []; @@ -122,4 +159,168 @@ class ImportService { return 'App\\Ninja\\Import\\' . $source . '\\' . ucwords($entityType) . 'Transformer'; } + + public function mapFile($filename) + { + require_once app_path().'/Includes/parsecsv.lib.php'; + $csv = new parseCSV(); + $csv->heading = false; + $csv->auto($filename); + + if (count($csv->data) + Client::scope()->count() > Auth::user()->getMaxNumClients()) { + throw new Exception(trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()])); + } + + Session::put('data', $csv->data); + + $headers = false; + $hasHeaders = false; + $mapped = array(); + $columns = array('', + Client::$fieldName, + Client::$fieldPhone, + Client::$fieldAddress1, + Client::$fieldAddress2, + Client::$fieldCity, + Client::$fieldState, + Client::$fieldPostalCode, + Client::$fieldCountry, + Client::$fieldNotes, + Contact::$fieldFirstName, + Contact::$fieldLastName, + Contact::$fieldPhone, + Contact::$fieldEmail, + ); + + if (count($csv->data) > 0) { + $headers = $csv->data[0]; + foreach ($headers as $title) { + if (strpos(strtolower($title), 'name') > 0) { + $hasHeaders = true; + break; + } + } + + for ($i = 0; $i Contact::$fieldFirstName, + 'last' => Contact::$fieldLastName, + 'email' => Contact::$fieldEmail, + 'mobile' => Contact::$fieldPhone, + 'phone' => Client::$fieldPhone, + 'name|organization' => Client::$fieldName, + 'street|address|address1' => Client::$fieldAddress1, + 'street2|address2' => Client::$fieldAddress2, + 'city' => Client::$fieldCity, + 'state|province' => Client::$fieldState, + 'zip|postal|code' => Client::$fieldPostalCode, + 'country' => Client::$fieldCountry, + 'note' => Client::$fieldNotes, + ); + + foreach ($map as $search => $column) { + foreach (explode("|", $search) as $string) { + if (strpos($title, 'sec') === 0) { + continue; + } + + if (strpos($title, $string) !== false) { + $mapped[$i] = $column; + break(2); + } + } + } + } + } + } + + $data = array( + 'data' => $csv->data, + 'headers' => $headers, + 'hasHeaders' => $hasHeaders, + 'columns' => $columns, + 'mapped' => $mapped, + ); + + return $data; + } + + public function importCSV($map, $hasHeaders) + { + $count = 0; + $data = Session::get('data'); + $countries = Cache::get('countries'); + $countryMap = []; + + foreach ($countries as $country) { + $countryMap[strtolower($country->name)] = $country->id; + } + + foreach ($data as $row) { + if ($hasHeaders) { + $hasHeaders = false; + continue; + } + + $data = [ + 'contacts' => [[]] + ]; + + foreach ($row as $index => $value) { + $field = $map[$index]; + if ( ! $value = trim($value)) { + continue; + } + + if ($field == Client::$fieldName) { + $data['name'] = $value; + } elseif ($field == Client::$fieldPhone) { + $data['work_phone'] = $value; + } elseif ($field == Client::$fieldAddress1) { + $data['address1'] = $value; + } elseif ($field == Client::$fieldAddress2) { + $data['address2'] = $value; + } elseif ($field == Client::$fieldCity) { + $data['city'] = $value; + } elseif ($field == Client::$fieldState) { + $data['state'] = $value; + } elseif ($field == Client::$fieldPostalCode) { + $data['postal_code'] = $value; + } elseif ($field == Client::$fieldCountry) { + $value = strtolower($value); + $data['country_id'] = isset($countryMap[$value]) ? $countryMap[$value] : null; + } elseif ($field == Client::$fieldNotes) { + $data['private_notes'] = $value; + } elseif ($field == Contact::$fieldFirstName) { + $data['contacts'][0]['first_name'] = $value; + } elseif ($field == Contact::$fieldLastName) { + $data['contacts'][0]['last_name'] = $value; + } elseif ($field == Contact::$fieldPhone) { + $data['contacts'][0]['phone'] = $value; + } elseif ($field == Contact::$fieldEmail) { + $data['contacts'][0]['email'] = strtolower($value); + } + } + + $rules = [ + 'contacts' => 'valid_contacts', + ]; + $validator = Validator::make($data, $rules); + if ($validator->fails()) { + continue; + } + + $this->clientRepo->save($data); + $count++; + } + + Session::forget('data'); + + return $count; + } + } diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 53da5a9fea..a5db12a5ec 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -74,7 +74,7 @@ return array( "notmasked" => "The values are masked", "less_than" => "The :attribute must be less than :value", "has_counter" => "The value must contain {\$counter}", - "valid_contacts" => "All of the contacts must have either an email or name", + "valid_contacts" => "The contact must have either an email or name", "valid_invoice_items" => "The invoice exceeds the maximum amount", /* diff --git a/resources/views/accounts/import_export.blade.php b/resources/views/accounts/import_export.blade.php index 88638288f4..cab0b31b9d 100644 --- a/resources/views/accounts/import_export.blade.php +++ b/resources/views/accounts/import_export.blade.php @@ -4,7 +4,7 @@ @parent