1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-09 20:52:56 +01:00

Working on import

This commit is contained in:
Hillel Coren 2015-11-18 19:16:23 +02:00
parent 73a0192446
commit bcad8af535
8 changed files with 241 additions and 227 deletions

View File

@ -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<count($headers); $i++) {
$title = strtolower($headers[$i]);
$mapped[$i] = '';
if ($hasHeaders) {
$map = array(
'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,
);
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();

View File

@ -1,5 +1,7 @@
<?php namespace app\Http\Controllers;
use Utils;
use View;
use Exception;
use Input;
use Session;
@ -18,19 +20,46 @@ class ImportController extends BaseController
public function doImport()
{
try {
$source = Input::get('source');
if ($source === IMPORT_CSV) {
$filename = Input::file('client_file')->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);
}
}

View File

@ -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]];

View File

@ -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'));

View File

@ -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<count($headers); $i++) {
$title = strtolower($headers[$i]);
$mapped[$i] = '';
if ($hasHeaders) {
$map = array(
'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,
);
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;
}
}

View File

@ -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",
/*

View File

@ -4,7 +4,7 @@
@parent
<style type="text/css">
.client-file,
.invoice-file,
.task-file {
display: none;
}

View File

@ -5,7 +5,7 @@
@include('accounts.nav', ['selected' => ACCOUNT_IMPORT_EXPORT])
{!! Former::open('settings/' . ACCOUNT_IMPORT_EXPORT)->addClass('warn-on-exit') !!}
{!! Former::open('/import_csv')->addClass('warn-on-exit') !!}
<div class="panel panel-default">
<div class="panel-heading">