diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index 7da808209b..53c25ed5d7 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -22,6 +22,7 @@ class ImportController extends BaseController { $source = Input::get('source'); $files = []; + $skipped = []; foreach (ImportService::$entityTypes as $entityType) { if (Input::file("{$entityType}_file")) { @@ -34,8 +35,16 @@ class ImportController extends BaseController $data = $this->importService->mapCSV($files); return View::make('accounts.import_map', ['data' => $data]); } else { - $result = $this->importService->import($source, $files); - Session::flash('message', trans('texts.imported_file') . ' - ' . $result); + $skipped = $this->importService->import($source, $files); + if (count($skipped)) { + $message = trans('texts.failed_to_import'); + foreach ($skipped as $skip) { + $message .= '
' . json_encode($skip); + } + Session::flash('warning', $message); + } else { + Session::flash('message', trans('texts.imported_file')); + } } } catch (Exception $exception) { Session::flash('error', $exception->getMessage()); @@ -48,15 +57,23 @@ class ImportController extends BaseController { $map = Input::get('map'); $headers = Input::get('headers'); + $skipped = []; - //try { - $count = $this->importService->importCSV($map, $headers); - $message = Utils::pluralize('created_client', $count); + try { + $skipped = $this->importService->importCSV($map, $headers); - Session::flash('message', $message); - //} catch (Exception $exception) { - // Session::flash('error', $exception->getMessage()); - //} + if (count($skipped)) { + $message = trans('texts.failed_to_import'); + foreach ($skipped as $skip) { + $message .= '
' . json_encode($skip); + } + Session::flash('warning', $message); + } else { + Session::flash('message', trans('texts.imported_file')); + } + } catch (Exception $exception) { + Session::flash('error', $exception->getMessage()); + } return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT); } diff --git a/app/Models/Client.php b/app/Models/Client.php index ee0e80fc89..9399d2d91c 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -71,19 +71,18 @@ class Client extends EntityModel public static function getImportMap() { return [ - 'first' => 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, + 'first' => 'first_name', + 'last' => 'last_name', + 'email' => 'email', + 'mobile|phone' => 'phone', + 'name|organization' => 'name', + 'street2|address2' => 'address2', + 'street|address|address1' => 'address1', + 'city' => 'city', + 'state|province' => 'state', + 'zip|postal|code' => 'postal_code', + 'country' => 'country', + 'note' => 'notes', ]; } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index dda8eaa2cc..f2c67b81c4 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -65,11 +65,11 @@ class Invoice extends EntityModel implements BalanceAffecting public static function getImportMap() { return [ - 'number' => Invoice::$fieldInvoiceNumber, - 'amount' => Invoice::$fieldAmount, + 'number^po' => 'invoice_number', + 'amount' => 'amount', 'organization' => 'name', - 'paid' => 'paid', - 'invoice_date' => Invoice::$fieldInvoiceDate, + 'paid^date' => 'paid', + 'invoice_date|create_date' => 'invoice_date', 'terms' => 'terms', 'notes' => 'notes', ]; diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php index cf105ae0bb..796c9815de 100644 --- a/app/Services/ImportService.php +++ b/app/Services/ImportService.php @@ -59,46 +59,24 @@ class ImportService } } - private function checkClientCount($count) - { - $totalClients = $count + Client::scope()->withTrashed()->count(); - if ($totalClients > Auth::user()->getMaxNumClients()) { - throw new Exception(trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()])); - } - } - private function execute($source, $entityType, $file) { - Excel::load($file, function ($reader) use ($source, $entityType) { + $skipped = []; + Excel::load($file, function ($reader) use ($source, $entityType, $skipped) { $this->checkData($entityType, count($reader->all())); $maps = $this->createMaps(); $reader->each(function ($row) use ($source, $entityType, $maps) { - $this->saveData($source, $entityType, $row, $maps); + $result = $this->saveData($source, $entityType, $row, $maps); + + if ( ! $result) { + $skipped[] = $row; + } }); }); - } - private function executeCSV($entityType, $map, $hasHeaders) - { - $source = IMPORT_CSV; - - $data = Session::get("{$entityType}-data"); - $this->checkData($entityType, count($data)); - $maps = $this->createMaps(); - - foreach ($data as $row) { - if ($hasHeaders) { - $hasHeaders = false; - continue; - } - - $row = $this->convertToObject($entityType, $row, $map); - $this->saveData($source, $entityType, $row, $maps); - } - - Session::forget("{$entityType}-data"); + return $skipped; } private function saveData($source, $entityType, $row, $maps) @@ -112,6 +90,7 @@ class ImportService $data = $this->fractal->createData($resource)->toArray(); + // if the invoice number is blank we'll assign it if ($entityType == ENTITY_INVOICE && !$data['invoice_number']) { $account = Auth::user()->account; $invoice = Invoice::createNew(); @@ -123,7 +102,7 @@ class ImportService } $entity = $this->{"{$entityType}Repo"}->save($data); - + // if the invoice is paid we'll also create a payment record if ($entityType === ENTITY_INVOICE && isset($row->paid) && $row->paid) { $this->createPayment($source, $row, $maps, $data['client_id'], $entity->public_id); @@ -137,6 +116,14 @@ class ImportService } } + private function checkClientCount($count) + { + $totalClients = $count + Client::scope()->withTrashed()->count(); + if ($totalClients > Auth::user()->getMaxNumClients()) { + throw new Exception(trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()])); + } + } + public static function getTransformerClassName($source, $entityType) { return 'App\\Ninja\\Import\\'.$source.'\\'.ucwords($entityType).'Transformer'; @@ -162,15 +149,14 @@ 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) { + } + if ($entityType === ENTITY_INVOICE) { $rules = [ 'client.contacts' => 'valid_contacts', 'invoice_items' => 'valid_invoice_items', @@ -279,13 +265,9 @@ class ImportService if ($hasHeaders) { foreach ($map as $search => $column) { - foreach (explode("|", $search) as $string) { - if (strpos($title, 'sec') === 0) { - continue; - } elseif (strpos($title, $string) !== false) { - $mapped[$i] = $column; - break(2); - } + if ($this->checkForMatch($title, $search)) { + $mapped[$i] = $column; + break; } } } @@ -304,11 +286,77 @@ class ImportService return $data; } + private function checkForMatch($column, $pattern) + { + if (strpos($column, 'sec') === 0) { + return false; + } + + if (strpos($pattern, '^')) { + list($include, $exclude) = explode('^', $pattern); + $includes = explode('|', $include); + $excludes = explode('|', $exclude); + } else { + $includes = explode('|', $pattern); + $excludes = []; + } + + foreach ($includes as $string) { + if (strpos($column, $string) !== false) { + $excluded = false; + foreach ($excludes as $exclude) { + if (strpos($column, $exclude) !== false) { + $excluded = true; + break; + } + } + if (!$excluded) { + return true; + } + } + } + + return false; + } + public function importCSV($maps, $headers) { + $skipped = []; + foreach ($maps as $entityType => $map) { - $this->executeCSV($entityType, $map, $headers[$entityType]); + $result = $this->executeCSV($entityType, $map, $headers[$entityType]); + $skipped = array_merge($skipped, $result); } + + return $skipped; + } + + private function executeCSV($entityType, $map, $hasHeaders) + { + $skipped = []; + $source = IMPORT_CSV; + + $data = Session::get("{$entityType}-data"); + $this->checkData($entityType, count($data)); + $maps = $this->createMaps(); + + foreach ($data as $row) { + if ($hasHeaders) { + $hasHeaders = false; + continue; + } + + $row = $this->convertToObject($entityType, $row, $map); + $result = $this->saveData($source, $entityType, $row, $maps); + + if ( ! $result) { + $skipped[] = $row; + } + } + + Session::forget("{$entityType}-data"); + + return $skipped; } private function convertToObject($entityType, $data, $map) @@ -330,6 +378,10 @@ class ImportService continue; } + if (isset($obj->$field) && $obj->$field) { + continue; + } + $obj->$field = $data[$index]; } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index a2f7c8ba77..687bab36a1 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -948,5 +948,6 @@ return array( 'notes' => 'Notes', 'invoice_will_create' => 'client will be created', 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', );