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:
parent
73a0192446
commit
bcad8af535
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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]];
|
||||
|
@ -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'));
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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",
|
||||
|
||||
/*
|
||||
|
@ -4,7 +4,7 @@
|
||||
@parent
|
||||
|
||||
<style type="text/css">
|
||||
.client-file,
|
||||
.invoice-file,
|
||||
.task-file {
|
||||
display: none;
|
||||
}
|
||||
|
@ -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">
|
||||
|
Loading…
Reference in New Issue
Block a user