diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 56b698b6d8a0..50535beb848e 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -37,7 +37,6 @@ use App\Models\Timezone; use App\Models\Industry; use App\Models\InvoiceDesign; use App\Models\TaxRate; -use App\Ninja\Import\DataImporterServiceInterface; use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ReferralRepository; @@ -57,7 +56,7 @@ class AccountController extends BaseController protected $contactMailer; 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(); @@ -66,7 +65,6 @@ class AccountController extends BaseController $this->contactMailer = $contactMailer; $this->referralRepository = $referralRepository; $this->clientRepository = $clientRepository; - $this->dataImporterService = $dataImporterService; } public function demo() @@ -417,8 +415,6 @@ class AccountController extends BaseController return AccountController::saveLocalization(); } elseif ($section === ACCOUNT_IMPORT_EXPORT) { return AccountController::importFile(); - } elseif ($section === IMPORT_FROM_FRESHBOOKS) { - return AccountController::importData(); } elseif ($section === ACCOUNT_MAP) { return AccountController::mapFile(); } elseif ($section === ACCOUNT_NOTIFICATIONS) { @@ -737,26 +733,6 @@ class AccountController extends BaseController 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() { $file = Input::file('file'); diff --git a/app/Http/Controllers/ImportExportController.php b/app/Http/Controllers/ExportController.php similarity index 99% rename from app/Http/Controllers/ImportExportController.php rename to app/Http/Controllers/ExportController.php index fdab81f4d9ef..79d5f82fa341 100644 --- a/app/Http/Controllers/ImportExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -14,7 +14,7 @@ use App\Models\Task; use App\Models\Invoice; use App\Models\Payment; -class ImportExportController extends BaseController +class ExportController extends BaseController { public function doExport(Request $request) { diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php new file mode 100644 index 000000000000..67ab454b1b14 --- /dev/null +++ b/app/Http/Controllers/ImportController.php @@ -0,0 +1,38 @@ +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); + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 16826fc7e7fe..b6f4c0efbafa 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -131,7 +131,9 @@ Route::group(['middleware' => 'auth'], function() { Route::post('user/setTheme', 'UserController@setTheme'); Route::post('remove_logo', 'AccountController@removeLogo'); 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::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_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_PRO', 20000); @@ -432,7 +442,7 @@ if (!defined('CONTACT_EMAIL')) { define('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html'); define('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/single/browser/v1/'); 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_SELF_HOST', 5); // include the custom design diff --git a/app/Listeners/InvoiceListener.php b/app/Listeners/InvoiceListener.php index 0a5a19c5bd0a..55759ba82ff8 100644 --- a/app/Listeners/InvoiceListener.php +++ b/app/Listeners/InvoiceListener.php @@ -23,7 +23,7 @@ class InvoiceListener public function updatedInvoice(InvoiceWasUpdated $event) { $invoice = $event->invoice; - $invoice->updatePaidStatus(); + $invoice->updatePaidStatus(false); } public function viewedInvoice(InvoiceInvitationWasViewed $event) diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index a49f2636fc46..ee91bc8afab1 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -192,14 +192,18 @@ class Invoice extends EntityModel implements BalanceAffecting } } - public function updatePaidStatus() + public function updatePaidStatus($save = true) { if ($this->isPaid() && $this->balance > 0) { $this->invoice_status_id = ($this->balance == $this->amount ? INVOICE_STATUS_SENT : INVOICE_STATUS_PARTIAL); - $this->save(); + if ($save) { + $this->save(); + } } 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->save(); + if ($save) { + $this->save(); + } } } diff --git a/app/Ninja/Import/DataImporterServiceInterface.php b/app/Ninja/Import/DataImporterServiceInterface.php deleted file mode 100644 index 7d87306fd7ac..000000000000 --- a/app/Ninja/Import/DataImporterServiceInterface.php +++ /dev/null @@ -1,15 +0,0 @@ -arrayToObject($data); + if (isset($maps[ENTITY_CLIENT][$data->organization])) { + 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' => $data->organization !== array() ? $data->organization : '', - 'work_phone' => $data->busPhone !== array() ? $data->busPhone : '', - 'address1' => $data->street !== array() ? $data->street : '', - 'address2' => $data->street2 !== array() ? $data->street2 : '', - 'city' => $data->city !== array() ? $data->city : '', - 'state' => $data->province !== array() ? $data->province : '', - 'postal_code' => $data->postalCode !== array() ? $data->postalCode : '', - 'private_notes' => $data->notes !== array() ? $data->notes : '', + 'name' => $data->organization, + 'work_phone' => $data->busphone, + 'address1' => $data->street, + 'address2' => $data->street2, + 'city' => $data->city, + 'state' => $data->province, + 'postal_code' => $data->postalcode, + 'private_notes' => $data->notes, 'contacts' => [ [ - 'public_id' => '', - 'first_name' => $data->firstName !== array() ? $data->firstName : '', - 'last_name' => $data->lastName !== array() ? $data->lastName : '', - 'email' => $data->email !== array() ? $data->email : '', - 'phone' => $data->mobPhone !== array() ? $data->mobPhone : $data->homePhone, - ] + 'first_name' => $data->firstname, + 'last_name' => $data->lastname, + 'email' => $data->email, + 'phone' => $data->mobphone ?: $data->homephone, + ], ], - 'country_id' => !Country::where('name', $data->country) - ->get() - ->isEmpty() ? Country::where('name', $data->country) - ->first()->id : null, + 'country_id' => $data->country_id, ]; }); } - - 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')); - } - - -} \ No newline at end of file +} diff --git a/app/Ninja/Import/FreshBooks/FreshBooksDataImporterService.php b/app/Ninja/Import/FreshBooks/FreshBooksDataImporterService.php deleted file mode 100644 index caa8370945a0..000000000000 --- a/app/Ninja/Import/FreshBooks/FreshBooksDataImporterService.php +++ /dev/null @@ -1,154 +0,0 @@ -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(); - } -} \ No newline at end of file diff --git a/app/Ninja/Import/FreshBooks/InvoiceTransformer.php b/app/Ninja/Import/FreshBooks/InvoiceTransformer.php index 2e8519ce7fb0..5793ae053477 100644 --- a/app/Ninja/Import/FreshBooks/InvoiceTransformer.php +++ b/app/Ninja/Import/FreshBooks/InvoiceTransformer.php @@ -1,103 +1,40 @@ -arrayToObject($data); - $client = Client::where('name', $data->organization)->orderBy('created_at', 'desc')->first(); - $data->client_id = $client->id; - $data->user_id = $client->user_id; - $data->account_id = $client->account_id; - $create_date = new \DateTime($data->create_date); - $data->create_date = date_format($create_date, DEFAULT_DATE_FORMAT); + if (isset($maps[ENTITY_INVOICE][$data->invoice_number])) { + return false; + } + + if (isset($maps[ENTITY_CLIENT][$data->organization])) { + $data->client_id = $maps[ENTITY_CLIENT][$data->organization]; + } else { + return false; + } + + return new Item($data, function ($data) use ($maps) { return [ - 'invoice_number' => $data->invoice_number !== array() ? $data->invoice_number : '', - 'client_id' => (int)$data->client_id !== array() ? $data->client_id : '', - 'user_id' => (int)$data->user_id !== array() ? $data->user_id : '', - 'account_id' => (int)$data->account_id !== array() ? $data->account_id : '', - 'amount' => (int)$data->amount !== array() ? $data->amount : '', - 'po_number' => $data->po_number !== array() ? $data->po_number : '', - 'terms' => $data->terms !== array() ? $data->terms : '', - '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_number' => $data->invoice_number, + 'paid' => (float) $data->paid, + 'client_id' => (int) $data->client_id, + 'po_number' => $data->po_number, + 'terms' => $data->terms, + 'public_notes' => $data->notes, + 'invoice_date_sql' => $data->create_date, 'invoice_items' => [ [ - 'product_key' => '', - 'notes' => $data->notes !== array() ? $data->notes : '', - 'task_public_id' => '', - 'cost' => (int)$data->amount !== array() ? $data->amount : '', - 'qty' => 1, - 'tax' => '', - 'tax_name' => '', - 'tax_rate' => 0 + 'notes' => $data->notes, + 'cost' => (float) $data->amount, + 'qty' => 1, ] ], - ]; }); } - - 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')); - } - - } \ No newline at end of file diff --git a/app/Ninja/Import/FreshBooks/PaymentTransformer.php b/app/Ninja/Import/FreshBooks/PaymentTransformer.php new file mode 100644 index 000000000000..1adb09df0634 --- /dev/null +++ b/app/Ninja/Import/FreshBooks/PaymentTransformer.php @@ -0,0 +1,19 @@ + $data->paid, + 'payment_date_sql' => $data->create_date, + 'client_id' => $data->client_id, + 'invoice_id' => $data->invoice_id, + ]; + }); + } +} \ No newline at end of file diff --git a/app/Ninja/Import/FreshBooks/StaffTransformer.php b/app/Ninja/Import/FreshBooks/StaffTransformer.php deleted file mode 100644 index b9dcceef9aae..000000000000 --- a/app/Ninja/Import/FreshBooks/StaffTransformer.php +++ /dev/null @@ -1,88 +0,0 @@ -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 - "); - } -} \ No newline at end of file diff --git a/app/Ninja/Import/FreshBooks/TaskTransformer.php b/app/Ninja/Import/FreshBooks/TaskTransformer.php new file mode 100644 index 000000000000..bf6478367704 --- /dev/null +++ b/app/Ninja/Import/FreshBooks/TaskTransformer.php @@ -0,0 +1,29 @@ +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, + ]; + } + +} +*/ \ No newline at end of file diff --git a/app/Ninja/Import/FreshBooks/TimesheetTransformer.php b/app/Ninja/Import/FreshBooks/TimesheetTransformer.php deleted file mode 100644 index adba19f6946c..000000000000 --- a/app/Ninja/Import/FreshBooks/TimesheetTransformer.php +++ /dev/null @@ -1,68 +0,0 @@ -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')); - } - - -} \ No newline at end of file diff --git a/app/Ninja/Repositories/ClientRepository.php b/app/Ninja/Repositories/ClientRepository.php index 0cea09c554a8..1eadf1f3acea 100644 --- a/app/Ninja/Repositories/ClientRepository.php +++ b/app/Ninja/Repositories/ClientRepository.php @@ -12,6 +12,15 @@ class ClientRepository extends BaseRepository 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) { $query = \DB::table('clients') diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 7bcf9c710e53..be6e625b1f73 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -22,6 +22,16 @@ class InvoiceRepository extends BaseRepository $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) { $query = \DB::table('invoices') @@ -156,9 +166,15 @@ class InvoiceRepository extends BaseRepository $invoice->invoice_number = trim($data['invoice_number']); } - $invoice->discount = round(Utils::parseFloat($data['discount']), 2); - $invoice->is_amount_discount = $data['is_amount_discount'] ? true : false; - $invoice->partial = round(Utils::parseFloat($data['partial']), 2); + if (isset($data['discount'])) { + $invoice->discount = round(Utils::parseFloat($data['discount']), 2); + } + if (isset($data['is_amount_discount'])) { + $invoice->is_amount_discount = $data['is_amount_discount'] ? true : false; + } + if (isset($data['partial'])) { + $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']); if ($invoice->is_recurring) { @@ -172,14 +188,16 @@ class InvoiceRepository extends BaseRepository $invoice->due_date = null; $invoice->auto_bill = isset($data['auto_bill']) && $data['auto_bill'] ? true : false; } else { - $invoice->due_date = isset($data['due_date_sql']) ? $data['due_date_sql'] : Utils::toSqlDate($data['due_date']); + 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->frequency_id = 0; $invoice->start_date = null; $invoice->end_date = null; } $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']); // process date variables @@ -188,7 +206,7 @@ class InvoiceRepository extends BaseRepository $invoice->public_notes = Utils::processVariables($invoice->public_notes); $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']) { $invoice->tax_rate = Utils::parseFloat($data['tax_rate']); @@ -306,7 +324,7 @@ class InvoiceRepository extends BaseRepository $task->invoice_id = $invoice->id; $task->client_id = $invoice->client_id; $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'])); if (\Auth::user()->account->update_products) { @@ -323,7 +341,7 @@ class InvoiceRepository extends BaseRepository $invoiceItem = InvoiceItem::createNew(); $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->cost = Utils::parseFloat($item['cost']); $invoiceItem->qty = Utils::parseFloat($item['qty']); diff --git a/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php index 6153e8dd24cb..4640fe4818f7 100644 --- a/app/Ninja/Repositories/PaymentRepository.php +++ b/app/Ninja/Repositories/PaymentRepository.php @@ -116,10 +116,12 @@ class PaymentRepository extends BaseRepository $payment->payment_date = date('Y-m-d'); } - $payment->transaction_reference = trim($input['transaction_reference']); + if (isset($input['transaction_reference'])) { + $payment->transaction_reference = trim($input['transaction_reference']); + } if (!$publicId) { - $clientId = Client::getPrivateId($input['client']); + $clientId = Client::getPrivateId(isset($input['client_id']) ? $input['client_id'] : $input['client']); $amount = Utils::parseFloat($input['amount']); if ($paymentTypeId == PAYMENT_TYPE_CREDIT) { @@ -137,8 +139,10 @@ class PaymentRepository extends BaseRepository } $payment->client_id = $clientId; - $payment->invoice_id = isset($input['invoice']) && $input['invoice'] != "-1" ? Invoice::getPrivateId($input['invoice']) : null; $payment->amount = $amount; + + $invoicePublicId = isset($input['invoice_id']) ? $input['invoice_id'] : $input['invoice']; + $payment->invoice_id = Invoice::getPrivateId($invoicePublicId); } $payment->save(); diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php new file mode 100644 index 000000000000..d2b00161e4cc --- /dev/null +++ b/app/Services/ImportService.php @@ -0,0 +1,119 @@ +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'; + } +} diff --git a/database/migrations/2013_11_05_180133_confide_setup_users_table.php b/database/migrations/2013_11_05_180133_confide_setup_users_table.php index 6a370bd67127..08a3bbf91b9c 100644 --- a/database/migrations/2013_11_05_180133_confide_setup_users_table.php +++ b/database/migrations/2013_11_05_180133_confide_setup_users_table.php @@ -418,7 +418,7 @@ class ConfideSetupUsersTable extends Migration { $t->string('product_key'); $t->text('notes'); $t->decimal('cost', 13, 2); - $t->decimal('qty', 13, 2)->nullable(); + $t->decimal('qty', 13, 2)->nullable(); $t->string('tax_name')->nullable(); $t->decimal('tax_rate', 13, 2)->nullable(); @@ -434,7 +434,7 @@ class ConfideSetupUsersTable extends Migration { Schema::create('payments', function($t) { $t->increments('id'); - $t->unsignedInteger('invoice_id')->nullable(); + $t->unsignedInteger('invoice_id')->index(); $t->unsignedInteger('account_id')->index(); $t->unsignedInteger('client_id')->index(); $t->unsignedInteger('contact_id')->nullable(); diff --git a/database/seeds/PaymentLibrariesSeeder.php b/database/seeds/PaymentLibrariesSeeder.php index 7bc1ca878e58..415fcf95f79c 100644 --- a/database/seeds/PaymentLibrariesSeeder.php +++ b/database/seeds/PaymentLibrariesSeeder.php @@ -115,7 +115,8 @@ class PaymentLibrariesSeeder extends Seeder ['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' => '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) { diff --git a/public/css/built.css b/public/css/built.css index b17274f6ea32..94e81164bd99 100644 --- a/public/css/built.css +++ b/public/css/built.css @@ -3377,4 +3377,8 @@ ul.user-accounts a:hover div.remove { width: 2px; content: ""; background-color: #e37329; +} + +div.panel-body div.panel-body { + padding-bottom: 0px; } \ No newline at end of file diff --git a/public/css/style.css b/public/css/style.css index 843c10a18d41..345b37e5c761 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1026,4 +1026,8 @@ ul.user-accounts a:hover div.remove { width: 2px; content: ""; background-color: #e37329; +} + +div.panel-body div.panel-body { + padding-bottom: 0px; } \ No newline at end of file diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 90af9d6a304d..4811d984e924 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -207,15 +207,8 @@ return array( //import CSV data pages 'import_clients' => 'Import Client Data', - 'import_from_freshbooks' => 'Import From FreshBooks', - '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', + 'csv_file' => 'CSV file', 'export_clients' => 'Export Client Data', - 'no_mapper' => 'No valid mapping for file', - 'invalid_csv_header' => 'Invalid CSV Header', - // application messages 'created_client' => 'Successfully created client', @@ -796,7 +789,7 @@ return array( 'invoice_not_found' => 'The requested invoice is not available', 'referral_program' => 'Referral Program', - 'referral_code' => 'Referral Code', + 'referral_code' => 'Referral URL', 'last_sent_on' => 'Sent Last: :date', 'page_expire' => 'This page will expire soon, :click_here to keep working', @@ -929,6 +922,16 @@ return array( 'include' => 'Include', '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' => [ 'inactive_client' => 'Emails can not be sent to inactive clients', 'inactive_contact' => 'Emails can not be sent to inactive contacts', diff --git a/resources/views/accounts/import_export.blade.php b/resources/views/accounts/import_export.blade.php index 44396e13d1b1..88638288f450 100644 --- a/resources/views/accounts/import_export.blade.php +++ b/resources/views/accounts/import_export.blade.php @@ -1,35 +1,44 @@ @extends('header') +@section('head') + @parent + + +@stop + + @section('content') @parent @include('accounts.nav', ['selected' => ACCOUNT_IMPORT_EXPORT]) -{!! Former::open_for_files('settings/' . ACCOUNT_MAP) !!}