/** * @param string $fileType * * @return ImportJob */ public function create(string $fileType) : ImportJob { $count = 0; $fileType = strtolower($fileType); $keys = array_keys(config('firefly.import_formats')); if (!in_array($fileType, $keys)) { throw new FireflyException(sprintf('Cannot use type "%s" for import job.', $fileType)); } while ($count < 30) { $key = Str::random(12); $existing = $this->findByKey($key); if (is_null($existing->id)) { $importJob = new ImportJob(); $importJob->user()->associate($this->user); $importJob->file_type = $fileType; $importJob->key = Str::random(12); $importJob->status = 'import_status_never_started'; $importJob->extended_status = ['total_steps' => 0, 'steps_done' => 0, 'import_count' => 0, 'importTag' => 0, 'errors' => []]; $importJob->save(); // breaks the loop: return $importJob; } $count++; } return new ImportJob(); }
/** * @return array */ private function getDataForColumnRoles() : array { $config = $this->job->configuration; $data = ['columns' => [], 'columnCount' => 0]; // show user column role configuration. $content = $this->job->uploadFileContents(); // create CSV reader. $reader = Reader::createFromString($content); $reader->setDelimiter($config['delimiter']); $start = $config['has-headers'] ? 1 : 0; $end = $start + config('csv.example_rows'); // collect example data in $data['columns'] while ($start < $end) { $row = $reader->fetchOne($start); // run specifics here: // and this is the point where the specifix go to work. foreach ($config['specifics'] as $name => $enabled) { /** @var SpecificInterface $specific */ $specific = app('FireflyIII\\Import\\Specifics\\' . $name); // it returns the row, possibly modified: $row = $specific->run($row); } foreach ($row as $index => $value) { $value = trim($value); if (strlen($value) > 0) { $data['columns'][$index][] = $value; } } $start++; $data['columnCount'] = count($row) > $data['columnCount'] ? count($row) : $data['columnCount']; } // make unique example data foreach ($data['columns'] as $index => $values) { $data['columns'][$index] = array_unique($values); } $data['set_roles'] = []; // collect possible column roles: $data['available_roles'] = []; foreach (array_keys(config('csv.import_roles')) as $role) { $data['available_roles'][$role] = trans('csv.column_' . $role); } $config['column-count'] = $data['columnCount']; $this->job->configuration = $config; $this->job->save(); return $data; }
/** * @param ImportJob $job * * @return Collection */ public static function runImport(ImportJob $job) : Collection { // update job to say we started. $job->status = 'import_running'; $job->save(); // create Importer $valid = array_keys(config('firefly.import_formats')); $class = 'INVALID'; if (in_array($job->file_type, $valid)) { $class = config('firefly.import_formats.' . $job->file_type); } /** @var ImporterInterface $importer */ $importer = app($class); $importer->setJob($job); // create import entries $collection = $importer->createImportEntries(); // validate / clean collection: $validator = new ImportValidator($collection); $validator->setUser($job->user); $validator->setJob($job); if ($job->configuration['import-account'] != 0) { $repository = app(AccountCrud::class, [$job->user]); $validator->setDefaultImportAccount($repository->find($job->configuration['import-account'])); } $cleaned = $validator->clean(); // then import collection: $storage = new ImportStorage($job->user, $cleaned); $storage->setJob($job); // and run store routine: $result = $storage->store(); // grab import tag: $status = $job->extended_status; $status['importTag'] = $storage->importTag->id; $job->extended_status = $status; $job->status = 'import_complete'; $job->save(); return $result; }
/** * @param int $index * @param ImportEntry $entry * * @return TransactionJournal * @throws FireflyException */ private function storeSingle(int $index, ImportEntry $entry) : TransactionJournal { if ($entry->valid === false) { Log::warning(sprintf('Cannot import row %d, because the entry is not valid.', $index)); $errors = join(', ', $entry->errors->all()); $errorText = sprintf('Row #%d: ' . $errors, $index); $extendedStatus = $this->job->extended_status; $extendedStatus['errors'][] = $errorText; $this->job->extended_status = $extendedStatus; $this->job->save(); return new TransactionJournal(); } $alreadyImported = $this->alreadyImported($entry->hash); if (!is_null($alreadyImported->id)) { Log::warning(sprintf('Cannot import row %d, because it has already been imported (journal #%d).', $index, $alreadyImported->id)); $errorText = trans('firefly.import_double', ['row' => $index, 'link' => route('transactions.show', [$alreadyImported->id]), 'description' => $alreadyImported->description]); $extendedStatus = $this->job->extended_status; $extendedStatus['errors'][] = $errorText; $this->job->extended_status = $extendedStatus; $this->job->save(); return new TransactionJournal(); } Log::debug(sprintf('Going to store row %d', $index)); $journal = $this->storeJournal($entry); $amount = $this->makePositive($entry->fields['amount']); $accounts = $this->storeAccounts($entry); // create new transactions. This is something that needs a rewrite for multiple/split transactions. $sourceData = ['account_id' => $accounts['source']->id, 'transaction_journal_id' => $journal->id, 'description' => $journal->description, 'amount' => bcmul($amount, '-1')]; $destinationData = ['account_id' => $accounts['destination']->id, 'transaction_journal_id' => $journal->id, 'description' => $journal->description, 'amount' => $amount]; $one = Transaction::create($sourceData); $two = Transaction::create($destinationData); $error = false; if (is_null($one->id)) { Log::error('Could not create transaction 1.', $one->getErrors()->all()); $error = true; } if (is_null($two->id)) { Log::error('Could not create transaction 1.', $two->getErrors()->all()); $error = true; } // respond to error if ($error === true) { $errorText = sprintf('Cannot import row %d, because an error occured when storing data.', $index); Log::error($errorText); $extendedStatus = $this->job->extended_status; $extendedStatus['errors'][] = $errorText; $this->job->extended_status = $extendedStatus; $this->job->save(); return new TransactionJournal(); } Log::debug('Created transaction 1', ['id' => $one->id, 'account' => $one->account_id, 'account_name' => $accounts['source']->name]); Log::debug('Created transaction 2', ['id' => $two->id, 'account' => $two->account_id, 'account_name' => $accounts['destination']->name]); $journal->completed = 1; $journal->save(); // attach import tag. $journal->tags()->save($this->importTag); // now attach budget and so on. $this->storeBudget($journal, $entry); $this->storeCategory($journal, $entry); $this->storeBill($journal, $entry); return $journal; }