Working on import

This commit is contained in:
Hillel Coren 2015-11-18 16:40:50 +02:00
parent 24bcb0e2e5
commit 5c6b2fe906
25 changed files with 399 additions and 587 deletions

View File

@ -37,7 +37,6 @@ use App\Models\Timezone;
use App\Models\Industry; use App\Models\Industry;
use App\Models\InvoiceDesign; use App\Models\InvoiceDesign;
use App\Models\TaxRate; use App\Models\TaxRate;
use App\Ninja\Import\DataImporterServiceInterface;
use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\ReferralRepository; use App\Ninja\Repositories\ReferralRepository;
@ -57,7 +56,7 @@ class AccountController extends BaseController
protected $contactMailer; protected $contactMailer;
protected $referralRepository; protected $referralRepository;
public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository, ClientRepository $clientRepository, DataImporterServiceInterface $dataImporterService) public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository, ClientRepository $clientRepository)
{ {
parent::__construct(); parent::__construct();
@ -66,7 +65,6 @@ class AccountController extends BaseController
$this->contactMailer = $contactMailer; $this->contactMailer = $contactMailer;
$this->referralRepository = $referralRepository; $this->referralRepository = $referralRepository;
$this->clientRepository = $clientRepository; $this->clientRepository = $clientRepository;
$this->dataImporterService = $dataImporterService;
} }
public function demo() public function demo()
@ -417,8 +415,6 @@ class AccountController extends BaseController
return AccountController::saveLocalization(); return AccountController::saveLocalization();
} elseif ($section === ACCOUNT_IMPORT_EXPORT) { } elseif ($section === ACCOUNT_IMPORT_EXPORT) {
return AccountController::importFile(); return AccountController::importFile();
} elseif ($section === IMPORT_FROM_FRESHBOOKS) {
return AccountController::importData();
} elseif ($section === ACCOUNT_MAP) { } elseif ($section === ACCOUNT_MAP) {
return AccountController::mapFile(); return AccountController::mapFile();
} elseif ($section === ACCOUNT_NOTIFICATIONS) { } elseif ($section === ACCOUNT_NOTIFICATIONS) {
@ -737,26 +733,6 @@ class AccountController extends BaseController
return Redirect::to('clients'); return Redirect::to('clients');
} }
private function importData()
{
try
{
$files['client'] = Input::file('client_file');
$files['invoice'] = Input::file('invoice_file');
$files['timesheet'] = Input::file('timesheet_file');
$imported_files = $this->dataImporterService->import($files);
}
catch(Exception $e)
{
Session::flash('error', $e->getMessage());
return Redirect::to('settings/' . ACCOUNT_IMPORT_EXPORT);
}
Session::flash('message', trans('texts.imported_file').' - '.$imported_files);
return Redirect::to('settings/' . ACCOUNT_IMPORT_EXPORT);
}
private function mapFile() private function mapFile()
{ {
$file = Input::file('file'); $file = Input::file('file');

View File

@ -14,7 +14,7 @@ use App\Models\Task;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
class ImportExportController extends BaseController class ExportController extends BaseController
{ {
public function doExport(Request $request) public function doExport(Request $request)
{ {

View File

@ -0,0 +1,38 @@
<?php namespace app\Http\Controllers;
use Input;
use Session;
use Redirect;
use App\Services\ImportService;
use App\Http\Controllers\BaseController;
class ImportController extends BaseController
{
public function __construct(ImportService $importService)
{
parent::__construct();
$this->importService = $importService;
}
public function doImport()
{
try {
$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);
} catch (Exception $e) {
Session::flash('error', $e->getMessage());
return Redirect::to('/settings/'.ACCOUNT_IMPORT_EXPORT);
}
Session::flash('message', trans('texts.imported_file').' - '.$imported_files);
return Redirect::to('/settings/'.ACCOUNT_IMPORT_EXPORT);
}
}

View File

@ -131,7 +131,9 @@ Route::group(['middleware' => 'auth'], function() {
Route::post('user/setTheme', 'UserController@setTheme'); Route::post('user/setTheme', 'UserController@setTheme');
Route::post('remove_logo', 'AccountController@removeLogo'); Route::post('remove_logo', 'AccountController@removeLogo');
Route::post('account/go_pro', 'AccountController@enableProPlan'); Route::post('account/go_pro', 'AccountController@enableProPlan');
Route::post('/export', 'ImportExportController@doExport');
Route::post('/export', 'ExportController@doExport');
Route::post('/import', 'ImportController@doImport');
Route::resource('gateways', 'AccountGatewayController'); Route::resource('gateways', 'AccountGatewayController');
Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable')); Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable'));
@ -337,7 +339,15 @@ if (!defined('CONTACT_EMAIL')) {
define('DEFAULT_FONT_SIZE', 9); define('DEFAULT_FONT_SIZE', 9);
define('DEFAULT_SEND_RECURRING_HOUR', 8); define('DEFAULT_SEND_RECURRING_HOUR', 8);
define('IMPORT_FROM_FRESHBOOKS', 'import_from_freshbook'); define('IMPORT_CSV', 'CSV');
define('IMPORT_FRESHBOOKS', 'FreshBooks');
define('IMPORT_WAVE', 'Wave');
define('IMPORT_RONIN', 'Ronin');
define('IMPORT_HIVEAGE', 'Hiveage');
define('IMPORT_ZOHO', 'Zoho');
define('IMPORT_NUTCACHE', 'Nutcache');
define('IMPORT_INVOICEABLE', 'Invoiceable');
define('IMPORT_HARVEST', 'Harvest');
define('MAX_NUM_CLIENTS', 100); define('MAX_NUM_CLIENTS', 100);
define('MAX_NUM_CLIENTS_PRO', 20000); define('MAX_NUM_CLIENTS_PRO', 20000);
@ -432,7 +442,7 @@ if (!defined('CONTACT_EMAIL')) {
define('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html'); define('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html');
define('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/single/browser/v1/'); define('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/single/browser/v1/');
define('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php'); define('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php');
define('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/affiliates/'); define('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/referral-program/');
define('COUNT_FREE_DESIGNS', 4); define('COUNT_FREE_DESIGNS', 4);
define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design

View File

@ -23,7 +23,7 @@ class InvoiceListener
public function updatedInvoice(InvoiceWasUpdated $event) public function updatedInvoice(InvoiceWasUpdated $event)
{ {
$invoice = $event->invoice; $invoice = $event->invoice;
$invoice->updatePaidStatus(); $invoice->updatePaidStatus(false);
} }
public function viewedInvoice(InvoiceInvitationWasViewed $event) public function viewedInvoice(InvoiceInvitationWasViewed $event)

View File

@ -192,16 +192,20 @@ class Invoice extends EntityModel implements BalanceAffecting
} }
} }
public function updatePaidStatus() public function updatePaidStatus($save = true)
{ {
if ($this->isPaid() && $this->balance > 0) { if ($this->isPaid() && $this->balance > 0) {
$this->invoice_status_id = ($this->balance == $this->amount ? INVOICE_STATUS_SENT : INVOICE_STATUS_PARTIAL); $this->invoice_status_id = ($this->balance == $this->amount ? INVOICE_STATUS_SENT : INVOICE_STATUS_PARTIAL);
if ($save) {
$this->save(); $this->save();
}
} elseif ($this->invoice_status_id && $this->amount > 0 && $this->balance == 0 && $this->invoice_status_id != INVOICE_STATUS_PAID) { } elseif ($this->invoice_status_id && $this->amount > 0 && $this->balance == 0 && $this->invoice_status_id != INVOICE_STATUS_PAID) {
$this->invoice_status_id = INVOICE_STATUS_PAID; $this->invoice_status_id = INVOICE_STATUS_PAID;
if ($save) {
$this->save(); $this->save();
} }
} }
}
public function updateBalances($balanceAdjustment, $partial = 0) public function updateBalances($balanceAdjustment, $partial = 0)
{ {

View File

@ -1,15 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: eduardocruz
* Date: 11/9/15
* Time: 11:03
*/
namespace App\Ninja\Import;
interface DataImporterServiceInterface
{
public function import($files);
}

View File

@ -1,107 +1,41 @@
<?php <?php namespace App\Ninja\Import\FreshBooks;
/**
* Created by PhpStorm.
* User: eduardocruz
* Date: 11/9/15
* Time: 11:47
*/
namespace app\Ninja\Import\FreshBooks;
use League\Fractal\TransformerAbstract; use League\Fractal\TransformerAbstract;
use League\Fractal\Resource\Collection;
use stdClass;
use App\Models\Country; use App\Models\Country;
use League\Fractal\Resource\Item;
class ClientTransformer extends TransformerAbstract class ClientTransformer extends TransformerAbstract
{ {
public function transform($data, $maps)
public function transform($data)
{ {
return new Collection($data, function(array $data) { if (isset($maps[ENTITY_CLIENT][$data->organization])) {
$data = $this->arrayToObject($data); return false;
}
if (isset($maps['countries'][$data->country])) {
$data->country_id = $maps['countries'][$data->country];
}
return new Item($data, function ($data) use ($maps) {
return [ return [
'name' => $data->organization !== array() ? $data->organization : '', 'name' => $data->organization,
'work_phone' => $data->busPhone !== array() ? $data->busPhone : '', 'work_phone' => $data->busphone,
'address1' => $data->street !== array() ? $data->street : '', 'address1' => $data->street,
'address2' => $data->street2 !== array() ? $data->street2 : '', 'address2' => $data->street2,
'city' => $data->city !== array() ? $data->city : '', 'city' => $data->city,
'state' => $data->province !== array() ? $data->province : '', 'state' => $data->province,
'postal_code' => $data->postalCode !== array() ? $data->postalCode : '', 'postal_code' => $data->postalcode,
'private_notes' => $data->notes !== array() ? $data->notes : '', 'private_notes' => $data->notes,
'contacts' => [ 'contacts' => [
[ [
'public_id' => '', 'first_name' => $data->firstname,
'first_name' => $data->firstName !== array() ? $data->firstName : '', 'last_name' => $data->lastname,
'last_name' => $data->lastName !== array() ? $data->lastName : '', 'email' => $data->email,
'email' => $data->email !== array() ? $data->email : '', 'phone' => $data->mobphone ?: $data->homephone,
'phone' => $data->mobPhone !== array() ? $data->mobPhone : $data->homePhone,
]
], ],
'country_id' => !Country::where('name', $data->country) ],
->get() 'country_id' => $data->country_id,
->isEmpty() ? Country::where('name', $data->country)
->first()->id : null,
]; ];
}); });
} }
private function arrayToObject($array)
{
$object = new stdClass();
$object->organization = $array[0];
$object->firstName = $array[1];
$object->lastName = $array[2];
$object->email = $array[3];
$object->street = $array[4];
$object->street2 = $array[5];
$object->city = $array[6];
$object->province = $array[7];
$object->country = $array[8];
$object->postalCode = $array[9];
$object->busPhone = $array[10];
$object->homePhone = $array[11];
$object->mobPhone = $array[12];
$object->fax = $array[13];
$object->secStreet = $array[14];
$object->secStreet2 = $array[15];
$object->secCity = $array[16];
$object->secProvince = $array[17];
$object->secCountry = $array[18];
$object->secPostalCode = $array[19];
$object->notes = $array[20];
return $object;
}
public function validateHeader($csvHeader)
{
$header = [0 => "Organization",
1 => "FirstName",
2 => "LastName",
3 => "Email",
4 => "Street",
5 => "Street2",
6 => "City",
7 => "Province",
8 => "Country",
9 => "PostalCode",
10 => "BusPhone",
11 => "HomePhone",
12 => "MobPhone",
13 => "Fax",
14 => "SecStreet",
15 => "SecStreet2",
16 => "SecCity",
17 => "SecProvince",
18 => "SecCountry",
19 => "SecPostalCode",
20 => "Notes"];
if(!empty(array_diff($header, $csvHeader)))
throw new Exception(trans('texts.invalid_csv_header'));
}
} }

View File

@ -1,154 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: eduardocruz
* Date: 11/9/15
* Time: 11:10
*/
namespace App\Ninja\Import\FreshBooks;
use Exception;
use App\Ninja\Import\DataImporterServiceInterface;
use League\Fractal\Manager;
use parseCSV;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\InvoiceRepository;
use Illuminate\Contracts\Container\Container;
class FreshBooksDataImporterService implements DataImporterServiceInterface
{
protected $transformer;
//protected $repository;
protected $invoiceRepo;
/**
* FreshBooksDataImporterService constructor.
*/
public function __construct(Manager $manager, ClientRepository $clientRepo, InvoiceRepository $invoiceRepo, Container $container)
{
$this->clientRepo = $clientRepo;
$this->invoiceRepo = $invoiceRepo;
$this->container = $container;
$this->fractal = $manager;
$this->transformerList = array(
'client' => __NAMESPACE__ . '\ClientTransformer',
'invoice' => __NAMESPACE__ . '\InvoiceTransformer',
'timesheet' => __NAMESPACE__ . '\TimesheetTransformer',
);
$this->repositoryList = array(
'client' => '\App\Ninja\Repositories\ClientRepository',
'invoice' => '\App\Ninja\Repositories\InvoiceRepository',
'timesheet' => '\App\Ninja\Repositories\TaskRepository',
);
}
public function import($files)
{
$imported_files = null;
foreach($files as $entity => $file)
{
$imported_files = $imported_files . $this->execute($entity, $file);
}
return $imported_files;
}
private function execute($entity, $file)
{
$this->transformer = $this->createTransformer($entity);
$this->repository = $this->createRepository($entity);
$data = $this->parseCSV($file);
$ignore_header = true;
try
{
$rows = $this->mapCsvToModel($data, $ignore_header);
} catch(Exception $e)
{
throw new Exception($e->getMessage() . ' - ' . $file->getClientOriginalName() );
}
$errorMessages = null;
foreach($rows as $row)
{
if($entity=='timesheet')
{
$publicId = false;
$this->repository->save($publicId, $row);
} else {
$this->repository->save($row);
}
}
return $file->getClientOriginalName().' '.$errorMessages;
}
private function parseCSV($file)
{
if ($file == null)
throw new Exception(trans('texts.select_file'));
$name = $file->getRealPath();
require_once app_path().'/Includes/parsecsv.lib.php';
$csv = new parseCSV();
$csv->heading = false;
$csv->auto($name);
//Review this code later. Free users can only have 100 clients.
/*
if (count($csv->data) + Client::scope()->count() > Auth::user()->getMaxNumClients()) {
$message = trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()]);
}
*/
return $csv->data;
}
/**
* @param $data
* Header of the Freshbook CSV File
* @param $ignore_header
* @return mixed
*/
private function mapCsvToModel($data, $ignore_header)
{
if($ignore_header)
{
$header = array_shift($data);
$this->transformer->validateHeader($header);
}
$resource = $this->transformer->transform($data);
$data = $this->fractal->createData($resource)->toArray();
return $data['data'];
}
public function createTransformer($type)
{
if (!array_key_exists($type, $this->transformerList)) {
throw new \InvalidArgumentException("$type is not a valid Transformer");
}
$className = $this->transformerList[$type];
return new $className();
}
public function createRepository($type)
{
if (!array_key_exists($type, $this->repositoryList)) {
throw new \InvalidArgumentException("$type is not a valid Repository");
}
$className = $this->repositoryList[$type];
return $this->container->make($className);
//return new $className();
}
}

View File

@ -1,103 +1,40 @@
<?php <?php namespace App\Ninja\Import\FreshBooks;
/**
* Created by PhpStorm.
* User: eduardocruz
* Date: 11/9/15
* Time: 11:47
*/
namespace app\Ninja\Import\FreshBooks;
use App\Models\Client;
use Exception;
use League\Fractal\Resource\Collection;
use League\Fractal\TransformerAbstract; use League\Fractal\TransformerAbstract;
use stdClass; use League\Fractal\Resource\Item;
use App\Models\Client;
class InvoiceTransformer extends TransformerAbstract class InvoiceTransformer extends TransformerAbstract
{ {
public function transform($data, $maps)
public function transform($data)
{ {
return new Collection($data, function(array $data) { if (isset($maps[ENTITY_INVOICE][$data->invoice_number])) {
$data = $this->arrayToObject($data); return false;
$client = Client::where('name', $data->organization)->orderBy('created_at', 'desc')->first(); }
$data->client_id = $client->id;
$data->user_id = $client->user_id; if (isset($maps[ENTITY_CLIENT][$data->organization])) {
$data->account_id = $client->account_id; $data->client_id = $maps[ENTITY_CLIENT][$data->organization];
$create_date = new \DateTime($data->create_date); } else {
$data->create_date = date_format($create_date, DEFAULT_DATE_FORMAT); return false;
}
return new Item($data, function ($data) use ($maps) {
return [ return [
'invoice_number' => $data->invoice_number !== array() ? $data->invoice_number : '', 'invoice_number' => $data->invoice_number,
'client_id' => (int)$data->client_id !== array() ? $data->client_id : '', 'paid' => (float) $data->paid,
'user_id' => (int)$data->user_id !== array() ? $data->user_id : '', 'client_id' => (int) $data->client_id,
'account_id' => (int)$data->account_id !== array() ? $data->account_id : '', 'po_number' => $data->po_number,
'amount' => (int)$data->amount !== array() ? $data->amount : '', 'terms' => $data->terms,
'po_number' => $data->po_number !== array() ? $data->po_number : '', 'public_notes' => $data->notes,
'terms' => $data->terms !== array() ? $data->terms : '', 'invoice_date_sql' => $data->create_date,
'public_notes' => $data->notes !== array() ? $data->notes : '',
//Best guess on required fields
'invoice_date' => $data->create_date !== array() ? $data->create_date : '',
'due_date' => $data->create_date !== array() ? $data->create_date : '',
'discount' => 0,
'invoice_footer' => '',
'invoice_design_id' => 1,
'invoice_items' => '',
'is_amount_discount' => 0,
'partial' => 0,
'invoice_items' => [ 'invoice_items' => [
[ [
'product_key' => '', 'notes' => $data->notes,
'notes' => $data->notes !== array() ? $data->notes : '', 'cost' => (float) $data->amount,
'task_public_id' => '',
'cost' => (int)$data->amount !== array() ? $data->amount : '',
'qty' => 1, 'qty' => 1,
'tax' => '',
'tax_name' => '',
'tax_rate' => 0
] ]
], ],
]; ];
}); });
} }
private function arrayToObject($array)
{
$object = new stdClass();
$object->invoice_number = $array[0];
$object->organization = $array[1];
$object->fname = $array[2];
$object->lname = $array[3];
$object->amount = $array[4];
$object->paid = $array[5];
$object->po_number = $array[6];
$object->create_date = $array[7];
$object->date_paid = $array[8];
$object->terms = $array[9];
$object->notes = $array[10];
return $object;
}
public function validateHeader($csvHeader)
{
$header = [0 => "invoice_number",
1 => "organization",
2 => "fname",
3 => "lname",
4 => "amount",
5 => "paid",
6 => "po_number",
7 => "create_date",
8 => "date_paid",
9 => "terms",
10 => "notes"];
if(!empty(array_diff($header, $csvHeader)))
throw new Exception(trans('texts.invalid_csv_header'));
}
} }

View File

@ -0,0 +1,19 @@
<?php namespace App\Ninja\Import\FreshBooks;
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,
];
});
}
}

View File

@ -1,88 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: eduardocruz
* Date: 11/9/15
* Time: 11:47
*/
namespace app\Ninja\Import\FreshBooks;
use League\Fractal\TransformerAbstract;
use League\Fractal\Resource\Collection;
use stdClass;
use Illuminate\Support\Facades\Auth;
use Exception;
class StaffTransformer extends TransformerAbstract
{
public function transform($data)
{
return new Collection($data, function(array $data) {
$data = $this->arrayToObject($data);
return [
'account_id' => Auth::user()->account_id,
'first_name' => $data->fname !== array() ? $data->fname : '',
'last_name' => $data->lname !== array() ? $data->lname : '',
'phone' => $data->bus_phone !== array() ? $data->bus_phone : $data->mob_phone,
'username' => $data->email !== array() ? $data->email : '',
'email' => $data->email !== array() ? $data->email : '',
];
});
}
private function arrayToObject($array)
{
$object = new stdClass();
$object->fname = $array[0];
$object->lname = $array[1];
$object->email = $array[2];
$object->p_stret = $array[3];
$object->p_street2 = $array[4];
$object->p_city = $array[5];
$object->p_province = $array[6];
$object->p_country = $array[7];
$object->p_code = $array[8];
$object->bus_phone = $array[9];
$object->home_phone = $array[10];
$object->mob_phone = $array[11];
$object->fax = $array[12];
$object->s_street = $array[13];
$object->s_street2 = $array[14];
$object->s_city = $array[15];
$object->s_province = $array[16];
$object->s_country = $array[17];
$object->s_code = $array[18];
return $object;
}
public function validateHeader($csvHeader)
{
$header = [0 => "fname",
1 => "lname",
2 => "email",
3 => "p_stret",
4 => "p_street2",
5 => "p_city",
6 => "p_province",
7 => "p_country",
8 => "p_code",
9 => "bus_phone",
10 => "home_phone",
11 => "mob_phone",
12 => "fax",
13 => "s_street",
14 => "s_street2",
15 => "s_city",
16 => "s_province",
17 => "s_country",
18 => "s_code"];
if(empty($difference))
return;
$difference = array_diff($header, $csvHeader);
$difference = implode(',', $difference);
throw new Exception(trans('texts.invalid_csv_header') . " - $difference - ");
}
}

View File

@ -0,0 +1,29 @@
<?php namespace App\Ninja\Import\FreshBooks;
use League\Fractal\TransformerAbstract;
use Illuminate\Support\Facades\Auth;
/*
class TaskTransformer extends TransformerAbstract
{
public function transform($data)
{
// start by converting to seconds
$seconds = ($data->hours * 3600);
$timeLogFinish = strtotime($data->date);
$timeLogStart = intval($timeLogFinish - $seconds);
$timeLog[] = [];
$timelog[] = $timeLogStart;
$timelog[] = $timeLogFinish;
$timeLog = json_encode(array($timelog));
return [
'action' => 'stop',
'time_log' => $timeLog,
'description' => $data->task,
];
}
}
*/

View File

@ -1,68 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: eduardocruz
* Date: 11/9/15
* Time: 11:47
*/
namespace app\Ninja\Import\FreshBooks;
use League\Fractal\TransformerAbstract;
use League\Fractal\Resource\Collection;
use stdClass;
use Illuminate\Support\Facades\Auth;
class TimesheetTransformer extends TransformerAbstract
{
public function transform($data)
{
return new Collection($data, function(array $data) {
$data = $this->arrayToObject($data);
// start by converting to seconds
$seconds = ($data->hours * 3600);
$timeLogFinish = strtotime($data->date);
$timeLogStart = intval($timeLogFinish - $seconds);
$timeLog[] = [];
$timelog[] = $timeLogStart;
$timelog[] = $timeLogFinish;
//dd(json_decode("[[$timeLogStart,$timeLogFinish]]"));
$timeLog = json_encode(array($timelog));
return [
'action' => 'stop',
'time_log' => $timeLog !== array() ? $timeLog : '',
'user_id' => Auth::user()->id,
'description' => $data->task !== array() ? $data->task : '',
];
});
}
private function arrayToObject($array)
{
$object = new stdClass();
$object->fname = $array[0];
$object->lname = $array[1];
$object->date = $array[2];
$object->project = $array[3];
$object->task = $array[4];
$object->hours = $array[5];
return $object;
}
public function validateHeader($csvHeader)
{
$header = [
0 => "fname",
1 => "lname",
2 => "date",
3 => "project",
4 => "task",
5 => "hours"];
if(!empty(array_diff($header, $csvHeader)))
throw new Exception(trans('texts.invalid_csv_header'));
}
}

View File

@ -12,6 +12,15 @@ class ClientRepository extends BaseRepository
return 'App\Models\Client'; return 'App\Models\Client';
} }
public function all()
{
return Client::scope()
->with('user', 'contacts', 'country')
->withTrashed()
->where('is_deleted', '=', false)
->get();
}
public function find($filter = null) public function find($filter = null)
{ {
$query = \DB::table('clients') $query = \DB::table('clients')

View File

@ -22,6 +22,16 @@ class InvoiceRepository extends BaseRepository
$this->paymentService = $paymentService; $this->paymentService = $paymentService;
} }
public function all()
{
return Invoice::scope()
->with('user', 'client.contacts', 'invoice_status')
->withTrashed()
->where('is_quote', '=', false)
->where('is_recurring', '=', false)
->get();
}
public function getInvoices($accountId, $clientPublicId = false, $entityType = ENTITY_INVOICE, $filter = false) public function getInvoices($accountId, $clientPublicId = false, $entityType = ENTITY_INVOICE, $filter = false)
{ {
$query = \DB::table('invoices') $query = \DB::table('invoices')
@ -156,9 +166,15 @@ class InvoiceRepository extends BaseRepository
$invoice->invoice_number = trim($data['invoice_number']); $invoice->invoice_number = trim($data['invoice_number']);
} }
if (isset($data['discount'])) {
$invoice->discount = round(Utils::parseFloat($data['discount']), 2); $invoice->discount = round(Utils::parseFloat($data['discount']), 2);
}
if (isset($data['is_amount_discount'])) {
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false; $invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
}
if (isset($data['partial'])) {
$invoice->partial = round(Utils::parseFloat($data['partial']), 2); $invoice->partial = round(Utils::parseFloat($data['partial']), 2);
}
$invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']); $invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']);
if ($invoice->is_recurring) { if ($invoice->is_recurring) {
@ -172,14 +188,16 @@ class InvoiceRepository extends BaseRepository
$invoice->due_date = null; $invoice->due_date = null;
$invoice->auto_bill = isset($data['auto_bill']) && $data['auto_bill'] ? true : false; $invoice->auto_bill = isset($data['auto_bill']) && $data['auto_bill'] ? true : false;
} else { } else {
if (isset($data['due_date']) || isset($data['due_date_sql'])) {
$invoice->due_date = isset($data['due_date_sql']) ? $data['due_date_sql'] : Utils::toSqlDate($data['due_date']); $invoice->due_date = isset($data['due_date_sql']) ? $data['due_date_sql'] : Utils::toSqlDate($data['due_date']);
}
$invoice->frequency_id = 0; $invoice->frequency_id = 0;
$invoice->start_date = null; $invoice->start_date = null;
$invoice->end_date = null; $invoice->end_date = null;
} }
$invoice->terms = trim($data['terms']) ? trim($data['terms']) : (!$publicId && $account->invoice_terms ? $account->invoice_terms : ''); $invoice->terms = trim($data['terms']) ? trim($data['terms']) : (!$publicId && $account->invoice_terms ? $account->invoice_terms : '');
$invoice->invoice_footer = trim($data['invoice_footer']) ? trim($data['invoice_footer']) : (!$publicId && $account->invoice_footer ? $account->invoice_footer : ''); $invoice->invoice_footer = (isset($data['invoice_footer']) && trim($data['invoice_footer'])) ? trim($data['invoice_footer']) : (!$publicId && $account->invoice_footer ? $account->invoice_footer : '');
$invoice->public_notes = trim($data['public_notes']); $invoice->public_notes = trim($data['public_notes']);
// process date variables // process date variables
@ -188,7 +206,7 @@ class InvoiceRepository extends BaseRepository
$invoice->public_notes = Utils::processVariables($invoice->public_notes); $invoice->public_notes = Utils::processVariables($invoice->public_notes);
$invoice->po_number = trim($data['po_number']); $invoice->po_number = trim($data['po_number']);
$invoice->invoice_design_id = $data['invoice_design_id']; $invoice->invoice_design_id = isset($data['invoice_design_id']) ? $data['invoice_design_id'] : $account->invoice_design_id;
if (isset($data['tax_name']) && isset($data['tax_rate']) && $data['tax_name']) { if (isset($data['tax_name']) && isset($data['tax_rate']) && $data['tax_name']) {
$invoice->tax_rate = Utils::parseFloat($data['tax_rate']); $invoice->tax_rate = Utils::parseFloat($data['tax_rate']);
@ -306,7 +324,7 @@ class InvoiceRepository extends BaseRepository
$task->invoice_id = $invoice->id; $task->invoice_id = $invoice->id;
$task->client_id = $invoice->client_id; $task->client_id = $invoice->client_id;
$task->save(); $task->save();
} else if ($item['product_key'] && !$invoice->has_tasks) { } else if (isset($item['product_key']) && $item['product_key'] && !$invoice->has_tasks) {
$product = Product::findProductByKey(trim($item['product_key'])); $product = Product::findProductByKey(trim($item['product_key']));
if (\Auth::user()->account->update_products) { if (\Auth::user()->account->update_products) {
@ -323,7 +341,7 @@ class InvoiceRepository extends BaseRepository
$invoiceItem = InvoiceItem::createNew(); $invoiceItem = InvoiceItem::createNew();
$invoiceItem->product_id = isset($product) ? $product->id : null; $invoiceItem->product_id = isset($product) ? $product->id : null;
$invoiceItem->product_key = trim($invoice->is_recurring ? $item['product_key'] : Utils::processVariables($item['product_key'])); $invoiceItem->product_key = isset($item['product_key']) ? (trim($invoice->is_recurring ? $item['product_key'] : Utils::processVariables($item['product_key']))) : '';
$invoiceItem->notes = trim($invoice->is_recurring ? $item['notes'] : Utils::processVariables($item['notes'])); $invoiceItem->notes = trim($invoice->is_recurring ? $item['notes'] : Utils::processVariables($item['notes']));
$invoiceItem->cost = Utils::parseFloat($item['cost']); $invoiceItem->cost = Utils::parseFloat($item['cost']);
$invoiceItem->qty = Utils::parseFloat($item['qty']); $invoiceItem->qty = Utils::parseFloat($item['qty']);

View File

@ -116,10 +116,12 @@ class PaymentRepository extends BaseRepository
$payment->payment_date = date('Y-m-d'); $payment->payment_date = date('Y-m-d');
} }
if (isset($input['transaction_reference'])) {
$payment->transaction_reference = trim($input['transaction_reference']); $payment->transaction_reference = trim($input['transaction_reference']);
}
if (!$publicId) { if (!$publicId) {
$clientId = Client::getPrivateId($input['client']); $clientId = Client::getPrivateId(isset($input['client_id']) ? $input['client_id'] : $input['client']);
$amount = Utils::parseFloat($input['amount']); $amount = Utils::parseFloat($input['amount']);
if ($paymentTypeId == PAYMENT_TYPE_CREDIT) { if ($paymentTypeId == PAYMENT_TYPE_CREDIT) {
@ -137,8 +139,10 @@ class PaymentRepository extends BaseRepository
} }
$payment->client_id = $clientId; $payment->client_id = $clientId;
$payment->invoice_id = isset($input['invoice']) && $input['invoice'] != "-1" ? Invoice::getPrivateId($input['invoice']) : null;
$payment->amount = $amount; $payment->amount = $amount;
$invoicePublicId = isset($input['invoice_id']) ? $input['invoice_id'] : $input['invoice'];
$payment->invoice_id = Invoice::getPrivateId($invoicePublicId);
} }
$payment->save(); $payment->save();

View File

@ -0,0 +1,119 @@
<?php namespace App\Services;
use Excel;
use Cache;
use Exception;
use League\Fractal\Manager;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Serializers\ArraySerializer;
class ImportService
{
protected $transformer;
protected $invoiceRepo;
protected $clientRepo;
public static $entityTypes = [
ENTITY_CLIENT,
ENTITY_INVOICE,
ENTITY_TASK,
];
public static $sources = [
IMPORT_CSV,
IMPORT_FRESHBOOKS,
//IMPORT_HARVEST,
//IMPORT_HIVEAGE,
//IMPORT_INVOICEABLE,
//IMPORT_NUTCACHE,
//IMPORT_RONIN,
//IMPORT_WAVE,
//IMPORT_ZOHO,
];
/**
* FreshBooksDataImporterService constructor.
*/
public function __construct(Manager $manager, ClientRepository $clientRepo, InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo)
{
$this->fractal = $manager;
$this->fractal->setSerializer(new ArraySerializer());
$this->clientRepo = $clientRepo;
$this->invoiceRepo = $invoiceRepo;
$this->paymentRepo = $paymentRepo;
}
public function import($source, $files)
{
$imported_files = null;
foreach ($files as $entityType => $file) {
$this->execute($source, $entityType, $file);
}
}
private function execute($source, $entityType, $file)
{
$transformerClassName = $this->getTransformerClassName($source, $entityType);
$transformer = new $transformerClassName;
Excel::load($file, function($reader) use ($source, $entityType, $transformer) {
$maps = $this->createMaps();
$reader->each(function($row) use ($source, $entityType, $transformer, $maps) {
if ($resource = $transformer->transform($row, $maps)) {
$data = $this->fractal->createData($resource)->toArray();
$entity = $this->{"{$entityType}Repo"}->save($data);
// if the invoice is paid we'll also create a payment record
if ($entityType === ENTITY_INVOICE && isset($data['paid']) && $data['paid']) {
$class = self::getTransformerClassName($source, 'payment');
$paymentTransformer = new $class;
$row->client_id = $data['client_id'];
$row->invoice_id = $entity->public_id;
if ($resource = $paymentTransformer->transform($row, $maps)) {
$data = $this->fractal->createData($resource)->toArray();
$this->paymentRepo->save($data);
}
}
}
});
});
}
private function createMaps()
{
$clientMap = [];
$clients = $this->clientRepo->all();
foreach ($clients as $client) {
$clientMap[$client->name] = $client->public_id;
}
$invoiceMap = [];
$invoices = $this->invoiceRepo->all();
foreach ($invoices as $invoice) {
$invoiceMap[$invoice->invoice_number] = $invoice->public_id;
}
$countryMap = [];
$countries = Cache::get('countries');
foreach ($countries as $country) {
$countryMap[$country->name] = $country->id;
}
return [
ENTITY_CLIENT => $clientMap,
ENTITY_INVOICE => $invoiceMap,
'countries' => $countryMap,
];
}
public static function getTransformerClassName($source, $entityType)
{
return 'App\\Ninja\\Import\\' . $source . '\\' . ucwords($entityType) . 'Transformer';
}
}

View File

@ -434,7 +434,7 @@ class ConfideSetupUsersTable extends Migration {
Schema::create('payments', function($t) Schema::create('payments', function($t)
{ {
$t->increments('id'); $t->increments('id');
$t->unsignedInteger('invoice_id')->nullable(); $t->unsignedInteger('invoice_id')->index();
$t->unsignedInteger('account_id')->index(); $t->unsignedInteger('account_id')->index();
$t->unsignedInteger('client_id')->index(); $t->unsignedInteger('client_id')->index();
$t->unsignedInteger('contact_id')->nullable(); $t->unsignedInteger('contact_id')->nullable();

View File

@ -115,7 +115,8 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'Mexican Peso', 'code' => 'MXN', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Mexican Peso', 'code' => 'MXN', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Egyptian Pound', 'code' => 'EGP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Egyptian Pound', 'code' => 'EGP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Colombian Peso', 'code' => 'COP', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], ['name' => 'Colombian Peso', 'code' => 'COP', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'West African Franc', 'code' => 'XOF', 'symbol' => 'CFA ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], ['name' => 'West African Franc', 'code' => 'XOF', 'symbol' => 'CFA ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Chinese Renminbi', 'code' => 'CNY', 'symbol' => 'RMB ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
]; ];
foreach ($currencies as $currency) { foreach ($currencies as $currency) {

View File

@ -3378,3 +3378,7 @@ ul.user-accounts a:hover div.remove {
content: ""; content: "";
background-color: #e37329; background-color: #e37329;
} }
div.panel-body div.panel-body {
padding-bottom: 0px;
}

View File

@ -1027,3 +1027,7 @@ ul.user-accounts a:hover div.remove {
content: ""; content: "";
background-color: #e37329; background-color: #e37329;
} }
div.panel-body div.panel-body {
padding-bottom: 0px;
}

View File

@ -207,15 +207,8 @@ return array(
//import CSV data pages //import CSV data pages
'import_clients' => 'Import Client Data', 'import_clients' => 'Import Client Data',
'import_from_freshbooks' => 'Import From FreshBooks', 'csv_file' => 'CSV file',
'csv_file' => 'Select CSV file',
'csv_client_file' => 'Select CSV Client file',
'csv_invoice_file' => 'Select CSV Invoice file',
'csv_timesheet_file' => 'Select CSV Timesheet file',
'export_clients' => 'Export Client Data', 'export_clients' => 'Export Client Data',
'no_mapper' => 'No valid mapping for file',
'invalid_csv_header' => 'Invalid CSV Header',
// application messages // application messages
'created_client' => 'Successfully created client', 'created_client' => 'Successfully created client',
@ -796,7 +789,7 @@ return array(
'invoice_not_found' => 'The requested invoice is not available', 'invoice_not_found' => 'The requested invoice is not available',
'referral_program' => 'Referral Program', 'referral_program' => 'Referral Program',
'referral_code' => 'Referral Code', 'referral_code' => 'Referral URL',
'last_sent_on' => 'Sent Last: :date', 'last_sent_on' => 'Sent Last: :date',
'page_expire' => 'This page will expire soon, :click_here to keep working', 'page_expire' => 'This page will expire soon, :click_here to keep working',
@ -929,6 +922,16 @@ return array(
'include' => 'Include', 'include' => 'Include',
'logo_too_large' => 'Your logo is :size, for better performance we suggest uploading an image file less than 200KB', 'logo_too_large' => 'Your logo is :size, for better performance we suggest uploading an image file less than 200KB',
'import_freshbooks' => 'Import From FreshBooks',
'import_data' => 'Import Data',
'source' => 'Source',
'csv' => 'CSV',
'client_file' => 'Client File',
'invoice_file' => 'Invoice File',
'task_file' => 'Task File',
'no_mapper' => 'No valid mapping for file',
'invalid_csv_header' => 'Invalid CSV Header',
'email_errors' => [ 'email_errors' => [
'inactive_client' => 'Emails can not be sent to inactive clients', 'inactive_client' => 'Emails can not be sent to inactive clients',
'inactive_contact' => 'Emails can not be sent to inactive contacts', 'inactive_contact' => 'Emails can not be sent to inactive contacts',

View File

@ -1,35 +1,44 @@
@extends('header') @extends('header')
@section('head')
@parent
<style type="text/css">
.client-file,
.task-file {
display: none;
}
</style>
@stop
@section('content') @section('content')
@parent @parent
@include('accounts.nav', ['selected' => ACCOUNT_IMPORT_EXPORT]) @include('accounts.nav', ['selected' => ACCOUNT_IMPORT_EXPORT])
{!! Former::open_for_files('settings/' . ACCOUNT_MAP) !!}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.import_clients') !!}</h3> <h3 class="panel-title">{!! trans('texts.import_data') !!}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
{!! Former::file('file')->label(trans('texts.csv_file')) !!}
{!! Former::actions( Button::info(trans('texts.upload'))->submit()->large()->appendIcon(Icon::create('open'))) !!}
</div>
</div>
{!! Former::close() !!}
{!! Former::open_for_files('settings/' . IMPORT_FROM_FRESHBOOKS) !!} {!! Former::open_for_files('/import') !!}
<div class="panel panel-default"> {!! Former::select('source')
<div class="panel-heading"> ->onchange('setFileTypesVisible()')
<h3 class="panel-title">{!! trans('texts.import_from_freshbooks') !!}</h3> ->options(array_combine(\App\Services\ImportService::$sources, \App\Services\ImportService::$sources))
</div> ->style('width: 200px') !!}
<div class="panel-body">
{!! Former::file('client_file')->label(trans('texts.csv_client_file')) !!} @foreach (\App\Services\ImportService::$entityTypes as $entityType)
{!! Former::file('invoice_file')->label(trans('texts.csv_invoice_file')) !!} {!! Former::file("{$entityType}_file")
{!! Former::file('timesheet_file')->label(trans('texts.csv_timesheet_file')) !!} ->addGroupClass("{$entityType}-file") !!}
@endforeach
{!! Former::actions( Button::info(trans('texts.upload'))->submit()->large()->appendIcon(Icon::create('open'))) !!} {!! Former::actions( Button::info(trans('texts.upload'))->submit()->large()->appendIcon(Icon::create('open'))) !!}
{!! Former::close() !!}
</div> </div>
</div> </div>
{!! Former::close() !!}
{!! Former::open('/export') !!} {!! Former::open('/export') !!}
@ -115,6 +124,26 @@
} }
} }
function setFileTypesVisible() {
var val = $('#source').val();
@foreach (\App\Services\ImportService::$entityTypes as $entityType)
$('.{{ $entityType }}-file').hide();
@endforeach
@foreach (\App\Services\ImportService::$sources as $source)
if (val === '{{ $source }}') {
@if ($source == IMPORT_CSV)
$('.client-file').show();
@else
@foreach (\App\Services\ImportService::$entityTypes as $entityType)
@if (class_exists(\App\Services\ImportService::getTransformerClassName($source, $entityType)))
$('.{{ $entityType }}-file').show();
@endif
@endforeach
@endif
}
@endforeach
}
</script> </script>
@stop @stop

View File

@ -51,11 +51,10 @@
@if ($user->referral_code) @if ($user->referral_code)
{{ Former::setOption('capitalize_translations', false) }} {{ Former::setOption('capitalize_translations', false) }}
{!! Former::plaintext('referral_code') {!! Former::plaintext('referral_code')
->help(NINJA_APP_URL . '/invoice_now?rc=' . $user->referral_code) ->help($referralCounts['free'] . ' ' . trans('texts.free') . ' | ' .
->value($user->referral_code . ' - '. $referralCounts['pro'] . ' ' . trans('texts.pro') .
$referralCounts['free'] . ' ' . trans('texts.free') . ' | ' . '<a href="'.REFERRAL_PROGRAM_URL.'" target="_blank" title="'.trans('texts.learn_more').'">' . Icon::create('question-sign') . '</a> ')
$referralCounts['pro'] . ' ' . trans('texts.pro') . ' ' . ->value(NINJA_APP_URL . '/invoice_now?rc=' . $user->referral_code) !!}
'<a href="'.REFERRAL_PROGRAM_URL.'" target="_blank" title="'.trans('texts.learn_more').'">' . Icon::create('question-sign') . '</a>') !!}
@else @else
{!! Former::checkbox('referral_code') {!! Former::checkbox('referral_code')
->help(trans('texts.referral_code_help')) ->help(trans('texts.referral_code_help'))