diff --git a/app/Import/Routine/FileRoutine.php b/app/Import/Routine/FileRoutine.php index 65e77616ea..f39153c91b 100644 --- a/app/Import/Routine/FileRoutine.php +++ b/app/Import/Routine/FileRoutine.php @@ -26,7 +26,6 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Support\Import\Routine\File\FileProcessorInterface; -use FireflyIII\Support\Import\Routine\File\FileRoutineInterface; use Log; /** diff --git a/app/Support/Import/Placeholder/ColumnValue.php b/app/Support/Import/Placeholder/ColumnValue.php new file mode 100644 index 0000000000..238a9687bc --- /dev/null +++ b/app/Support/Import/Placeholder/ColumnValue.php @@ -0,0 +1,113 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Placeholder; + +/** + * Class Column + */ +class ColumnValue +{ + /** @var int */ + private $mappedValue; + /** @var string */ + private $originalRole; + /** @var string */ + private $role; + /** @var string */ + private $value; + + /** + * ColumnValue constructor. + */ + public function __construct() + { + $this->mappedValue = 0; + } + + /** + * @return int + */ + public function getMappedValue(): int + { + return $this->mappedValue; + } + + /** + * @param int $mappedValue + */ + public function setMappedValue(int $mappedValue): void + { + $this->mappedValue = $mappedValue; + } + + /** + * @return string + */ + public function getOriginalRole(): string + { + return $this->originalRole; + } + + /** + * @param string $originalRole + */ + public function setOriginalRole(string $originalRole): void + { + $this->originalRole = $originalRole; + } + + /** + * @return string + */ + public function getRole(): string + { + return $this->role; + } + + /** + * @param string $role + */ + public function setRole(string $role): void + { + $this->role = $role; + } + + /** + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + /** + * @param string $value + */ + public function setValue(string $value): void + { + $this->value = $value; + } + + +} \ No newline at end of file diff --git a/app/Support/Import/Placeholder/ImportTransaction.php b/app/Support/Import/Placeholder/ImportTransaction.php new file mode 100644 index 0000000000..57ba4522b6 --- /dev/null +++ b/app/Support/Import/Placeholder/ImportTransaction.php @@ -0,0 +1,220 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Placeholder; + +use FireflyIII\Exceptions\FireflyException; + +/** + * Class ImportTransaction + */ +class ImportTransaction +{ + /** @var string */ + private $accountBic; + /** @var string */ + private $accountIban; + /** @var int */ + private $accountId; + /** @var string */ + private $accountName; + /** @var string */ + private $amount; + /** @var string */ + private $amountCredit; + /** @var string */ + private $amountDebit; + /** @var int */ + private $billId; + /** @var int */ + private $budgetId; + /** @var int */ + private $categoryId; + /** @var int */ + private $currencyId; + /** @var string */ + private $date; + /** @var string */ + private $description; + /** @var string */ + private $externalId; + /** @var string */ + private $foreignAmount; + /** @var int */ + private $foreignCurrencyId; + /** @var array */ + private $meta; + /** @var array */ + private $modifiers; + /** @var string */ + private $note; + /** @var string */ + private $opposingBic; + /** @var string */ + private $opposingIban; + /** @var int */ + private $opposingId; + /** @var string */ + private $opposingName; + /** @var array */ + private $tags; + + /** + * ImportTransaction constructor. + */ + public function __construct() + { + $this->tags = []; + $this->modifiers = []; + $this->meta = []; + $this->description = ''; + $this->note = ''; + } + + /** + * @param ColumnValue $columnValue + * + * @throws FireflyException + */ + public function addColumnValue(ColumnValue $columnValue): void + { + switch ($columnValue->getRole()) { + default: + throw new FireflyException( + sprintf('ImportTransaction cannot handle role "%s" with value "%s"', $columnValue->getRole(), $columnValue->getValue()) + ); + case 'account-id': + // could be the result of a mapping? + $this->accountId = $this->getMappedValue($columnValue); + break; + case 'account-name': + $this->accountName = $columnValue->getValue(); + break; + case 'sepa-ct-id'; + case 'sepa-ct-op'; + case 'sepa-db'; + case 'sepa-cc': + case 'sepa-country'; + case 'sepa-ep'; + case 'sepa-ci'; + case 'internal-reference': + case 'date-interest': + case 'date-invoice': + case 'date-book': + case 'date-payment': + case 'date-process': + case 'date-due': + $this->meta[$columnValue->getRole()] = $columnValue->getValue(); + break; + case'amount_debit': + $this->amountDebit = $columnValue->getValue(); + break; + case'amount_credit': + $this->amountCredit = $columnValue->getValue(); + break; + case 'amount': + $this->amount = $columnValue->getValue(); + break; + case 'amount_foreign': + $this->foreignAmount = $columnValue->getValue(); + break; + case 'foreign-currency-id': + $this->foreignCurrencyId = $this->getMappedValue($columnValue); + break; + case 'bill-id': + $this->billId = $this->getMappedValue($columnValue); + break; + case 'budget-id': + $this->budgetId = $this->getMappedValue($columnValue); + break; + case 'category-id': + $this->categoryId = $this->getMappedValue($columnValue); + break; + case 'currency-id': + $this->currencyId = $this->getMappedValue($columnValue); + break; + case 'date-transaction': + $this->date = $columnValue->getValue(); + break; + case 'description': + $this->description .= $columnValue->getValue(); + break; + case 'note': + $this->note .= $columnValue->getValue(); + break; + case 'external-id': + $this->externalId = $columnValue->getValue(); + break; + case 'rabo-debit-credit': + case 'ing-debit-credit': + $this->modifiers[$columnValue->getRole()] = $columnValue->getValue(); + break; + case 'opposing-id': + $this->opposingId = $this->getMappedValue($columnValue); + break; + case 'opposing-name': + $this->opposingName = $columnValue->getValue(); + break; + case 'opposing-bic': + $this->opposingBic = $columnValue->getValue(); + break; + case 'tags-comma': + // todo split using pre-processor. + $this->tags = $columnValue->getValue(); + break; + case 'tags-space': + // todo split using pre-processor. + $this->tags = $columnValue->getValue(); + break; + case 'account-iban': + $this->accountIban = $columnValue->getValue(); + break; + case 'opposing-iban': + $this->opposingIban = $columnValue->getValue(); + break; + case '_ignore': + case 'bill-name': + case 'currency-name': + case 'currency-code': + case 'foreign-currency-code': + case 'currency-symbol': + case 'budget-name': + case 'category-name': + case 'account-number': + case 'opposing-number': + } + } + + /** + * Returns the mapped value if it exists in the ColumnValue object. + * + * @param ColumnValue $columnValue + * + * @return int + */ + private function getMappedValue(ColumnValue $columnValue): int + { + return $columnValue->getMappedValue() > 0 ? $columnValue->getMappedValue() : (int)$columnValue->getValue(); + } + +} \ No newline at end of file diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php index ff8476659b..e9a0d873d9 100644 --- a/app/Support/Import/Routine/File/CSVProcessor.php +++ b/app/Support/Import/Routine/File/CSVProcessor.php @@ -28,6 +28,8 @@ use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Models\Attachment; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Support\Import\Placeholder\ColumnValue; +use FireflyIII\Support\Import\Placeholder\ImportTransaction; use Illuminate\Support\Collection; use League\Csv\Exception; use League\Csv\Reader; @@ -44,8 +46,12 @@ class CSVProcessor implements FileProcessorInterface { /** @var AttachmentHelperInterface */ private $attachments; + /** @var array */ + private $config; /** @var ImportJob */ private $importJob; + /** @var array */ + private $mappedValues; /** @var ImportJobRepositoryInterface */ private $repository; @@ -57,7 +63,7 @@ class CSVProcessor implements FileProcessorInterface */ public function run(): array { - $config = $this->importJob->configuration; + // in order to actually map we also need to read the FULL file. try { @@ -66,14 +72,11 @@ class CSVProcessor implements FileProcessorInterface Log::error($e->getMessage()); throw new FireflyException('Cannot get reader: ' . $e->getMessage()); } - // get mapping from config - $roles = $config['column-roles']; - // get all lines from file: - $lines = $this->getLines($reader, $config); + $lines = $this->getLines($reader); // make import objects, according to their role: - $importables = $this->processLines($lines, $roles); + $importables = $this->processLines($lines); echo '
';
print_r($importables);
@@ -89,6 +92,7 @@ class CSVProcessor implements FileProcessorInterface
public function setJob(ImportJob $job): void
{
$this->importJob = $job;
+ $this->config = $job->configuration;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->attachments = app(AttachmentHelperInterface::class);
$this->repository->setUser($job->user);
@@ -99,14 +103,13 @@ class CSVProcessor implements FileProcessorInterface
* Returns all lines from the CSV file.
*
* @param Reader $reader
- * @param array $config
*
* @return array
* @throws FireflyException
*/
- private function getLines(Reader $reader, array $config): array
+ private function getLines(Reader $reader): array
{
- $offset = isset($config['has-headers']) && $config['has-headers'] === true ? 1 : 0;
+ $offset = isset($this->config['has-headers']) && $this->config['has-headers'] === true ? 1 : 0;
try {
$stmt = (new Statement)->offset($offset);
} catch (Exception $e) {
@@ -146,45 +149,93 @@ class CSVProcessor implements FileProcessorInterface
}
/**
- * Process a single column. Return is an array with:
- * [0 => key, 1 => value]
- * where the first item is the key under which the value
- * must be stored, and the second is the value.
+ * If the value in the column is mapped to a certain ID,
+ * the column where this ID must be placed will change.
*
- * @param int $column
- * @param string $value
- * @param string $role
+ * For example, if you map role "budget-name" with value "groceries" to 1,
+ * then that should become the budget-id. Not the name.
*
- * @return array
+ * @param int $column
+ * @param int $mapped
+ *
+ * @return string
* @throws FireflyException
*/
- private function processColumn(int $column, string $value, string $role): array
+ private function getRoleForColumn(int $column, int $mapped): string
{
+ $roles = $this->config['column-roles'];
+ $role = $roles[$column] ?? '_ignore';
+ if ($mapped === 0) {
+ Log::debug(sprintf('Column #%d with role "%s" is not mapped.', $column, $role));
+
+ return $role;
+ }
+ if (!(isset($this->config['column-do-mapping'][$column]) && $this->config['column-do-mapping'][$column] === true)) {
+
+ return $role;
+ }
switch ($role) {
default:
- throw new FireflyException(sprintf('Cannot handle role "%s" with value "%s"', $role, $value));
-
-
- // feed each line into a new class which will process
- // the line.
+ throw new FireflyException(sprintf('Cannot indicate new role for mapped role "%s"', $role));
+ case 'account-id':
+ case 'account-name':
+ case 'account-iban':
+ case 'account-number':
+ $newRole = 'account-id';
+ break;
+ case 'foreign-currency-id':
+ case 'foreign-currency-code':
+ $newRole = 'foreign-currency-id';
+ break;
+ case 'bill-id':
+ case 'bill-name':
+ $newRole = 'bill-id';
+ break;
+ case 'currency-id':
+ case 'currency-name':
+ case 'currency-code':
+ case 'currency-symbol':
+ $newRole = 'currency-id';
+ break;
+ case 'budget-id':
+ case 'budget-name':
+ $newRole = 'budget-id';
+ break;
+ case 'category-id':
+ case 'category-name':
+ $newRole = 'category-id';
+ break;
+ case 'opposing-id':
+ case 'opposing-name':
+ case 'opposing-iban':
+ case 'opposing-number':
+ $newRole = 'opposing-id';
+ break;
}
+ Log::debug(sprintf('Role was "%s", but because of mapping, role becomes "%s"', $role, $newRole));
+
+ // also store the $mapped values in a "mappedValues" array.
+ $this->mappedValues[$newRole] = $mapped;
+
+ return $newRole;
}
+
/**
* Process all lines in the CSV file. Each line is processed separately.
*
* @param array $lines
- * @param array $roles
*
* @return array
* @throws FireflyException
*/
- private function processLines(array $lines, array $roles): array
+ private function processLines(array $lines): array
{
$processed = [];
- foreach ($lines as $line) {
- $processed[] = $this->processSingleLine($line, $roles);
-
+ $count = \count($lines);
+ foreach ($lines as $index => $line) {
+ Log::debug(sprintf('Now at line #%d of #%d', $index, $count));
+ $processed[] = $this->processSingleLine($line);
}
return $processed;
@@ -197,18 +248,34 @@ class CSVProcessor implements FileProcessorInterface
* @param array $line
* @param array $roles
*
- * @return array
+ * @return ImportTransaction
* @throws FireflyException
*/
- private function processSingleLine(array $line, array $roles): array
+ private function processSingleLine(array $line): ImportTransaction
{
+ $transaction = new ImportTransaction;
// todo run all specifics on row.
- $transaction = [];
foreach ($line as $column => $value) {
- $value = trim($value);
- $role = $roles[$column] ?? '_ignore';
- [$key, $result] = $this->processColumn($column, $value, $role);
- // if relevant, find mapped value:
+ $value = trim($value);
+ $originalRole = $this->config['column-roles'][$column] ?? '_ignore';
+ if (\strlen($value) > 0 && $originalRole !== '_ignore') {
+
+ // is a mapped value present?
+ $mapped = $this->config['column-mapping-config'][$column][$value] ?? 0;
+ // the role might change.
+ $role = $this->getRoleForColumn($column, $mapped);
+
+ $columnValue = new ColumnValue;
+ $columnValue->setValue($value);
+ $columnValue->setRole($role);
+ $columnValue->setMappedValue($mapped);
+ $columnValue->setOriginalRole($originalRole);
+ $transaction->addColumnValue($columnValue);
+
+ // create object that parses this column value.
+
+ Log::debug(sprintf('Now at column #%d (%s), value "%s"', $column, $role, $value));
+ }
}
return $transaction;