From 0acf4ff1fc7380165d24c26204c494b06ef2b02d Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 2 Jun 2016 22:03:59 +0300 Subject: [PATCH] Support importing JSON file --- app/Http/Controllers/ExportController.php | 6 +- app/Http/Controllers/ImportController.php | 5 +- app/Http/Controllers/InvoiceController.php | 42 +-- app/Http/Requests/CreatePaymentRequest.php | 8 +- app/Http/routes.php | 1 + app/Listeners/SubscriptionListener.php | 7 +- app/Models/Account.php | 25 +- app/Models/EntityModel.php | 19 +- .../Import/FreshBooks/PaymentTransformer.php | 6 +- .../Import/Hiveage/PaymentTransformer.php | 6 +- .../Import/Invoiceable/PaymentTransformer.php | 6 +- .../Import/Nutcache/PaymentTransformer.php | 6 +- app/Ninja/Import/Ronin/PaymentTransformer.php | 6 +- app/Ninja/Import/Wave/PaymentTransformer.php | 8 +- app/Ninja/Import/Zoho/PaymentTransformer.php | 6 +- app/Ninja/Repositories/ClientRepository.php | 8 +- app/Ninja/Repositories/InvoiceRepository.php | 16 +- app/Providers/EventServiceProvider.php | 2 - app/Providers/RouteServiceProvider.php | 2 +- app/Services/ImportService.php | 264 +++++++++++++----- resources/lang/en/texts.php | 2 + .../views/accounts/import_export.blade.php | 8 +- resources/views/master.blade.php | 36 +-- 23 files changed, 323 insertions(+), 172 deletions(-) diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 244b6e8aee5f..114c52a110ab 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -43,10 +43,12 @@ class ExportController extends BaseController $manager->setSerializer(new ArraySerializer()); $account = Auth::user()->account; - $account->loadAllData(); + $account->load(['clients.contacts', 'clients.invoices.payments', 'clients.invoices.invoice_items']); $resource = new Item($account, new AccountTransformer); - $data = $manager->createData($resource)->toArray(); + $data = $manager->parseIncludes('clients.invoices.payments') + ->createData($resource) + ->toArray(); return response()->json($data); } diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index fe006332ca48..3e53170ced35 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -36,8 +36,11 @@ class ImportController extends BaseController if ($source === IMPORT_CSV) { $data = $this->importService->mapCSV($files); return View::make('accounts.import_map', ['data' => $data]); + } elseif ($source === IMPORT_JSON) { + $results = $this->importService->importJSON($files[IMPORT_JSON]); + return $this->showResult($results); } else { - $results = $this->importService->import($source, $files); + $results = $this->importService->importFiles($source, $files); return $this->showResult($results); } } catch (Exception $exception) { diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 62faaca695b5..da9b90467350 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -95,9 +95,9 @@ class InvoiceController extends BaseController { $account = Auth::user()->account; $invoice = $request->entity()->load('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'payments'); - + $entityType = $invoice->getEntityType(); - + $contactIds = DB::table('invitations') ->join('contacts', 'contacts.id', '=', 'invitations.contact_id') ->where('invitations.invoice_id', '=', $invoice->id) @@ -158,12 +158,12 @@ class InvoiceController extends BaseController if (!$invoice->is_recurring && $invoice->balance > 0) { $actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')]; } - + foreach ($invoice->payments as $payment) { $label = trans("texts.view_payment"); if (count($invoice->payments) > 1) { - $label .= ' - ' . $account->formatMoney($payment->amount, $invoice->client); - } + $label .= ' - ' . $account->formatMoney($payment->amount, $invoice->client); + } $actions[] = ['url' => $payment->present()->url, 'label' => $label]; } } @@ -180,7 +180,7 @@ class InvoiceController extends BaseController if(!Auth::user()->hasPermission('view_all')){ $clients = $clients->where('clients.user_id', '=', Auth::user()->id); } - + $data = array( 'clients' => $clients->get(), 'entityType' => $entityType, @@ -230,7 +230,7 @@ class InvoiceController extends BaseController public function create(InvoiceRequest $request, $clientPublicId = 0, $isRecurring = false) { $account = Auth::user()->account; - + $entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE; $clientId = null; @@ -240,17 +240,17 @@ class InvoiceController extends BaseController $invoice = $account->createInvoice($entityType, $clientId); $invoice->public_id = 0; - + if (Session::get('expenses')) { $invoice->expenses = Expense::scope(Session::get('expenses'))->with('documents')->get(); } - + $clients = Client::scope()->with('contacts', 'country')->orderBy('name'); if (!Auth::user()->hasPermission('view_all')) { $clients = $clients->where('clients.user_id', '=', Auth::user()->id); } - + $data = [ 'clients' => $clients->get(), 'entityType' => $invoice->getEntityType(), @@ -335,26 +335,26 @@ class InvoiceController extends BaseController $rates = TaxRate::scope()->orderBy('name')->get(); $options = []; $defaultTax = false; - + foreach ($rates as $rate) { - $options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%'; - + $options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%'; + // load default invoice tax if ($rate->id == $account->default_tax_rate_id) { $defaultTax = $rate; } - } - + } + // Check for any taxes which have been deleted if ($invoice->exists) { foreach ($invoice->getTaxes() as $key => $rate) { if (isset($options[$key])) { continue; - } + } $options[$key] = $rate['name'] . ' ' . $rate['rate'] . '%'; } } - + return [ 'data' => Input::old('data'), 'account' => Auth::user()->account->load('country'), @@ -396,10 +396,10 @@ class InvoiceController extends BaseController { $data = $request->input(); $data['documents'] = $request->file('documents'); - + $action = Input::get('action'); $entityType = Input::get('entityType'); - + $invoice = $this->invoiceService->save($data); $entityType = $invoice->getEntityType(); $message = trans("texts.created_{$entityType}"); @@ -433,7 +433,7 @@ class InvoiceController extends BaseController { $data = $request->input(); $data['documents'] = $request->file('documents'); - + $action = Input::get('action'); $entityType = Input::get('entityType'); @@ -547,7 +547,7 @@ class InvoiceController extends BaseController $clone = $this->invoiceService->convertQuote($request->entity()); Session::flash('message', trans('texts.converted_to_invoice')); - + return Redirect::to('invoices/' . $clone->public_id); } diff --git a/app/Http/Requests/CreatePaymentRequest.php b/app/Http/Requests/CreatePaymentRequest.php index d14d1ddba616..dbc830271197 100644 --- a/app/Http/Requests/CreatePaymentRequest.php +++ b/app/Http/Requests/CreatePaymentRequest.php @@ -23,14 +23,14 @@ class CreatePaymentRequest extends PaymentRequest { $input = $this->input(); $invoice = Invoice::scope($input['invoice'])->firstOrFail(); - + $rules = array( - 'client' => 'required', - 'invoice' => 'required', + 'client' => 'required', // TODO: change to client_id once views are updated + 'invoice' => 'required', // TODO: change to invoice_id once views are updated 'amount' => "required|less_than:{$invoice->balance}|positive", ); - if ($input['payment_type_id'] == PAYMENT_TYPE_CREDIT) { + if ( ! empty($input['payment_type_id']) && $input['payment_type_id'] == PAYMENT_TYPE_CREDIT) { $rules['payment_type_id'] = 'has_credit:'.$input['client'].','.$input['amount']; } diff --git a/app/Http/routes.php b/app/Http/routes.php index f4e65d0af180..f8fcbca5ffd7 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -472,6 +472,7 @@ if (!defined('CONTACT_EMAIL')) { define('DEFAULT_SEND_RECURRING_HOUR', 8); define('IMPORT_CSV', 'CSV'); + define('IMPORT_JSON', 'JSON'); define('IMPORT_FRESHBOOKS', 'FreshBooks'); define('IMPORT_WAVE', 'Wave'); define('IMPORT_RONIN', 'Ronin'); diff --git a/app/Listeners/SubscriptionListener.php b/app/Listeners/SubscriptionListener.php index 493a53f1110d..50d6f3be9240 100644 --- a/app/Listeners/SubscriptionListener.php +++ b/app/Listeners/SubscriptionListener.php @@ -3,6 +3,7 @@ use Auth; use Utils; +use App\Models\EntityModel; use App\Events\ClientWasCreated; use App\Events\QuoteWasCreated; use App\Events\InvoiceWasCreated; @@ -48,7 +49,7 @@ class SubscriptionListener public function createdCredit(CreditWasCreated $event) { - + } public function createdVendor(VendorWasCreated $event) @@ -63,6 +64,10 @@ class SubscriptionListener private function checkSubscriptions($eventId, $entity, $transformer, $include = '') { + if ( ! EntityModel::$notifySubscriptions) { + return; + } + $subscription = $entity->account->getSubscription($eventId); if ($subscription) { diff --git a/app/Models/Account.php b/app/Models/Account.php index 61526c9095d4..3248bf95814f 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -545,7 +545,7 @@ class Account extends Eloquent if ($this->hasClientNumberPattern($invoice) && !$clientId) { // do nothing, we don't yet know the value - } else { + } elseif ( ! $invoice->invoice_number) { $invoice->invoice_number = $this->getNextInvoiceNumber($invoice); } } @@ -649,7 +649,7 @@ class Account extends Eloquent return $this->getNextInvoiceNumber($invoice); } - public function getNextInvoiceNumber($invoice) + public function getNextInvoiceNumber($invoice, $validateUnique = true) { if ($this->hasNumberPattern($invoice->invoice_type_id)) { $number = $this->getNumberPattern($invoice); @@ -657,13 +657,16 @@ class Account extends Eloquent $counter = $this->getCounter($invoice->invoice_type_id); $prefix = $this->getNumberPrefix($invoice->invoice_type_id); $counterOffset = 0; + $check = false; // confirm the invoice number isn't already taken do { $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); - $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); - $counter++; - $counterOffset++; + if ($validateUnique) { + $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); + $counter++; + $counterOffset++; + } } while ($check); // update the invoice counter to be caught up @@ -688,7 +691,7 @@ class Account extends Eloquent public function incrementCounter($invoice) { // if they didn't use the counter don't increment it - if ($invoice->invoice_number != $this->getNextInvoiceNumber($invoice)) { + if ($invoice->invoice_number != $this->getNextInvoiceNumber($invoice, false)) { return; } @@ -1452,6 +1455,14 @@ class Account extends Eloquent } } -Account::updated(function ($account) { +Account::updated(function ($account) +{ + // prevent firing event if the invoice/quote counter was changed + // TODO: remove once counters are moved to separate table + $dirty = $account->getDirty(); + if (isset($dirty['invoice_number_counter']) || isset($dirty['quote_number_counter'])) { + return; + } + Event::fire(new UserSettingsChanged()); }); diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index 4b724d3953ee..4a6bffbc9e6b 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -9,22 +9,31 @@ class EntityModel extends Eloquent public $timestamps = true; protected $hidden = ['id']; + public static $notifySubscriptions = true; + public static function createNew($context = null) { $className = get_called_class(); $entity = new $className(); if ($context) { - $entity->user_id = $context instanceof User ? $context->id : $context->user_id; - $entity->account_id = $context->account_id; + $user = $context instanceof User ? $context : $context->user; + $account = $context->account; } elseif (Auth::check()) { - $entity->user_id = Auth::user()->id; - $entity->account_id = Auth::user()->account_id; + $user = Auth::user(); + $account = Auth::user()->account; } else { Utils::fatalError(); } - if(method_exists($className, 'withTrashed')){ + $entity->user_id = $user->id; + $entity->account_id = $account->id; + + // store references to the original user/account to prevent needing to reload them + $entity->setRelation('user', $user); + $entity->setRelation('account', $account); + + if (method_exists($className, 'withTrashed')){ $lastEntity = $className::withTrashed() ->scope(false, $entity->account_id); } else { diff --git a/app/Ninja/Import/FreshBooks/PaymentTransformer.php b/app/Ninja/Import/FreshBooks/PaymentTransformer.php index 1f69fdbacf41..eaf371f91eda 100644 --- a/app/Ninja/Import/FreshBooks/PaymentTransformer.php +++ b/app/Ninja/Import/FreshBooks/PaymentTransformer.php @@ -5,9 +5,9 @@ use League\Fractal\Resource\Item; class PaymentTransformer extends BaseTransformer { - public function transform($data, $maps) + public function transform($data) { - return new Item($data, function ($data) use ($maps) { + return new Item($data, function ($data) { return [ 'amount' => $data->paid, 'payment_date_sql' => $data->create_date, @@ -16,4 +16,4 @@ class PaymentTransformer extends BaseTransformer ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/Hiveage/PaymentTransformer.php b/app/Ninja/Import/Hiveage/PaymentTransformer.php index d6232d05bcc9..e7e4befb5714 100644 --- a/app/Ninja/Import/Hiveage/PaymentTransformer.php +++ b/app/Ninja/Import/Hiveage/PaymentTransformer.php @@ -5,9 +5,9 @@ use League\Fractal\Resource\Item; class PaymentTransformer extends BaseTransformer { - public function transform($data, $maps) + public function transform($data) { - return new Item($data, function ($data) use ($maps) { + return new Item($data, function ($data) { return [ 'amount' => $data->paid_total, 'payment_date_sql' => $this->getDate($data->last_paid_on), @@ -16,4 +16,4 @@ class PaymentTransformer extends BaseTransformer ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/Invoiceable/PaymentTransformer.php b/app/Ninja/Import/Invoiceable/PaymentTransformer.php index c52494cdc689..ea2310c01ada 100644 --- a/app/Ninja/Import/Invoiceable/PaymentTransformer.php +++ b/app/Ninja/Import/Invoiceable/PaymentTransformer.php @@ -5,9 +5,9 @@ use League\Fractal\Resource\Item; class PaymentTransformer extends BaseTransformer { - public function transform($data, $maps) + public function transform($data) { - return new Item($data, function ($data) use ($maps) { + return new Item($data, function ($data) { return [ 'amount' => $data->paid, 'payment_date_sql' => $data->date_paid, @@ -16,4 +16,4 @@ class PaymentTransformer extends BaseTransformer ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/Nutcache/PaymentTransformer.php b/app/Ninja/Import/Nutcache/PaymentTransformer.php index 04e783361f80..9434e274fc05 100644 --- a/app/Ninja/Import/Nutcache/PaymentTransformer.php +++ b/app/Ninja/Import/Nutcache/PaymentTransformer.php @@ -5,9 +5,9 @@ use League\Fractal\Resource\Item; class PaymentTransformer extends BaseTransformer { - public function transform($data, $maps) + public function transform($data) { - return new Item($data, function ($data) use ($maps) { + return new Item($data, function ($data) { return [ 'amount' => (float) $data->paid_to_date, 'payment_date_sql' => $this->getDate($data->date), @@ -16,4 +16,4 @@ class PaymentTransformer extends BaseTransformer ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/Ronin/PaymentTransformer.php b/app/Ninja/Import/Ronin/PaymentTransformer.php index c04101456200..b797d3f672f3 100644 --- a/app/Ninja/Import/Ronin/PaymentTransformer.php +++ b/app/Ninja/Import/Ronin/PaymentTransformer.php @@ -5,9 +5,9 @@ use League\Fractal\Resource\Item; class PaymentTransformer extends BaseTransformer { - public function transform($data, $maps) + public function transform($data) { - return new Item($data, function ($data) use ($maps) { + return new Item($data, function ($data) { return [ 'amount' => (float) $data->total - (float) $data->balance, 'payment_date_sql' => $data->date_paid, @@ -16,4 +16,4 @@ class PaymentTransformer extends BaseTransformer ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/Wave/PaymentTransformer.php b/app/Ninja/Import/Wave/PaymentTransformer.php index 522fe8ff9238..e809f0d3b408 100644 --- a/app/Ninja/Import/Wave/PaymentTransformer.php +++ b/app/Ninja/Import/Wave/PaymentTransformer.php @@ -5,13 +5,13 @@ use League\Fractal\Resource\Item; class PaymentTransformer extends BaseTransformer { - public function transform($data, $maps) + public function transform($data) { if ( ! $this->getInvoiceClientId($data->invoice_num)) { return false; } - - return new Item($data, function ($data) use ($maps) { + + return new Item($data, function ($data) { return [ 'amount' => (float) $data->amount, 'payment_date_sql' => $this->getDate($data->payment_date), @@ -20,4 +20,4 @@ class PaymentTransformer extends BaseTransformer ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/Zoho/PaymentTransformer.php b/app/Ninja/Import/Zoho/PaymentTransformer.php index a8fc74962321..0f9ad8bad891 100644 --- a/app/Ninja/Import/Zoho/PaymentTransformer.php +++ b/app/Ninja/Import/Zoho/PaymentTransformer.php @@ -5,9 +5,9 @@ use League\Fractal\Resource\Item; class PaymentTransformer extends BaseTransformer { - public function transform($data, $maps) + public function transform($data) { - return new Item($data, function ($data) use ($maps) { + return new Item($data, function ($data) { return [ 'amount' => (float) $data->total - (float) $data->balance, 'payment_date_sql' => $data->last_payment_date, @@ -16,4 +16,4 @@ class PaymentTransformer extends BaseTransformer ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Repositories/ClientRepository.php b/app/Ninja/Repositories/ClientRepository.php index bb56d944e6fc..cf2ad12daf39 100644 --- a/app/Ninja/Repositories/ClientRepository.php +++ b/app/Ninja/Repositories/ClientRepository.php @@ -118,9 +118,11 @@ class ClientRepository extends BaseRepository $first = false; } - foreach ($client->contacts as $contact) { - if (!in_array($contact->public_id, $contactIds)) { - $contact->delete(); + if ( ! $client->wasRecentlyCreated) { + foreach ($client->contacts as $contact) { + if (!in_array($contact->public_id, $contactIds)) { + $contact->delete(); + } } } diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index c15e261848d0..d79c15f24c08 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -494,13 +494,15 @@ class InvoiceRepository extends BaseRepository } } - foreach ($invoice->documents as $document){ - if(!in_array($document->public_id, $document_ids)){ - // Removed - // Not checking permissions; deleting a document is just editing the invoice - if($document->invoice_id == $invoice->id){ - // Make sure the document isn't on a clone - $document->delete(); + if ( ! $invoice->wasRecentlyCreated) { + foreach ($invoice->documents as $document){ + if(!in_array($document->public_id, $document_ids)){ + // Removed + // Not checking permissions; deleting a document is just editing the invoice + if($document->invoice_id == $invoice->id){ + // Make sure the document isn't on a clone + $document->delete(); + } } } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index b19594dacb87..74ff7ac78fc3 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -11,7 +11,6 @@ class EventServiceProvider extends ServiceProvider { * @var array */ protected $listen = [ - // Clients 'App\Events\ClientWasCreated' => [ 'App\Listeners\ActivityListener@createdClient', @@ -151,7 +150,6 @@ class EventServiceProvider extends ServiceProvider { 'App\Events\UserSettingsChanged' => [ 'App\Listeners\HandleUserSettingsChanged', ], - ]; /** diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 506179d6d4e3..11d3c8005903 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -24,7 +24,7 @@ class RouteServiceProvider extends ServiceProvider { { parent::boot($router); - + } diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php index 61eb0a1881f9..147a63cdeb08 100644 --- a/app/Services/ImportService.php +++ b/app/Services/ImportService.php @@ -17,6 +17,7 @@ use App\Ninja\Repositories\ProductRepository; use App\Ninja\Serializers\ArraySerializer; use App\Models\Client; use App\Models\Invoice; +use App\Models\EntityModel; class ImportService { @@ -25,9 +26,13 @@ class ImportService protected $clientRepo; protected $contactRepo; protected $productRepo; - protected $processedRows = array(); + protected $processedRows = []; + + private $maps = []; + public $results = []; public static $entityTypes = [ + IMPORT_JSON, ENTITY_CLIENT, ENTITY_CONTACT, ENTITY_INVOICE, @@ -39,6 +44,7 @@ class ImportService public static $sources = [ IMPORT_CSV, + IMPORT_JSON, IMPORT_FRESHBOOKS, //IMPORT_HARVEST, IMPORT_HIVEAGE, @@ -68,10 +74,70 @@ class ImportService $this->productRepo = $productRepo; } - public function import($source, $files) + public function importJSON($file) + { + $this->init(); + + $file = file_get_contents($file); + $json = json_decode($file, true); + $json = $this->removeIdFields($json); + + $this->checkClientCount(count($json['clients'])); + + foreach ($json['clients'] as $jsonClient) { + + if ($this->validate($jsonClient, ENTITY_CLIENT) === true) { + $client = $this->clientRepo->save($jsonClient); + $this->addSuccess($client); + } else { + $this->addFailure(ENTITY_CLIENT, $jsonClient); + continue; + } + + foreach ($jsonClient['invoices'] as $jsonInvoice) { + $jsonInvoice['client_id'] = $client->id; + if ($this->validate($jsonInvoice, ENTITY_INVOICE) === true) { + $invoice = $this->invoiceRepo->save($jsonInvoice); + $this->addSuccess($invoice); + } else { + $this->addFailure(ENTITY_INVOICE, $jsonInvoice); + continue; + } + + foreach ($jsonInvoice['payments'] as $jsonPayment) { + $jsonPayment['client_id'] = $jsonPayment['client'] = $client->id; // TODO: change to client_id once views are updated + $jsonPayment['invoice_id'] = $jsonPayment['invoice'] = $invoice->id; // TODO: change to invoice_id once views are updated + if ($this->validate($jsonPayment, ENTITY_PAYMENT) === true) { + $payment = $this->paymentRepo->save($jsonPayment); + $this->addSuccess($payment); + } else { + $this->addFailure(ENTITY_PAYMENT, $jsonPayment); + continue; + } + } + } + } + + return $this->results; + } + + public function removeIdFields($array) + { + foreach ($array as $key => $val) { + if (is_array($val)) { + $array[$key] = $this->removeIdFields($val); + } elseif ($key === 'id') { + unset($array[$key]); + } + } + return $array; + } + + public function importFiles($source, $files) { $results = []; $imported_files = null; + $this->initMaps(); foreach ($files as $entityType => $file) { $results[$entityType] = $this->execute($source, $entityType, $file); @@ -89,12 +155,12 @@ class ImportService // Convert the data $row_list = array(); - $maps = $this->createMaps(); - Excel::load($file, function ($reader) use ($source, $entityType, $maps, &$row_list, &$results) { + + Excel::load($file, function ($reader) use ($source, $entityType, &$row_list, &$results) { $this->checkData($entityType, count($reader->all())); - $reader->each(function ($row) use ($source, $entityType, $maps, &$row_list, &$results) { - $data_index = $this->transformRow($source, $entityType, $row, $maps); + $reader->each(function ($row) use ($source, $entityType, &$row_list, &$results) { + $data_index = $this->transformRow($source, $entityType, $row); if ($data_index !== false) { if ($data_index !== true) { @@ -109,7 +175,7 @@ class ImportService // Save the data foreach ($row_list as $row_data) { - $result = $this->saveData($source, $entityType, $row_data['row'], $row_data['data_index'], $maps); + $result = $this->saveData($source, $entityType, $row_data['row'], $row_data['data_index']); if ($result) { $results[RESULT_SUCCESS][] = $result; } else { @@ -120,10 +186,10 @@ class ImportService return $results; } - private function transformRow($source, $entityType, $row, $maps) + private function transformRow($source, $entityType, $row) { - $transformer = $this->getTransformer($source, $entityType, $maps); - $resource = $transformer->transform($row, $maps); + $transformer = $this->getTransformer($source, $entityType, $this->maps); + $resource = $transformer->transform($row); if (!$resource) { return false; @@ -138,7 +204,7 @@ class ImportService $data['invoice_number'] = $account->getNextInvoiceNumber($invoice); } - if ($this->validate($source, $data, $entityType) !== true) { + if ($this->validate($data, $entityType) !== true) { return false; } @@ -160,14 +226,18 @@ class ImportService return key($this->processedRows); } - private function saveData($source, $entityType, $row, $data_index, $maps) + private function saveData($source, $entityType, $row, $data_index) { $data = $this->processedRows[$data_index]; $entity = $this->{"{$entityType}Repo"}->save($data); + // update the entity maps + $mapFunction = 'add' . ucwords($entity->getEntityType()) . 'ToMaps'; + $this->$mapFunction($entity); + // if the invoice is paid we'll also create a payment record if ($entityType === ENTITY_INVOICE && isset($data['paid']) && $data['paid'] > 0) { - $this->createPayment($source, $row, $maps, $data['client_id'], $entity->id); + $this->createPayment($source, $row, $data['client_id'], $entity->id); } return $entity; @@ -200,21 +270,22 @@ class ImportService return new $className($maps); } - private function createPayment($source, $data, $maps, $clientId, $invoiceId) + private function createPayment($source, $data, $clientId, $invoiceId) { - $paymentTransformer = $this->getTransformer($source, ENTITY_PAYMENT, $maps); + $paymentTransformer = $this->getTransformer($source, ENTITY_PAYMENT, $this->maps); $data->client_id = $clientId; $data->invoice_id = $invoiceId; - if ($resource = $paymentTransformer->transform($data, $maps)) { + if ($resource = $paymentTransformer->transform($data)) { $data = $this->fractal->createData($resource)->toArray(); $this->paymentRepo->save($data); } } - private function validate($source, $data, $entityType) + private function validate($data, $entityType) { + /* // Harvest's contacts are listed separately if ($entityType === ENTITY_CLIENT && $source != IMPORT_HARVEST) { $rules = [ @@ -234,71 +305,21 @@ class ImportService 'product_key' => 'required', ]; } + */ + $requestClass = 'App\\Http\\Requests\\Create' . ucwords($entityType) . 'Request'; + $request = new $requestClass(); + $request->setUserResolver(function() { return Auth::user(); }); + $request->replace($data); - $validator = Validator::make($data, $rules); + $validator = Validator::make($data, $request->rules()); if ($validator->fails()) { - $messages = $validator->messages(); - - return $messages->first(); + return $validator->messages()->first(); } else { return true; } } - private function createMaps() - { - $clientMap = []; - $clients = $this->clientRepo->all(); - foreach ($clients as $client) { - if ($name = strtolower(trim($client->name))) { - $clientMap[$name] = $client->id; - } - } - - $invoiceMap = []; - $invoiceClientMap = []; - $invoices = $this->invoiceRepo->all(); - foreach ($invoices as $invoice) { - if ($number = strtolower(trim($invoice->invoice_number))) { - $invoiceMap[$number] = $invoice->id; - $invoiceClientMap[$number] = $invoice->client_id; - } - } - - $productMap = []; - $products = $this->productRepo->all(); - foreach ($products as $product) { - if ($key = strtolower(trim($product->product_key))) { - $productMap[$key] = $product->id; - } - } - - $countryMap = []; - $countryMap2 = []; - $countries = Cache::get('countries'); - foreach ($countries as $country) { - $countryMap[strtolower($country->name)] = $country->id; - $countryMap2[strtolower($country->iso_3166_2)] = $country->id; - } - - $currencyMap = []; - $currencies = Cache::get('currencies'); - foreach ($currencies as $currency) { - $currencyMap[strtolower($currency->code)] = $currency->id; - } - - return [ - ENTITY_CLIENT => $clientMap, - ENTITY_INVOICE => $invoiceMap, - ENTITY_INVOICE.'_'.ENTITY_CLIENT => $invoiceClientMap, - ENTITY_PRODUCT => $productMap, - 'countries' => $countryMap, - 'countries2' => $countryMap2, - 'currencies' => $currencyMap, - ]; - } - public function mapCSV($files) { $data = []; @@ -430,7 +451,7 @@ class ImportService $data = Session::get("{$entityType}-data"); $this->checkData($entityType, count($data)); - $maps = $this->createMaps(); + $this->initMaps(); // Convert the data $row_list = array(); @@ -441,7 +462,7 @@ class ImportService } $row = $this->convertToObject($entityType, $row, $map); - $data_index = $this->transformRow($source, $entityType, $row, $maps); + $data_index = $this->transformRow($source, $entityType, $row); if ($data_index !== false) { if ($data_index !== true) { @@ -455,7 +476,7 @@ class ImportService // Save the data foreach ($row_list as $row_data) { - $result = $this->saveData($source, $entityType, $row_data['row'], $row_data['data_index'], $maps); + $result = $this->saveData($source, $entityType, $row_data['row'], $row_data['data_index']); if ($result) { $results[RESULT_SUCCESS][] = $result; @@ -493,4 +514,93 @@ class ImportService return $obj; } + + private function addSuccess($entity) + { + $this->results[$entity->getEntityType()][RESULT_SUCCESS][] = $entity; + } + + private function addFailure($entityType, $data) + { + $this->results[$entityType][RESULT_FAILURE][] = $data; + } + + private function init() + { + EntityModel::$notifySubscriptions = false; + + foreach ([ENTITY_CLIENT, ENTITY_INVOICE, ENTITY_PAYMENT] as $entityType) { + $this->results[$entityType] = [ + RESULT_SUCCESS => [], + RESULT_FAILURE => [], + ]; + } + } + + private function initMaps() + { + $this->init(); + + $this->maps = [ + 'client' => [], + 'invoice' => [], + 'invoice_client' => [], + 'product' => [], + 'countries' => [], + 'countries2' => [], + 'currencies' => [], + 'client_ids' => [], + 'invoice_ids' => [], + ]; + + $clients = $this->clientRepo->all(); + foreach ($clients as $client) { + $this->addClientToMaps($client); + } + + $invoices = $this->invoiceRepo->all(); + foreach ($invoices as $invoice) { + $this->addInvoiceToMaps($invoice); + } + + $products = $this->productRepo->all(); + foreach ($products as $product) { + $this->addProductToMaps($product); + } + + $countries = Cache::get('countries'); + foreach ($countries as $country) { + $this->maps['countries'][strtolower($country->name)] = $country->id; + $this->maps['countries2'][strtolower($country->iso_3166_2)] = $country->id; + } + + $currencies = Cache::get('currencies'); + foreach ($currencies as $currency) { + $this->maps['currencies'][strtolower($currency->code)] = $currency->id; + } + } + + private function addInvoiceToMaps($invoice) + { + if ($number = strtolower(trim($invoice->invoice_number))) { + $this->maps['invoice'][$number] = $invoice->id; + $this->maps['invoice_client'][$number] = $invoice->client_id; + $this->maps['invoice_ids'][$invoice->public_id] = $invoice->id; + } + } + + private function addClientToMaps($client) + { + if ($name = strtolower(trim($client->name))) { + $this->maps['client'][$name] = $client->id; + $this->maps['client_ids'][$client->public_id] = $client->id; + } + } + + private function addProductToMaps($product) + { + if ($key = strtolower(trim($product->product_key))) { + $this->maps['product'][$key] = $product->id; + } + } } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index b96ed265e9d3..6d4fcdba26ec 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1321,6 +1321,8 @@ $LANG = array( 'products_will_create' => 'products will be created.', 'product_key' => 'Product', 'created_products' => 'Successfully created :count product(s)', + 'export_help' => 'Use JSON if you plan to import the data into Invoice Ninja.', + 'JSON_file' => 'JSON File', ); diff --git a/resources/views/accounts/import_export.blade.php b/resources/views/accounts/import_export.blade.php index b0f2fd5d7c26..93acd555e801 100644 --- a/resources/views/accounts/import_export.blade.php +++ b/resources/views/accounts/import_export.blade.php @@ -53,7 +53,8 @@ ->addOption('CSV', 'CSV') ->addOption('XLS', 'XLS') ->addOption('JSON', 'JSON') - ->style('max-width: 200px') !!} + ->style('max-width: 200px') + ->inlineHelp('export_help') !!} {!! Former::checkbox('entity_types') ->label('include') @@ -100,6 +101,11 @@ @endif @endforeach } + @if ($source === IMPORT_JSON) + if (val === '{{ $source }}') { + $('.JSON-file').show(); + } + @endif @endforeach } diff --git a/resources/views/master.blade.php b/resources/views/master.blade.php index 8cd3bbb034e4..463be8be0eb7 100644 --- a/resources/views/master.blade.php +++ b/resources/views/master.blade.php @@ -4,12 +4,12 @@ @if (isset($account) && $account instanceof \App\Models\Account && $account->hasFeature(FEATURE_WHITE_LABEL)) {{ trans('texts.client_portal') }} @else - {{ isset($title) ? ($title . ' | Invoice Ninja') : ('Invoice Ninja | ' . trans('texts.app_title')) }} + {{ isset($title) ? ($title . ' | Invoice Ninja') : ('Invoice Ninja | ' . trans('texts.app_title')) }} @endif - + @@ -22,24 +22,24 @@ - + - + @@ -132,7 +132,7 @@ - @if (isset($_ENV['TAG_MANAGER_KEY']) && $_ENV['TAG_MANAGER_KEY']) + @if (isset($_ENV['TAG_MANAGER_KEY']) && $_ENV['TAG_MANAGER_KEY']) @@ -140,20 +140,20 @@ new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= '//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); - })(window,document,'script','dataLayer','{{ $_ENV['TAG_MANAGER_KEY'] }}'); + })(window,document,'script','dataLayer','{{ $_ENV['TAG_MANAGER_KEY'] }}'); - @elseif (isset($_ENV['ANALYTICS_KEY']) && $_ENV['ANALYTICS_KEY']) + @elseif (isset($_ENV['ANALYTICS_KEY']) && $_ENV['ANALYTICS_KEY']) @endif - + @yield('body') +