mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 15:34:39 -04:00
Updated readme
This commit is contained in:
parent
b4b027de5c
commit
ae57fdf9a3
@ -22,6 +22,7 @@ MAIL_PASSWORD
|
|||||||
|
|
||||||
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
|
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
|
||||||
LOG=single
|
LOG=single
|
||||||
|
REQUIRE_HTTPS=false
|
||||||
|
|
||||||
GOOGLE_CLIENT_ID
|
GOOGLE_CLIENT_ID
|
||||||
GOOGLE_CLIENT_SECRET
|
GOOGLE_CLIENT_SECRET
|
||||||
|
@ -21,14 +21,8 @@ class ImportController extends BaseController
|
|||||||
public function doImport()
|
public function doImport()
|
||||||
{
|
{
|
||||||
$source = Input::get('source');
|
$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 = [];
|
$files = [];
|
||||||
|
|
||||||
foreach (ImportService::$entityTypes as $entityType) {
|
foreach (ImportService::$entityTypes as $entityType) {
|
||||||
if (Input::file("{$entityType}_file")) {
|
if (Input::file("{$entityType}_file")) {
|
||||||
$files[$entityType] = Input::file("{$entityType}_file")->getRealPath();
|
$files[$entityType] = Input::file("{$entityType}_file")->getRealPath();
|
||||||
@ -36,29 +30,33 @@ class ImportController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if ($source === IMPORT_CSV) {
|
||||||
|
$data = $this->importService->mapCSV($files);
|
||||||
|
return View::make('accounts.import_map', ['data' => $data]);
|
||||||
|
} else {
|
||||||
$result = $this->importService->import($source, $files);
|
$result = $this->importService->import($source, $files);
|
||||||
Session::flash('message', trans('texts.imported_file') . ' - ' . $result);
|
Session::flash('message', trans('texts.imported_file') . ' - ' . $result);
|
||||||
|
}
|
||||||
} catch (Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
Session::flash('error', $exception->getMessage());
|
Session::flash('error', $exception->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
|
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function doImportCSV()
|
public function doImportCSV()
|
||||||
{
|
{
|
||||||
$map = Input::get('map');
|
$map = Input::get('map');
|
||||||
$hasHeaders = Input::get('header_checkbox');
|
$headers = Input::get('headers');
|
||||||
|
|
||||||
try {
|
//try {
|
||||||
$count = $this->importService->importCSV($map, $hasHeaders);
|
$count = $this->importService->importCSV($map, $headers);
|
||||||
$message = Utils::pluralize('created_client', $count);
|
$message = Utils::pluralize('created_client', $count);
|
||||||
|
|
||||||
Session::flash('message', $message);
|
Session::flash('message', $message);
|
||||||
} catch (Exception $exception) {
|
//} catch (Exception $exception) {
|
||||||
Session::flash('error', $exception->getMessage());
|
// Session::flash('error', $exception->getMessage());
|
||||||
}
|
//}
|
||||||
|
|
||||||
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
|
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ class StartupCheck
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure all request are over HTTPS in production
|
// Ensure all request are over HTTPS in production
|
||||||
if (Utils::isNinjaProd() && !Request::secure()) {
|
if (Utils::requireHTTPS() && !Request::secure()) {
|
||||||
return Redirect::secure(Request::getRequestUri());
|
return Redirect::secure(Request::getRequestUri());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,11 @@ class Utils
|
|||||||
return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'] == 'true';
|
return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'] == 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function requireHTTPS()
|
||||||
|
{
|
||||||
|
return Utils::isNinjaProd() || (isset($_ENV['REQUIRE_HTTPS']) && $_ENV['REQUIRE_HTTPS'] == 'true');
|
||||||
|
}
|
||||||
|
|
||||||
public static function isOAuthEnabled()
|
public static function isOAuthEnabled()
|
||||||
{
|
{
|
||||||
$providers = [
|
$providers = [
|
||||||
|
@ -39,15 +39,53 @@ class Client extends EntityModel
|
|||||||
'website',
|
'website',
|
||||||
];
|
];
|
||||||
|
|
||||||
public static $fieldName = 'Client - Name';
|
public static $fieldName = 'name';
|
||||||
public static $fieldPhone = 'Client - Phone';
|
public static $fieldPhone = 'work_phone';
|
||||||
public static $fieldAddress1 = 'Client - Street';
|
public static $fieldAddress1 = 'address1';
|
||||||
public static $fieldAddress2 = 'Client - Apt/Floor';
|
public static $fieldAddress2 = 'address2';
|
||||||
public static $fieldCity = 'Client - City';
|
public static $fieldCity = 'city';
|
||||||
public static $fieldState = 'Client - State';
|
public static $fieldState = 'state';
|
||||||
public static $fieldPostalCode = 'Client - Postal Code';
|
public static $fieldPostalCode = 'postal_code';
|
||||||
public static $fieldNotes = 'Client - Notes';
|
public static $fieldNotes = 'notes';
|
||||||
public static $fieldCountry = 'Client - Country';
|
public static $fieldCountry = 'country';
|
||||||
|
|
||||||
|
public static function getImportColumns()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
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,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function account()
|
public function account()
|
||||||
{
|
{
|
||||||
|
@ -17,10 +17,10 @@ class Contact extends EntityModel
|
|||||||
'send_invoice',
|
'send_invoice',
|
||||||
];
|
];
|
||||||
|
|
||||||
public static $fieldFirstName = 'Contact - First Name';
|
public static $fieldFirstName = 'first_name';
|
||||||
public static $fieldLastName = 'Contact - Last Name';
|
public static $fieldLastName = 'last_name';
|
||||||
public static $fieldEmail = 'Contact - Email';
|
public static $fieldEmail = 'email';
|
||||||
public static $fieldPhone = 'Contact - Phone';
|
public static $fieldPhone = 'phone';
|
||||||
|
|
||||||
public function account()
|
public function account()
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@ use DateTime;
|
|||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Laracasts\Presenter\PresentableTrait;
|
use Laracasts\Presenter\PresentableTrait;
|
||||||
use App\Models\BalanceAffecting;
|
use App\Models\BalanceAffecting;
|
||||||
|
use App\Models\Client;
|
||||||
use App\Events\QuoteWasCreated;
|
use App\Events\QuoteWasCreated;
|
||||||
use App\Events\QuoteWasUpdated;
|
use App\Events\QuoteWasUpdated;
|
||||||
use App\Events\InvoiceWasCreated;
|
use App\Events\InvoiceWasCreated;
|
||||||
@ -29,6 +30,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
'auto_bill' => 'boolean',
|
'auto_bill' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// used for custom invoice numbers
|
||||||
public static $patternFields = [
|
public static $patternFields = [
|
||||||
'counter',
|
'counter',
|
||||||
'custom1',
|
'custom1',
|
||||||
@ -38,6 +40,40 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
'date:',
|
'date:',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static $fieldInvoiceNumber = 'invoice_number';
|
||||||
|
public static $fieldInvoiceDate = 'invoice_date';
|
||||||
|
public static $fieldDueDate = 'due_date';
|
||||||
|
public static $fieldAmount = 'amount';
|
||||||
|
public static $fieldPaid = 'paid';
|
||||||
|
public static $fieldNotes = 'notes';
|
||||||
|
public static $fieldTerms = 'terms';
|
||||||
|
|
||||||
|
public static function getImportColumns()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Client::$fieldName,
|
||||||
|
Invoice::$fieldInvoiceNumber,
|
||||||
|
Invoice::$fieldInvoiceDate,
|
||||||
|
Invoice::$fieldDueDate,
|
||||||
|
Invoice::$fieldAmount,
|
||||||
|
Invoice::$fieldPaid,
|
||||||
|
Invoice::$fieldNotes,
|
||||||
|
Invoice::$fieldTerms,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getImportMap()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'number' => Invoice::$fieldInvoiceNumber,
|
||||||
|
'amount' => Invoice::$fieldAmount,
|
||||||
|
'organization' => 'name',
|
||||||
|
'paid' => 'paid',
|
||||||
|
'create_date' => Invoice::$fieldInvoiceDate,
|
||||||
|
'terms' => 'terms',
|
||||||
|
'notes' => 'notes',
|
||||||
|
];
|
||||||
|
}
|
||||||
public function getRoute()
|
public function getRoute()
|
||||||
{
|
{
|
||||||
$entityType = $this->getEntityType();
|
$entityType = $this->getEntityType();
|
||||||
|
40
app/Ninja/Import/CSV/ClientTransformer.php
Normal file
40
app/Ninja/Import/CSV/ClientTransformer.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php namespace App\Ninja\Import\CSV;
|
||||||
|
|
||||||
|
use League\Fractal\TransformerAbstract;
|
||||||
|
use App\Models\Country;
|
||||||
|
use League\Fractal\Resource\Item;
|
||||||
|
|
||||||
|
class ClientTransformer extends TransformerAbstract
|
||||||
|
{
|
||||||
|
public function transform($data, $maps)
|
||||||
|
{
|
||||||
|
if (isset($maps[ENTITY_CLIENT][$data->name])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($maps['countries'][$data->country])) {
|
||||||
|
$data->country_id = $maps['countries'][$data->country];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Item($data, function ($data) use ($maps) {
|
||||||
|
return [
|
||||||
|
'name' => isset($data->name) ? $data->name : null,
|
||||||
|
'work_phone' => isset($data->work_phone) ? $data->work_phone : null,
|
||||||
|
'address1' => isset($data->address1) ? $data->address1 : null,
|
||||||
|
'city' => isset($data->city) ? $data->city : null,
|
||||||
|
'state' => isset($data->state) ? $data->state : null,
|
||||||
|
'postal_code' => isset($data->postal_code) ? $data->postal_code : null,
|
||||||
|
'private_notes' => isset($data->notes) ? $data->notes : null,
|
||||||
|
'contacts' => [
|
||||||
|
[
|
||||||
|
'first_name' => isset($data->first_name) ? $data->first_name : null,
|
||||||
|
'last_name' => isset($data->last_name) ? $data->last_name : null,
|
||||||
|
'email' => isset($data->email) ? $data->email : null,
|
||||||
|
'phone' => isset($data->phone) ? $data->phone : null,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'country_id' => isset($data->country_id) ? $data->country_id : null,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
40
app/Ninja/Import/CSV/InvoiceTransformer.php
Normal file
40
app/Ninja/Import/CSV/InvoiceTransformer.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php namespace App\Ninja\Import\CSV;
|
||||||
|
|
||||||
|
use League\Fractal\TransformerAbstract;
|
||||||
|
use League\Fractal\Resource\Item;
|
||||||
|
use App\Models\Client;
|
||||||
|
|
||||||
|
class InvoiceTransformer extends TransformerAbstract
|
||||||
|
{
|
||||||
|
public function transform($data, $maps)
|
||||||
|
{
|
||||||
|
if (isset($maps[ENTITY_INVOICE][$data->invoice_number])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($maps[ENTITY_CLIENT][$data->name])) {
|
||||||
|
$data->client_id = $maps[ENTITY_CLIENT][$data->name];
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Item($data, function ($data) use ($maps) {
|
||||||
|
return [
|
||||||
|
'invoice_number' => isset($data->invoice_number) ? $data->invoice_number : null,
|
||||||
|
'paid' => isset($data->paid) ? (float) $data->paid : null,
|
||||||
|
'client_id' => (int) $data->client_id,
|
||||||
|
'po_number' => isset($data->po_number) ? $data->po_number : null,
|
||||||
|
'terms' => isset($data->terms) ? $data->terms : null,
|
||||||
|
'public_notes' => isset($data->notes) ? $data->notes : null,
|
||||||
|
'invoice_date_sql' => isset($data->create_date) ? $data->create_date : null,
|
||||||
|
'invoice_items' => [
|
||||||
|
[
|
||||||
|
'notes' => isset($data->notes) ? $data->notes : null,
|
||||||
|
'cost' => isset($data->amount) ? (float) $data->amount : null,
|
||||||
|
'qty' => 1,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
19
app/Ninja/Import/CSV/PaymentTransformer.php
Normal file
19
app/Ninja/Import/CSV/PaymentTransformer.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php namespace App\Ninja\Import\CSV;
|
||||||
|
|
||||||
|
use League\Fractal\TransformerAbstract;
|
||||||
|
use League\Fractal\Resource\Item;
|
||||||
|
|
||||||
|
class PaymentTransformer extends TransformerAbstract
|
||||||
|
{
|
||||||
|
public function transform($data, $maps)
|
||||||
|
{
|
||||||
|
return new Item($data, function ($data) use ($maps) {
|
||||||
|
return [
|
||||||
|
'amount' => $data->paid,
|
||||||
|
'payment_date_sql' => $data->create_date,
|
||||||
|
'client_id' => $data->client_id,
|
||||||
|
'invoice_id' => $data->invoice_id,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
<?php namespace App\Services;
|
<?php namespace app\Services;
|
||||||
|
|
||||||
|
use stdClass;
|
||||||
use Excel;
|
use Excel;
|
||||||
use Cache;
|
use Cache;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Utils;
|
|
||||||
use parsecsv;
|
use parsecsv;
|
||||||
use Session;
|
use Session;
|
||||||
use Validator;
|
use Validator;
|
||||||
@ -14,7 +14,7 @@ use App\Ninja\Repositories\InvoiceRepository;
|
|||||||
use App\Ninja\Repositories\PaymentRepository;
|
use App\Ninja\Repositories\PaymentRepository;
|
||||||
use App\Ninja\Serializers\ArraySerializer;
|
use App\Ninja\Serializers\ArraySerializer;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Models\Contact;
|
use App\Models\Invoice;
|
||||||
|
|
||||||
class ImportService
|
class ImportService
|
||||||
{
|
{
|
||||||
@ -59,24 +59,57 @@ class ImportService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function execute($source, $entityType, $file)
|
private function checkClientCount($count)
|
||||||
{
|
{
|
||||||
$transformerClassName = $this->getTransformerClassName($source, $entityType);
|
$totalClients = $count + Client::scope()->withTrashed()->count();
|
||||||
$transformer = new $transformerClassName;
|
|
||||||
|
|
||||||
Excel::load($file, function($reader) use ($source, $entityType, $transformer) {
|
|
||||||
|
|
||||||
if ($entityType === ENTITY_CLIENT) {
|
|
||||||
$totalClients = count($reader->all()) + Client::scope()->withTrashed()->count();
|
|
||||||
if ($totalClients > Auth::user()->getMaxNumClients()) {
|
if ($totalClients > Auth::user()->getMaxNumClients()) {
|
||||||
throw new Exception(trans('texts.limit_clients', ['count' => 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, $transformer) {
|
||||||
|
|
||||||
|
$this->checkData($entityType, count($reader->all()));
|
||||||
$maps = $this->createMaps();
|
$maps = $this->createMaps();
|
||||||
|
|
||||||
$reader->each(function ($row) use ($source, $entityType, $transformer, $maps) {
|
$reader->each(function ($row) use ($source, $entityType, $transformer, $maps) {
|
||||||
if ($resource = $transformer->transform($row, $maps)) {
|
$this->saveData($source, $entityType, $row, $maps);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function saveData($source, $entityType, $row, $maps)
|
||||||
|
{
|
||||||
|
$transformer = $this->getTransformer($source, $entityType);
|
||||||
|
$resource = $transformer->transform($row, $maps);
|
||||||
|
|
||||||
|
if (!$resource) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$data = $this->fractal->createData($resource)->toArray();
|
$data = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
if ($this->validate($data, $entityType) !== true) {
|
if ($this->validate($data, $entityType) !== true) {
|
||||||
@ -87,19 +120,41 @@ class ImportService
|
|||||||
|
|
||||||
// if the invoice is paid we'll also create a payment record
|
// if the invoice is paid we'll also create a payment record
|
||||||
if ($entityType === ENTITY_INVOICE && isset($data['paid']) && $data['paid']) {
|
if ($entityType === ENTITY_INVOICE && isset($data['paid']) && $data['paid']) {
|
||||||
$class = self::getTransformerClassName($source, ENTITY_PAYMENT);
|
$this->createPayment($source, $row, $maps, $data['client_id'], $entity->public_id);
|
||||||
$paymentTransformer = new $class;
|
}
|
||||||
$row->client_id = $data['client_id'];
|
}
|
||||||
$row->invoice_id = $entity->public_id;
|
|
||||||
if ($resource = $paymentTransformer->transform($row, $maps)) {
|
private function checkData($entityType, $count)
|
||||||
|
{
|
||||||
|
if ($entityType === ENTITY_CLIENT) {
|
||||||
|
$this->checkClientCount($count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getTransformerClassName($source, $entityType)
|
||||||
|
{
|
||||||
|
return 'App\\Ninja\\Import\\'.$source.'\\'.ucwords($entityType).'Transformer';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getTransformer($source, $entityType)
|
||||||
|
{
|
||||||
|
$className = self::getTransformerClassName($source, $entityType);
|
||||||
|
|
||||||
|
return new $className();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createPayment($source, $data, $maps, $clientId, $invoiceId)
|
||||||
|
{
|
||||||
|
$paymentTransformer = $this->getTransformer($source, ENTITY_PAYMENT);
|
||||||
|
|
||||||
|
$row->client_id = $clientId;
|
||||||
|
$row->invoice_id = $invoiceId;
|
||||||
|
|
||||||
|
if ($resource = $paymentTransformer->transform($data, $maps)) {
|
||||||
$data = $this->fractal->createData($resource)->toArray();
|
$data = $this->fractal->createData($resource)->toArray();
|
||||||
$this->paymentRepo->save($data);
|
$this->paymentRepo->save($data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// looking for a better solution...
|
// looking for a better solution...
|
||||||
// http://stackoverflow.com/questions/33781567/how-can-i-re-use-the-validation-code-in-my-laravel-formrequest-classes
|
// http://stackoverflow.com/questions/33781567/how-can-i-re-use-the-validation-code-in-my-laravel-formrequest-classes
|
||||||
@ -109,7 +164,8 @@ class ImportService
|
|||||||
$rules = [
|
$rules = [
|
||||||
'contacts' => 'valid_contacts',
|
'contacts' => 'valid_contacts',
|
||||||
];
|
];
|
||||||
} if ($entityType === ENTITY_INVOICE) {
|
}
|
||||||
|
if ($entityType === ENTITY_INVOICE) {
|
||||||
$rules = [
|
$rules = [
|
||||||
'client.contacts' => 'valid_contacts',
|
'client.contacts' => 'valid_contacts',
|
||||||
'invoice_items' => 'valid_invoice_items',
|
'invoice_items' => 'valid_invoice_items',
|
||||||
@ -122,6 +178,7 @@ class ImportService
|
|||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
$messages = $validator->messages();
|
$messages = $validator->messages();
|
||||||
|
|
||||||
return $messages->first();
|
return $messages->first();
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
@ -155,42 +212,50 @@ class ImportService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getTransformerClassName($source, $entityType)
|
public function mapCSV($files)
|
||||||
{
|
{
|
||||||
return 'App\\Ninja\\Import\\' . $source . '\\' . ucwords($entityType) . 'Transformer';
|
$data = [];
|
||||||
|
|
||||||
|
foreach ($files as $entityType => $filename) {
|
||||||
|
if ($entityType === ENTITY_CLIENT) {
|
||||||
|
$columns = Client::getImportColumns();
|
||||||
|
$map = Client::getImportMap();
|
||||||
|
} else {
|
||||||
|
$columns = Invoice::getImportColumns();
|
||||||
|
$map = Invoice::getImportMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mapFile($filename)
|
// Lookup field translations
|
||||||
|
foreach ($columns as $key => $value) {
|
||||||
|
unset($columns[$key]);
|
||||||
|
$columns[$value] = trans("texts.{$value}");
|
||||||
|
}
|
||||||
|
array_unshift($columns, ' ');
|
||||||
|
|
||||||
|
$data[$entityType] = $this->mapFile($entityType, $filename, $columns, $map);
|
||||||
|
|
||||||
|
if ($entityType === ENTITY_CLIENT) {
|
||||||
|
if (count($data[$entityType]['data']) + Client::scope()->count() > Auth::user()->getMaxNumClients()) {
|
||||||
|
throw new Exception(trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mapFile($entityType, $filename, $columns, $map)
|
||||||
{
|
{
|
||||||
require_once app_path().'/Includes/parsecsv.lib.php';
|
require_once app_path().'/Includes/parsecsv.lib.php';
|
||||||
$csv = new parseCSV();
|
$csv = new parseCSV();
|
||||||
$csv->heading = false;
|
$csv->heading = false;
|
||||||
$csv->auto($filename);
|
$csv->auto($filename);
|
||||||
|
|
||||||
if (count($csv->data) + Client::scope()->count() > Auth::user()->getMaxNumClients()) {
|
Session::put("{$entityType}-data", $csv->data);
|
||||||
throw new Exception(trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
Session::put('data', $csv->data);
|
|
||||||
|
|
||||||
$headers = false;
|
$headers = false;
|
||||||
$hasHeaders = false;
|
$hasHeaders = false;
|
||||||
$mapped = array();
|
$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) {
|
if (count($csv->data) > 0) {
|
||||||
$headers = $csv->data[0];
|
$headers = $csv->data[0];
|
||||||
@ -206,29 +271,11 @@ class ImportService
|
|||||||
$mapped[$i] = '';
|
$mapped[$i] = '';
|
||||||
|
|
||||||
if ($hasHeaders) {
|
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 ($map as $search => $column) {
|
||||||
foreach (explode("|", $search) as $string) {
|
foreach (explode("|", $search) as $string) {
|
||||||
if (strpos($title, 'sec') === 0) {
|
if (strpos($title, 'sec') === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
} elseif (strpos($title, $string) !== false) {
|
||||||
|
|
||||||
if (strpos($title, $string) !== false) {
|
|
||||||
$mapped[$i] = $column;
|
$mapped[$i] = $column;
|
||||||
break(2);
|
break(2);
|
||||||
}
|
}
|
||||||
@ -239,6 +286,7 @@ class ImportService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$data = array(
|
$data = array(
|
||||||
|
'entityType' => $entityType,
|
||||||
'data' => $csv->data,
|
'data' => $csv->data,
|
||||||
'headers' => $headers,
|
'headers' => $headers,
|
||||||
'hasHeaders' => $hasHeaders,
|
'hasHeaders' => $hasHeaders,
|
||||||
@ -249,78 +297,35 @@ class ImportService
|
|||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function importCSV($map, $hasHeaders)
|
public function importCSV($maps, $headers)
|
||||||
{
|
{
|
||||||
$count = 0;
|
foreach ($maps as $entityType => $map) {
|
||||||
$data = Session::get('data');
|
$this->executeCSV($entityType, $map, $headers[$entityType]);
|
||||||
$countries = Cache::get('countries');
|
}
|
||||||
$countryMap = [];
|
|
||||||
|
|
||||||
foreach ($countries as $country) {
|
|
||||||
$countryMap[strtolower($country->name)] = $country->id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($data as $row) {
|
private function convertToObject($entityType, $data, $map)
|
||||||
if ($hasHeaders) {
|
{
|
||||||
$hasHeaders = false;
|
$obj = new stdClass();
|
||||||
|
|
||||||
|
if ($entityType === ENTITY_CLIENT) {
|
||||||
|
$columns = Client::getImportColumns();
|
||||||
|
} else {
|
||||||
|
$columns = Invoice::getImportColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($columns as $column) {
|
||||||
|
$obj->$column = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($map as $index => $field) {
|
||||||
|
if (! $field) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = [
|
$obj->$field = $data[$index];
|
||||||
'contacts' => [[]]
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($row as $index => $value) {
|
|
||||||
$field = $map[$index];
|
|
||||||
if ( ! $value = trim($value)) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($field == Client::$fieldName) {
|
return $obj;
|
||||||
$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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -12,8 +12,6 @@ We offer two options:
|
|||||||
* 10% of revenue
|
* 10% of revenue
|
||||||
* $1,000 for a site limited to 1,000 accounts
|
* $1,000 for a site limited to 1,000 accounts
|
||||||
|
|
||||||
If you'd like to use our code to sell your own invoicing app email us for details about our affiliate program.
|
|
||||||
|
|
||||||
### Installation Options
|
### Installation Options
|
||||||
* [Self-Host Zip](https://www.invoiceninja.com/knowledgebase/self-host/) - Free
|
* [Self-Host Zip](https://www.invoiceninja.com/knowledgebase/self-host/) - Free
|
||||||
* [Docker File](https://github.com/invoiceninja/dockerfiles) - Free
|
* [Docker File](https://github.com/invoiceninja/dockerfiles) - Free
|
||||||
|
@ -87,7 +87,7 @@ return array(
|
|||||||
'guest' => 'Guest',
|
'guest' => 'Guest',
|
||||||
'company_details' => 'Company Details',
|
'company_details' => 'Company Details',
|
||||||
'online_payments' => 'Online Payments',
|
'online_payments' => 'Online Payments',
|
||||||
'notifications' => 'Notifications',
|
'notifications' => 'Email Notifications',
|
||||||
'import_export' => 'Import/Export',
|
'import_export' => 'Import/Export',
|
||||||
'done' => 'Done',
|
'done' => 'Done',
|
||||||
'save' => 'Save',
|
'save' => 'Save',
|
||||||
@ -334,7 +334,7 @@ return array(
|
|||||||
// product management
|
// product management
|
||||||
'product_library' => 'Product Library',
|
'product_library' => 'Product Library',
|
||||||
'product' => 'Product',
|
'product' => 'Product',
|
||||||
'products' => 'Products',
|
'products' => 'Product Library',
|
||||||
'fill_products' => 'Auto-fill products',
|
'fill_products' => 'Auto-fill products',
|
||||||
'fill_products_help' => 'Selecting a product will automatically <b>fill in the description and cost</b>',
|
'fill_products_help' => 'Selecting a product will automatically <b>fill in the description and cost</b>',
|
||||||
'update_products' => 'Auto-update products',
|
'update_products' => 'Auto-update products',
|
||||||
@ -945,4 +945,8 @@ return array(
|
|||||||
'admin' => 'Admin',
|
'admin' => 'Admin',
|
||||||
'disabled' => 'Disabled',
|
'disabled' => 'Disabled',
|
||||||
'show_archived_users' => 'Show archived users',
|
'show_archived_users' => 'Show archived users',
|
||||||
|
'notes' => 'Notes',
|
||||||
|
'invoice_will_create' => 'client will be created',
|
||||||
|
'invoices_will_create' => 'invoices will be created',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
@parent
|
@parent
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.invoice-file,
|
|
||||||
.task-file {
|
.task-file {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -131,15 +130,11 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
@foreach (\App\Services\ImportService::$sources as $source)
|
@foreach (\App\Services\ImportService::$sources as $source)
|
||||||
if (val === '{{ $source }}') {
|
if (val === '{{ $source }}') {
|
||||||
@if ($source == IMPORT_CSV)
|
|
||||||
$('.client-file').show();
|
|
||||||
@else
|
|
||||||
@foreach (\App\Services\ImportService::$entityTypes as $entityType)
|
@foreach (\App\Services\ImportService::$entityTypes as $entityType)
|
||||||
@if (class_exists(\App\Services\ImportService::getTransformerClassName($source, $entityType)))
|
@if (class_exists(\App\Services\ImportService::getTransformerClassName($source, $entityType)))
|
||||||
$('.{{ $entityType }}-file').show();
|
$('.{{ $entityType }}-file').show();
|
||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
@endif
|
|
||||||
}
|
}
|
||||||
@endforeach
|
@endforeach
|
||||||
}
|
}
|
||||||
|
@ -7,83 +7,18 @@
|
|||||||
|
|
||||||
{!! Former::open('/import_csv')->addClass('warn-on-exit') !!}
|
{!! Former::open('/import_csv')->addClass('warn-on-exit') !!}
|
||||||
|
|
||||||
<div class="panel panel-default">
|
@if (isset($data[ENTITY_CLIENT]))
|
||||||
<div class="panel-heading">
|
@include('accounts.partials.map', $data[ENTITY_CLIENT])
|
||||||
<h3 class="panel-title">{!! trans('texts.import_clients') !!}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
|
|
||||||
@if ($headers)
|
|
||||||
|
|
||||||
<label for="header_checkbox">
|
|
||||||
<input type="checkbox" name="header_checkbox" id="header_checkbox" {{ $hasHeaders ? 'CHECKED' : '' }}> {{ trans('texts.first_row_headers') }}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<p> </p>
|
|
||||||
|
|
||||||
<table class="table invoice-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{ trans('texts.column') }}</th>
|
|
||||||
<th class="col_sample">{{ trans('texts.sample') }}</th>
|
|
||||||
<th>{{ trans('texts.import_to') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
@for ($i=0; $i<count($headers); $i++)
|
|
||||||
<tr>
|
|
||||||
<td>{{ $headers[$i] }}</td>
|
|
||||||
<td class="col_sample">{{ $data[1][$i] }}</td>
|
|
||||||
<td>{!! Former::select('map[' . $i . ']')->options($columns, $mapped[$i], true)->raw() !!}</td>
|
|
||||||
</tr>
|
|
||||||
@endfor
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<p> </p>
|
|
||||||
|
|
||||||
<span id="numClients"></span>
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
@if (isset($data[ENTITY_INVOICE]))
|
||||||
</div>
|
@include('accounts.partials.map', $data[ENTITY_INVOICE])
|
||||||
|
@endif
|
||||||
|
|
||||||
{!! Former::actions(
|
{!! Former::actions(
|
||||||
Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/import_export'))->appendIcon(Icon::create('remove-circle')),
|
Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/import_export'))->appendIcon(Icon::create('remove-circle')),
|
||||||
Button::success(trans('texts.import'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))) !!}
|
Button::success(trans('texts.import'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))) !!}
|
||||||
|
|
||||||
{!! Former::close() !!}
|
{!! Former::close() !!}
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
$(function() {
|
|
||||||
|
|
||||||
var numClients = {{ count($data) }};
|
|
||||||
function setSampleShown() {
|
|
||||||
if ($('#header_checkbox').is(':checked')) {
|
|
||||||
$('.col_sample').show();
|
|
||||||
setNumClients(numClients - 1);
|
|
||||||
} else {
|
|
||||||
$('.col_sample').hide();
|
|
||||||
setNumClients(numClients);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setNumClients(num)
|
|
||||||
{
|
|
||||||
if (num == 1)
|
|
||||||
{
|
|
||||||
$('#numClients').html("1 {{ trans('texts.client_will_create') }}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$('#numClients').html(num + " {{ trans('texts.clients_will_create') }}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#header_checkbox').click(setSampleShown);
|
|
||||||
setSampleShown();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
@stop
|
@stop
|
66
resources/views/accounts/partials/map.blade.php
Normal file
66
resources/views/accounts/partials/map.blade.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">{!! trans("texts.import_{$entityType}s") !!}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
|
||||||
|
<label for="{{ $entityType }}_header_checkbox">
|
||||||
|
<input type="checkbox" name="headers[{{ $entityType }}]" id="{{ $entityType }}_header_checkbox" {{ $hasHeaders ? 'CHECKED' : '' }}> {{ trans('texts.first_row_headers') }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<p> </p>
|
||||||
|
|
||||||
|
<table class="table invoice-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ trans('texts.column') }}</th>
|
||||||
|
<th class="col_sample">{{ trans('texts.sample') }}</th>
|
||||||
|
<th>{{ trans('texts.import_to') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
@for ($i=0; $i<count($headers); $i++)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $headers[$i] }}</td>
|
||||||
|
<td class="col_sample">{{ $data[1][$i] }}</td>
|
||||||
|
<td>{!! Former::select('map['.$entityType.'][' . $i . ']')->options($columns, $mapped[$i])->raw() !!}</td>
|
||||||
|
</tr>
|
||||||
|
@endfor
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p> </p>
|
||||||
|
|
||||||
|
<span id="num{{ $entityType }}"></span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
var num{{ $entityType }} = {{ count($data) }};
|
||||||
|
function set{{ $entityType }}SampleShown() {
|
||||||
|
if ($('#{{ $entityType }}_header_checkbox').is(':checked')) {
|
||||||
|
$('.col_sample').show();
|
||||||
|
setNum{{ $entityType }}(num{{ $entityType }} - 1);
|
||||||
|
} else {
|
||||||
|
$('.col_sample').hide();
|
||||||
|
setNum{{ $entityType }}(num{{ $entityType }});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNum{{ $entityType }}(num)
|
||||||
|
{
|
||||||
|
if (num == 1) {
|
||||||
|
$('#num{{ $entityType }}').html("1 {{ trans("texts.{$entityType}_will_create") }}");
|
||||||
|
} else {
|
||||||
|
$('#num{{ $entityType }}').html(num + " {{ trans("texts.{$entityType}s_will_create") }}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#{{ $entityType }}_header_checkbox').click(set{{ $entityType }}SampleShown);
|
||||||
|
set{{ $entityType }}SampleShown();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
Loading…
x
Reference in New Issue
Block a user