You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
8.5 KiB
254 lines
8.5 KiB
<?php
|
|
|
|
/*
|
|
* RoleService.php
|
|
* Copyright (c) 2021 james@firefly-iii.org
|
|
*
|
|
* This file is part of the Firefly III Data Importer
|
|
* (https://github.com/firefly-iii/data-importer).
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\CSV\Roles;
|
|
|
|
use App\Services\Camt\Transaction;
|
|
use App\Services\Session\Constants;
|
|
use App\Services\Shared\Configuration\Configuration;
|
|
use App\Services\Storage\StorageService;
|
|
use Genkgo\Camt\Camt053\DTO\Statement as CamtStatement;
|
|
use Genkgo\Camt\Config;
|
|
use Genkgo\Camt\Reader as CamtReader;
|
|
use Illuminate\Support\Facades\Log;
|
|
use League\Csv\Exception;
|
|
use League\Csv\InvalidArgument;
|
|
use League\Csv\Reader;
|
|
use League\Csv\Statement;
|
|
use League\Csv\UnableToProcessCsv;
|
|
|
|
/**
|
|
* Class RoleService
|
|
*/
|
|
class RoleService
|
|
{
|
|
public const EXAMPLE_COUNT = 7;
|
|
public const EXAMPLE_LENGTH = 26;
|
|
|
|
/**
|
|
* @throws InvalidArgument
|
|
* @throws UnableToProcessCsv
|
|
*/
|
|
public static function getColumns(string $content, Configuration $configuration): array
|
|
{
|
|
$reader = Reader::createFromString($content);
|
|
|
|
// configure reader:
|
|
$delimiter = $configuration->getDelimiter();
|
|
|
|
switch ($delimiter) {
|
|
default:
|
|
case 'comma':
|
|
$reader->setDelimiter(',');
|
|
|
|
break;
|
|
|
|
case 'semicolon':
|
|
$reader->setDelimiter(';');
|
|
|
|
break;
|
|
|
|
case 'tab':
|
|
$reader->setDelimiter("\t");
|
|
|
|
break;
|
|
}
|
|
|
|
$headers = [];
|
|
if (true === $configuration->isHeaders()) {
|
|
try {
|
|
$stmt = new Statement()->limit(1)->offset(0);
|
|
$records = $stmt->process($reader);
|
|
$headers = $records->fetchOne();
|
|
// @codeCoverageIgnoreStart
|
|
} catch (Exception $e) {
|
|
Log::error($e->getMessage());
|
|
|
|
throw new \InvalidArgumentException($e->getMessage());
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
Log::debug('Detected file headers:', $headers);
|
|
}
|
|
if (false === $configuration->isHeaders()) {
|
|
Log::debug('Role service: file has no headers');
|
|
|
|
try {
|
|
$stmt = new Statement()->limit(1)->offset(0);
|
|
$records = $stmt->process($reader);
|
|
$count = count($records->fetchOne());
|
|
Log::debug(sprintf('Role service: first row has %d columns', $count));
|
|
for ($i = 0; $i < $count; ++$i) {
|
|
$headers[] = sprintf('Column #%d', $i + 1);
|
|
}
|
|
// @codeCoverageIgnoreStart
|
|
} catch (Exception $e) {
|
|
Log::error($e->getMessage());
|
|
|
|
throw new \InvalidArgumentException($e->getMessage());
|
|
}
|
|
}
|
|
|
|
return $headers;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public static function getExampleData(string $content, Configuration $configuration): array
|
|
{
|
|
$reader = Reader::createFromString($content);
|
|
|
|
// configure reader:
|
|
$delimiter = $configuration->getDelimiter();
|
|
|
|
switch ($delimiter) {
|
|
default:
|
|
case 'comma':
|
|
$reader->setDelimiter(',');
|
|
|
|
break;
|
|
|
|
case 'semicolon':
|
|
$reader->setDelimiter(';');
|
|
|
|
break;
|
|
|
|
case 'tab':
|
|
$reader->setDelimiter("\t");
|
|
|
|
break;
|
|
}
|
|
|
|
$offset = $configuration->isHeaders() ? 1 : 0;
|
|
$examples = [];
|
|
|
|
// make statement.
|
|
try {
|
|
$stmt = new Statement()->limit(self::EXAMPLE_COUNT)->offset($offset);
|
|
// @codeCoverageIgnoreStart
|
|
} catch (Exception $e) {
|
|
Log::error($e->getMessage());
|
|
|
|
throw new \InvalidArgumentException($e->getMessage());
|
|
}
|
|
|
|
/** @codeCoverageIgnoreEnd */
|
|
|
|
// grab the records:
|
|
$records = $stmt->process($reader);
|
|
|
|
/** @var array $line */
|
|
foreach ($records as $line) {
|
|
$line = array_values($line);
|
|
// $line = SpecificService::runSpecifics($line, $configuration->getSpecifics());
|
|
foreach ($line as $index => $cell) {
|
|
if (strlen((string) $cell) > self::EXAMPLE_LENGTH) {
|
|
$cell = sprintf('%s...', substr((string) $cell, 0, self::EXAMPLE_LENGTH));
|
|
}
|
|
$examples[$index][] = $cell;
|
|
$examples[$index] = array_unique($examples[$index]);
|
|
}
|
|
}
|
|
foreach ($examples as $line => $entries) {
|
|
asort($entries);
|
|
$examples[$line] = $entries;
|
|
}
|
|
|
|
return $examples;
|
|
}
|
|
|
|
public static function getExampleDataFromCamt(string $content, Configuration $configuration): array
|
|
{
|
|
$camtReader = new CamtReader(Config::getDefault());
|
|
$camtMessage = $camtReader->readString(StorageService::getContent(session()->get(Constants::UPLOAD_DATA_FILE))); // -> Level A
|
|
$transactions = [];
|
|
$examples = [];
|
|
$fieldNames = array_keys(config('camt.fields'));
|
|
foreach ($fieldNames as $name) {
|
|
$examples[$name] = [];
|
|
}
|
|
|
|
/**
|
|
* This code creates separate Transaction objects for transaction details,
|
|
* even when the user indicates these details should be splits or ignored entirely.
|
|
* This is because we still need to extract possible example data from these transaction details.
|
|
*/
|
|
$statements = $camtMessage->getRecords();
|
|
|
|
/** @var CamtStatement $statement */
|
|
foreach ($statements as $statement) { // -> Level B
|
|
$entries = $statement->getEntries();
|
|
foreach ($entries as $entry) { // -> Level C
|
|
$count = count($entry->getTransactionDetails()); // count level D entries.
|
|
if (0 === $count) {
|
|
// TODO Create a single transaction, I guess?
|
|
$transactions[] = new Transaction($configuration, $camtMessage, $statement, $entry, []);
|
|
}
|
|
if (0 !== $count) {
|
|
foreach ($entry->getTransactionDetails() as $detail) {
|
|
$transactions[] = new Transaction($configuration, $camtMessage, $statement, $entry, [$detail]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$count = 0;
|
|
|
|
/** @var Transaction $transaction */
|
|
foreach ($transactions as $transaction) {
|
|
if (15 === $count) { // do not check more than 15 transactions to fill the example-data
|
|
break;
|
|
}
|
|
foreach ($fieldNames as $name) {
|
|
if (array_key_exists($name, $examples)) { // there is at least one example, so we can check how many
|
|
if (count($examples[$name]) > 5) { // there are already five examples, so jump to next field
|
|
continue;
|
|
}
|
|
} // otherwise, try to fetch data
|
|
$splits = $transaction->countSplits();
|
|
if (0 === $splits) {
|
|
$value = $transaction->getFieldByIndex($name, 0);
|
|
if ('' !== $value) {
|
|
$examples[$name][] = $value;
|
|
}
|
|
}
|
|
if ($splits > 0) {
|
|
for ($index = 0; $index < $splits; ++$index) {
|
|
$value = $transaction->getFieldByIndex($name, $index);
|
|
if ('' !== $value) {
|
|
$examples[$name][] = $value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
++$count;
|
|
}
|
|
foreach ($examples as $key => $list) {
|
|
$examples[$key] = array_unique($list);
|
|
$examples[$key] = array_filter($examples[$key], fn (string $value) => '' !== $value);
|
|
}
|
|
|
|
return $examples;
|
|
}
|
|
}
|