diff --git a/app/Export/Collector/AttachmentCollector.php b/app/Export/Collector/AttachmentCollector.php index 2fcb534b13..e068f32585 100644 --- a/app/Export/Collector/AttachmentCollector.php +++ b/app/Export/Collector/AttachmentCollector.php @@ -13,11 +13,10 @@ declare(strict_types = 1); namespace FireflyIII\Export\Collector; -use Amount; +use Carbon\Carbon; use Crypt; use FireflyIII\Models\Attachment; use FireflyIII\Models\ExportJob; -use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Support\Collection; @@ -31,12 +30,14 @@ use Storage; */ class AttachmentCollector extends BasicCollector implements CollectorInterface { - /** @var string */ - private $explanationString = ''; + /** @var Carbon */ + private $end; /** @var \Illuminate\Contracts\Filesystem\Filesystem */ private $exportDisk; /** @var AttachmentRepositoryInterface */ private $repository; + /** @var Carbon */ + private $start; /** @var \Illuminate\Contracts\Filesystem\Filesystem */ private $uploadDisk; @@ -69,34 +70,17 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface $this->exportAttachment($attachment); } - // put the explanation string in a file and attach it as well. - $file = $this->job->key . '-Source of all your attachments explained.txt'; - $this->exportDisk->put($file, $this->explanationString); - $this->getFiles()->push($file); - return true; } /** - * @param Attachment $attachment + * @param Carbon $start + * @param Carbon $end */ - private function explain(Attachment $attachment) + public function setDates(Carbon $start, Carbon $end) { - /** @var TransactionJournal $journal */ - $journal = $attachment->attachable; - $args = [ - 'attachment_name' => e($attachment->filename), - 'attachment_id' => $attachment->id, - 'type' => strtolower($journal->transactionType->type), - 'description' => e($journal->description), - 'journal_id' => $journal->id, - 'date' => $journal->date->formatLocalized(strval(trans('config.month_and_day'))), - 'amount' => Amount::formatJournal($journal, false), - ]; - $string = trans('firefly.attachment_explanation', $args) . "\n"; - Log::debug('Appended explanation string', ['string' => $string]); - $this->explanationString .= $string; - + $this->start = $start; + $this->end = $end; } /** @@ -112,10 +96,8 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface $decrypted = Crypt::decrypt($this->uploadDisk->get($file)); $exportFile = $this->exportFileName($attachment); $this->exportDisk->put($exportFile, $decrypted); - $this->getFiles()->push($exportFile); + $this->getEntries()->push($exportFile); - // explain: - $this->explain($attachment); } catch (DecryptException $e) { Log::error('Catchable error: could not decrypt attachment #' . $attachment->id . ' because: ' . $e->getMessage()); } @@ -143,7 +125,7 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface */ private function getAttachments(): Collection { - $attachments = $this->repository->get(); + $attachments = $this->repository->getBetween($this->start, $this->end); return $attachments; } diff --git a/app/Export/Collector/BasicCollector.php b/app/Export/Collector/BasicCollector.php index 353dfaf04a..4fd571c84d 100644 --- a/app/Export/Collector/BasicCollector.php +++ b/app/Export/Collector/BasicCollector.php @@ -27,7 +27,7 @@ class BasicCollector /** @var ExportJob */ protected $job; /** @var Collection */ - private $files; + private $entries; /** * BasicCollector constructor. @@ -36,24 +36,24 @@ class BasicCollector */ public function __construct(ExportJob $job) { - $this->files = new Collection; - $this->job = $job; + $this->entries = new Collection; + $this->job = $job; } /** * @return Collection */ - public function getFiles(): Collection + public function getEntries(): Collection { - return $this->files; + return $this->entries; } /** - * @param Collection $files + * @param Collection $entries */ - public function setFiles(Collection $files) + public function setEntries(Collection $entries) { - $this->files = $files; + $this->entries = $entries; } diff --git a/app/Export/Collector/CollectorInterface.php b/app/Export/Collector/CollectorInterface.php index abf427c2d2..6fc24233fe 100644 --- a/app/Export/Collector/CollectorInterface.php +++ b/app/Export/Collector/CollectorInterface.php @@ -25,7 +25,7 @@ interface CollectorInterface /** * @return Collection */ - public function getFiles(): Collection; + public function getEntries(): Collection; /** * @return bool @@ -33,9 +33,9 @@ interface CollectorInterface public function run(): bool; /** - * @param Collection $files + * @param Collection $entries * */ - public function setFiles(Collection $files); + public function setEntries(Collection $entries); } diff --git a/app/Export/Collector/JournalCollector.php b/app/Export/Collector/JournalCollector.php new file mode 100644 index 0000000000..7ff9d19571 --- /dev/null +++ b/app/Export/Collector/JournalCollector.php @@ -0,0 +1,343 @@ +getWorkSet(); + + /* + * Extract: + * possible budget ids for journals + * possible category ids journals + * possible budget ids for transactions + * possible category ids for transactions + */ + $journals = $this->extractJournalIds(); + $transactions = $this->extractTransactionIds(); + + // extend work set with category data from journals: + $this->categoryDataForJournals($journals); + + // extend work set with category cate from transactions (overrules journals): + $this->categoryDataForTransactions($transactions); + + // same for budgets: + $this->budgetDataForJournals($journals); + $this->budgetDataForTransactions($transactions); + + $this->setEntries($this->workSet); + return true; + } + + /** + * @param Collection $accounts + */ + public function setAccounts(Collection $accounts) + { + $this->accounts = $accounts; + } + + /** + * @param Carbon $start + * @param Carbon $end + */ + public function setDates(Carbon $start, Carbon $end) + { + $this->start = $start; + $this->end = $end; + } + + /** + * @param array $journals + * + * @return bool + */ + private function budgetDataForJournals(array $journals): bool + { + $set = DB::table('budget_transaction_journal') + ->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction_journal.budget_id') + ->whereIn('budget_transaction_journal.transaction_journal_id', $journals) + ->get( + [ + 'budget_transaction_journal.budget_id', + 'budget_transaction_journal.transaction_journal_id', + 'budgets.name', + 'budgets.encrypted', + ] + ); + $set->each( + function ($obj) { + $obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name; + } + ); + $array = []; + foreach ($set as $obj) { + $array[$obj->transaction_journal_id] = ['id' => $obj->budget_id, 'name' => $obj->name]; + } + + $this->workSet->each( + function ($obj) use ($array) { + if (isset($array[$obj->transaction_journal_id])) { + $obj->budget_id = $array[$obj->transaction_journal_id]['id']; + $obj->budget_name = $array[$obj->transaction_journal_id]['name']; + } + } + ); + + return true; + + } + + /** + * @param array $transactions + * + * @return bool + */ + private function budgetDataForTransactions(array $transactions): bool + { + $set = DB::table('budget_transaction') + ->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction.budget_id') + ->whereIn('budget_transaction.transaction_id', $transactions) + ->get( + [ + 'budget_transaction.budget_id', + 'budget_transaction.transaction_id', + 'budgets.name', + 'budgets.encrypted', + ] + ); + $set->each( + function ($obj) { + $obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name; + } + ); + $array = []; + foreach ($set as $obj) { + $array[$obj->transaction_id] = ['id' => $obj->budget_id, 'name' => $obj->name]; + } + + $this->workSet->each( + function ($obj) use ($array) { + + // first transaction + if (isset($array[$obj->id])) { + $obj->budget_id = $array[$obj->id]['id']; + $obj->budget_name = $array[$obj->id]['name']; + } + } + ); + + return true; + + } + + /** + * @param array $journals + * + * @return bool + */ + private function categoryDataForJournals(array $journals): bool + { + $set = DB::table('category_transaction_journal') + ->leftJoin('categories', 'categories.id', '=', 'category_transaction_journal.category_id') + ->whereIn('category_transaction_journal.transaction_journal_id', $journals) + ->get( + [ + 'category_transaction_journal.category_id', + 'category_transaction_journal.transaction_journal_id', + 'categories.name', + 'categories.encrypted', + ] + ); + $set->each( + function ($obj) { + $obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name; + } + ); + $array = []; + foreach ($set as $obj) { + $array[$obj->transaction_journal_id] = ['id' => $obj->category_id, 'name' => $obj->name]; + } + + $this->workSet->each( + function ($obj) use ($array) { + if (isset($array[$obj->transaction_journal_id])) { + $obj->category_id = $array[$obj->transaction_journal_id]['id']; + $obj->category_name = $array[$obj->transaction_journal_id]['name']; + } + } + ); + + return true; + + } + + /** + * @param array $transactions + * + * @return bool + */ + private function categoryDataForTransactions(array $transactions): bool + { + $set = DB::table('category_transaction') + ->leftJoin('categories', 'categories.id', '=', 'category_transaction.category_id') + ->whereIn('category_transaction.transaction_id', $transactions) + ->get( + [ + 'category_transaction.category_id', + 'category_transaction.transaction_id', + 'categories.name', + 'categories.encrypted', + ] + ); + $set->each( + function ($obj) { + $obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name; + } + ); + $array = []; + foreach ($set as $obj) { + $array[$obj->transaction_id] = ['id' => $obj->category_id, 'name' => $obj->name]; + } + + $this->workSet->each( + function ($obj) use ($array) { + + // first transaction + if (isset($array[$obj->id])) { + $obj->category_id = $array[$obj->id]['id']; + $obj->category_name = $array[$obj->id]['name']; + } + } + ); + + return true; + + } + + /** + * @return array + */ + private function extractJournalIds(): array + { + return $this->workSet->pluck('transaction_journal_id')->toArray(); + } + + /** + * @return array + */ + private function extractTransactionIds() + { + $set = $this->workSet->pluck('id')->toArray(); + $opposing = $this->workSet->pluck('opposing_id')->toArray(); + $complete = $set + $opposing; + + return array_unique($complete); + } + + /** + * + */ + private function getWorkSet() + { + $accountIds = $this->accounts->pluck('id')->toArray(); + $this->workSet = Transaction + ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->leftJoin( + 'transactions AS opposing', function (JoinClause $join) { + $join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id') + ->where('opposing.amount', '=', DB::raw('transactions.amount * -1')) + ->where('transactions.identifier', '=', 'opposing.identifier'); + } + ) + ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') + ->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id') + ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id') + ->leftJoin('transaction_currencies', 'transaction_journals.transaction_currency_id', '=', 'transaction_currencies.id') + ->whereIn('transactions.account_id', $accountIds) + ->where('transaction_journals.user_id', $this->job->user_id) + ->where('transaction_journals.date', '>=', $this->start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $this->end->format('Y-m-d')) + ->where('transaction_journals.completed', 1) + ->whereNull('transaction_journals.deleted_at') + ->whereNull('transactions.deleted_at') + ->whereNull('opposing.deleted_at') + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transactions.identifier', 'ASC') + ->get( + [ + 'transactions.id', + 'transactions.amount', + 'transactions.description', + 'transactions.account_id', + 'accounts.name as account_name', + 'accounts.encrypted as account_name_encrypted', + 'transactions.identifier', + + 'opposing.id as opposing_id', + 'opposing.amount AS opposing_amount', + 'opposing.description as opposing_description', + 'opposing.account_id as opposing_account_id', + 'opposing_accounts.name as opposing_account_name', + 'opposing_accounts.encrypted as opposing_account_encrypted', + 'opposing.identifier as opposing_identifier', + + 'transaction_journals.id as transaction_journal_id', + 'transaction_journals.date', + 'transaction_journals.description as journal_description', + 'transaction_journals.encrypted as journal_encrypted', + 'transaction_journals.transaction_type_id', + 'transaction_types.type as transaction_type', + 'transaction_journals.transaction_currency_id', + 'transaction_currencies.code AS transaction_currency_code', + + ] + ); + } +} \ No newline at end of file diff --git a/app/Export/Collector/UploadCollector.php b/app/Export/Collector/UploadCollector.php index b60a17035f..044662b617 100644 --- a/app/Export/Collector/UploadCollector.php +++ b/app/Export/Collector/UploadCollector.php @@ -26,16 +26,14 @@ use Storage; */ class UploadCollector extends BasicCollector implements CollectorInterface { - /** @var string */ - private $expected; /** @var \Illuminate\Contracts\Filesystem\Filesystem */ private $exportDisk; - private $importKeys = []; /** @var \Illuminate\Contracts\Filesystem\Filesystem */ private $uploadDisk; + /** @var string */ + private $vintageFormat; /** - * * AttachmentCollector constructor. * * @param ExportJob $job @@ -51,50 +49,82 @@ class UploadCollector extends BasicCollector implements CollectorInterface $this->exportDisk = Storage::disk('export'); // file names associated with the old import routine. - $this->expected = 'csv-upload-' . auth()->user()->id . '-'; + $this->vintageFormat = sprintf('csv-upload-%d-', auth()->user()->id); - // for the new import routine: - $this->getImportKeys(); } /** + * Is called from the outside to actually start the export. + * * @return bool */ public function run(): bool { - // grab upload directory. - $files = $this->uploadDisk->files(); + // collect old upload files (names beginning with "csv-upload". + $this->collectVintageUploads(); - foreach ($files as $entry) { - $this->processUpload($entry); + // then collect current upload files: + $this->collectModernUploads(); + + + // // grab upload directory. + // $files = $this->uploadDisk->files(); + // + // foreach ($files as $entry) { + // $this->processUpload($entry); + // } + + return true; + } + + /** + * This method collects all the uploads that are uploaded using the new importer. So after the summer of 2016. + * + * @return bool + */ + private function collectModernUploads(): bool + { + $set = $this->job->user->importJobs()->where('status', 'import_complete')->get(['import_jobs.*']); + $keys = []; + if ($set->count() > 0) { + $keys = $set->pluck('key')->toArray(); + } + + foreach ($keys as $key) { + $this->processModernUpload($key); } return true; } /** + * This method collects all the uploads that are uploaded using the "old" importer. So from before the summer of 2016. * + * @return bool */ - private function getImportKeys() + private function collectVintageUploads():bool { - $set = auth()->user()->importJobs()->where('status', 'import_complete')->get(['import_jobs.*']); - if ($set->count() > 0) { - $keys = $set->pluck('key')->toArray(); - $this->importKeys = $keys; + // grab upload directory. + $files = $this->uploadDisk->files(); + foreach ($files as $entry) { + $this->processVintageUpload($entry); } - Log::debug('Valid import keys are ', $this->importKeys); + + return true; } /** + * This method tells you when the vintage upload file was actually uploaded. + * * @param string $entry * * @return string */ - private function getOriginalUploadDate(string $entry): string + private function getVintageUploadDate(string $entry): string { // this is an original upload. - $parts = explode('-', str_replace(['.csv.encrypted', $this->expected], '', $entry)); + $parts = explode('-', str_replace(['.csv.encrypted', $this->vintageFormat], '', $entry)); $originalUpload = intval($parts[1]); $date = date('Y-m-d \a\t H-i-s', $originalUpload); @@ -102,33 +132,17 @@ class UploadCollector extends BasicCollector implements CollectorInterface } /** + * Tells you if a file name is a vintage upload. + * * @param string $entry * * @return bool */ - private function isImportFile(string $entry): bool + private function isVintageImport(string $entry): bool { - $name = str_replace('.upload', '', $entry); - if (in_array($name, $this->importKeys)) { - Log::debug(sprintf('Import file "%s" is in array', $name), $this->importKeys); - - return true; - } - Log::debug(sprintf('Import file "%s" is NOT in array', $name), $this->importKeys); - - return false; - } - - /** - * @param string $entry - * - * @return bool - */ - private function isOldImport(string $entry): bool - { - $len = strlen($this->expected); + $len = strlen($this->vintageFormat); // file is part of the old import routine: - if (substr($entry, 0, $len) === $this->expected) { + if (substr($entry, 0, $len) === $this->vintageFormat) { return true; } @@ -137,49 +151,62 @@ class UploadCollector extends BasicCollector implements CollectorInterface } /** - * @param $entry + * @param string $key + * + * @return bool */ - private function processUpload(string $entry) - { - // file is old import: - if ($this->isOldImport($entry)) { - $this->saveOldImportFile($entry); - } - - // file is current import. - if ($this->isImportFile($entry)) { - $this->saveImportFile($entry); - } - } - - /** - * @param string $entry - */ - private function saveImportFile(string $entry) + private function processModernUpload(string $key): bool { // find job associated with import file: - $name = str_replace('.upload', '', $entry); - $job = auth()->user()->importJobs()->where('key', $name)->first(); - $content = ''; - try { - $content = Crypt::decrypt($this->uploadDisk->get($entry)); - } catch (DecryptException $e) { - Log::error('Could not decrypt old import file ' . $entry . '. Skipped because ' . $e->getMessage()); + $job = $this->job->user->importJobs()->where('key', $key)->first(); + if (is_null($job)) { + return false; } - if (!is_null($job) && strlen($content) > 0) { + // find the file for this import: + $content = ''; + try { + $content = Crypt::decrypt($this->uploadDisk->get(sprintf('%s.upload', $key))); + } catch (DecryptException $e) { + Log::error(sprintf('Could not decrypt old import file "%s". Skipped because: %s', $key, $e->getMessage())); + } + + if (strlen($content) > 0) { // add to export disk. $date = $job->created_at->format('Y-m-d'); $file = sprintf('%s-Old %s import dated %s.%s', $this->job->key, strtoupper($job->file_type), $date, $job->file_type); $this->exportDisk->put($file, $content); - $this->getFiles()->push($file); + $this->getEntries()->push($file); } + + return true; } /** + * If the file is a vintage upload, process it. + * + * @param string $entry + * + * @return bool + */ + private function processVintageUpload(string $entry): bool + { + if ($this->isVintageImport($entry)) { + $this->saveVintageImportFile($entry); + + return true; + } + + return false; + } + + + /** + * This will store the content of the old vintage upload somewhere. + * * @param string $entry */ - private function saveOldImportFile(string $entry) + private function saveVintageImportFile(string $entry) { $content = ''; try { @@ -190,10 +217,10 @@ class UploadCollector extends BasicCollector implements CollectorInterface if (strlen($content) > 0) { // add to export disk. - $date = $this->getOriginalUploadDate($entry); + $date = $this->getVintageUploadDate($entry); $file = $this->job->key . '-Old import dated ' . $date . '.csv'; $this->exportDisk->put($file, $content); - $this->getFiles()->push($file); + $this->getEntries()->push($file); } } diff --git a/app/Export/ConfigurationFile.php b/app/Export/ConfigurationFile.php deleted file mode 100644 index be32ea8df5..0000000000 --- a/app/Export/ConfigurationFile.php +++ /dev/null @@ -1,68 +0,0 @@ -job = $job; - $this->exportDisk = Storage::disk('export'); - } - - /** - * @return string - */ - public function make(): string - { - $fields = array_keys(Entry::getFieldsAndTypes()); - $types = Entry::getFieldsAndTypes(); - - $configuration = [ - 'date-format' => 'Y-m-d', // unfortunately, this is hard-coded. - 'has-headers' => true, - 'map' => [], // we could build a map if necessary for easy re-import. - 'roles' => [], - 'mapped' => [], - 'specifix' => [], - ]; - foreach ($fields as $field) { - $configuration['roles'][] = $types[$field]; - } - $file = $this->job->key . '-configuration.json'; - $this->exportDisk->put($file, json_encode($configuration, JSON_PRETTY_PRINT)); - - return $file; - } - -} diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php index fb7c12b68a..3723d7a14e 100644 --- a/app/Export/Entry/Entry.php +++ b/app/Export/Entry/Entry.php @@ -13,8 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Export\Entry; -use FireflyIII\Models\TransactionJournal; -use Illuminate\Support\Collection; +use Crypt; /** * To extend the exported object, in case of new features in Firefly III for example, @@ -35,98 +34,75 @@ use Illuminate\Support\Collection; */ final class Entry { - /** @var string */ - public $amount; - /** @var EntryBill */ - public $bill; - /** @var EntryBudget */ - public $budget; - /** @var EntryCategory */ - public $category; - /** @var string */ + public $journal_id; public $date; - /** @var string */ public $description; - /** @var EntryAccount */ - public $destinationAccount; - /** @var Collection */ - public $destinationAccounts; - /** @var EntryAccount */ - public $sourceAccount; - /** @var Collection */ - public $sourceAccounts; + + public $currency_code; + public $amount; + + public $transaction_type; + + public $source_account_id; + public $source_account_name; + + public $destination_account_id; + public $destination_account_name; + + + public $budget_id; + public $budget_name; + public $category_id; + public $category_name; /** * Entry constructor. */ private function __construct() { - $this->sourceAccounts = new Collection; - $this->destinationAccounts = new Collection; } /** - * @param TransactionJournal $journal + * @param $object * * @return Entry */ - public static function fromJournal(TransactionJournal $journal) + public static function fromObject($object): Entry { + $entry = new self; - $entry = new self; - $entry->description = $journal->description; - $entry->date = $journal->date->format('Y-m-d'); - $entry->amount = TransactionJournal::amount($journal); + // journal information: + $entry->journal_id = $object->transaction_journal_id; + $entry->description = $object->journal_encrypted === 1 ? Crypt::decrypt($object->journal_description) : $object->journal_description; + $entry->amount = $object->amount; // always positive + $entry->date = $object->date; + $entry->transaction_type = $object->transaction_type; + $entry->currency_code = $object->transaction_currency_code; - $entry->budget = new EntryBudget($journal->budgets->first()); - $entry->category = new EntryCategory($journal->categories->first()); - $entry->bill = new EntryBill($journal->bill); + // source information: + $entry->source_account_id = $object->account_id; + $entry->source_account_name = $object->account_name_encrypted === 1 ? Crypt::decrypt($object->account_name) : $object->account_name; - $sources = TransactionJournal::sourceAccountList($journal); - $destinations = TransactionJournal::destinationAccountList($journal); - $entry->sourceAccount = new EntryAccount($sources->first()); - $entry->destinationAccount = new EntryAccount($destinations->first()); - foreach ($sources as $source) { - $entry->sourceAccounts->push(new EntryAccount($source)); - } + // destination information + $entry->destination_account_id = $object->opposing_account_id; + $entry->destination_account_name = $object->opposing_account_encrypted === 1 ? Crypt::decrypt($object->opposing_account_name) + : $object->opposing_account_name; - foreach ($destinations as $destination) { - $entry->destinationAccounts->push(new EntryAccount($destination)); + + // category and budget + $entry->category_id = $object->category_id ?? ''; + $entry->category_name = $object->category_name ?? ''; + $entry->budget_id = $object->budget_id ?? ''; + $entry->budget_name = $object->budget_name ?? ''; + + + // update description when transaction description is different: + if (!is_null($object->description) && $object->description != $entry->description) { + $entry->description = $entry->description . ' (' . $object->description . ')'; } return $entry; - - } - - /** - * @return array - */ - public static function getFieldsAndTypes(): array - { - // key = field name (see top of class) - // value = field type (see csv.php under 'roles') - return [ - 'description' => 'description', - 'amount' => 'amount', - 'date' => 'date-transaction', - 'source_account_id' => 'account-id', - 'source_account_name' => 'account-name', - 'source_account_iban' => 'account-iban', - 'source_account_type' => '_ignore', - 'source_account_number' => 'account-number', - 'destination_account_id' => 'opposing-id', - 'destination_account_name' => 'opposing-name', - 'destination_account_iban' => 'opposing-iban', - 'destination_account_type' => '_ignore', - 'destination_account_number' => 'account-number', - 'budget_id' => 'budget-id', - 'budget_name' => 'budget-name', - 'category_id' => 'category-id', - 'category_name' => 'category-name', - 'bill_id' => 'bill-id', - 'bill_name' => 'bill-name', - ]; } } diff --git a/app/Export/Entry/EntryAccount.php b/app/Export/Entry/EntryAccount.php deleted file mode 100644 index 8ea2f95b8e..0000000000 --- a/app/Export/Entry/EntryAccount.php +++ /dev/null @@ -1,49 +0,0 @@ -accountId = $account->id; - $this->name = $account->name; - $this->iban = $account->iban; - $this->type = $account->accountType->type; - $this->number = $account->getMeta('accountNumber'); - } -} diff --git a/app/Export/Entry/EntryBill.php b/app/Export/Entry/EntryBill.php deleted file mode 100644 index 723975253a..0000000000 --- a/app/Export/Entry/EntryBill.php +++ /dev/null @@ -1,43 +0,0 @@ -billId = $bill->id; - $this->name = $bill->name; - } - } - -} diff --git a/app/Export/Entry/EntryBudget.php b/app/Export/Entry/EntryBudget.php deleted file mode 100644 index cbad41a283..0000000000 --- a/app/Export/Entry/EntryBudget.php +++ /dev/null @@ -1,43 +0,0 @@ -budgetId = $budget->id; - $this->name = $budget->name; - } - } - -} diff --git a/app/Export/Entry/EntryCategory.php b/app/Export/Entry/EntryCategory.php deleted file mode 100644 index 051137f11f..0000000000 --- a/app/Export/Entry/EntryCategory.php +++ /dev/null @@ -1,42 +0,0 @@ -categoryId = $category->id; - $this->name = $category->name; - } - } -} diff --git a/app/Export/Exporter/CsvExporter.php b/app/Export/Exporter/CsvExporter.php index d13497ce6a..0091b55ff1 100644 --- a/app/Export/Exporter/CsvExporter.php +++ b/app/Export/Exporter/CsvExporter.php @@ -16,7 +16,6 @@ namespace FireflyIII\Export\Exporter; use FireflyIII\Export\Entry\Entry; use FireflyIII\Export\Entry\EntryAccount; use FireflyIII\Models\ExportJob; -use Illuminate\Support\Collection; use League\Csv\Writer; use SplFileObject; @@ -62,110 +61,24 @@ class CsvExporter extends BasicExporter implements ExporterInterface $writer = Writer::createFromPath(new SplFileObject($fullPath, 'a+'), 'w'); $rows = []; - // Count the maximum number of sources and destinations each entry has. May need to expand the number of export fields: - $maxSourceAccounts = 1; - $maxDestAccounts = 1; - /** @var Entry $entry */ - foreach ($this->getEntries() as $entry) { - $sources = $entry->sourceAccounts->count(); - $destinations = $entry->destinationAccounts->count(); - $maxSourceAccounts = max($maxSourceAccounts, $sources); - $maxDestAccounts = max($maxDestAccounts, $destinations); - } - $rows[] = array_keys($this->getFieldsAndTypes($maxSourceAccounts, $maxDestAccounts)); + // get field names for header row: + $first = $this->getEntries()->first(); + $headers = array_keys(get_object_vars($first)); + $rows[] = $headers; /** @var Entry $entry */ foreach ($this->getEntries() as $entry) { - // order is defined in Entry::getFieldsAndTypes. - $current = [$entry->description, $entry->amount, $entry->date]; - $sourceData = $this->getAccountData($maxSourceAccounts, $entry->sourceAccounts); - $current = array_merge($current, $sourceData); - $destData = $this->getAccountData($maxDestAccounts, $entry->destinationAccounts); - $current = array_merge($current, $destData); - $rest = [$entry->budget->budgetId, $entry->budget->name, $entry->category->categoryId, $entry->category->name, $entry->bill->billId, - $entry->bill->name]; - $current = array_merge($current, $rest); - $rows[] = $current; + $line = []; + foreach ($headers as $header) { + $line[] = $entry->$header; + } + $rows[] = $line; } $writer->insertAll($rows); return true; } - /** - * @param int $max - * @param Collection $accounts - * - * @return array - */ - private function getAccountData(int $max, Collection $accounts): array - { - $current = []; - for ($i = 0; $i < $max; $i++) { - /** @var EntryAccount $source */ - $source = $accounts->get($i); - $currentId = ''; - $currentName = ''; - $currentIban = ''; - $currentType = ''; - $currentNumber = ''; - if ($source) { - $currentId = $source->accountId; - $currentName = $source->name; - $currentIban = $source->iban; - $currentType = $source->type; - $currentNumber = $source->number; - } - $current[] = $currentId; - $current[] = $currentName; - $current[] = $currentIban; - $current[] = $currentType; - $current[] = $currentNumber; - } - unset($source); - - return $current; - } - - /** - * @param int $sources - * @param int $destinations - * - * @return array - */ - private function getFieldsAndTypes(int $sources, int $destinations): array - { - // key = field name (see top of class) - // value = field type (see csv.php under 'roles') - $array = [ - 'description' => 'description', - 'amount' => 'amount', - 'date' => 'date-transaction', - ]; - for ($i = 0; $i < $sources; $i++) { - $array['source_account_' . $i . '_id'] = 'account-id'; - $array['source_account_' . $i . '_name'] = 'account-name'; - $array['source_account_' . $i . '_iban'] = 'account-iban'; - $array['source_account_' . $i . '_type'] = '_ignore'; - $array['source_account_' . $i . '_number'] = 'account-number'; - } - for ($i = 0; $i < $destinations; $i++) { - $array['destination_account_' . $i . '_id'] = 'account-id'; - $array['destination_account_' . $i . '_name'] = 'account-name'; - $array['destination_account_' . $i . '_iban'] = 'account-iban'; - $array['destination_account_' . $i . '_type'] = '_ignore'; - $array['destination_account_' . $i . '_number'] = 'account-number'; - } - - $array['budget_id'] = 'budget-id'; - $array['budget_name'] = 'budget-name'; - $array['category_id'] = 'category-id'; - $array['category_name'] = 'category-name'; - $array['bill_id'] = 'bill-id'; - $array['bill_name'] = 'bill-name'; - - return $array; - } private function tempFile() { diff --git a/app/Export/Processor.php b/app/Export/Processor.php index 7c33ae04b2..88669c6d72 100644 --- a/app/Export/Processor.php +++ b/app/Export/Processor.php @@ -15,13 +15,13 @@ namespace FireflyIII\Export; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Export\Collector\AttachmentCollector; +use FireflyIII\Export\Collector\JournalCollector; use FireflyIII\Export\Collector\UploadCollector; use FireflyIII\Export\Entry\Entry; use FireflyIII\Models\ExportJob; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Repositories\Journal\JournalTaskerInterface; use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Collection; +use Log; use Storage; use ZipArchive; @@ -40,8 +40,6 @@ class Processor /** @var bool */ public $includeAttachments; /** @var bool */ - public $includeConfig; - /** @var bool */ public $includeOldUploads; /** @var ExportJob */ public $job; @@ -68,7 +66,6 @@ class Processor $this->accounts = $settings['accounts']; $this->exportFormat = $settings['exportFormat']; $this->includeAttachments = $settings['includeAttachments']; - $this->includeConfig = $settings['includeConfig']; $this->includeOldUploads = $settings['includeOldUploads']; $this->job = $settings['job']; $this->journals = new Collection; @@ -84,8 +81,9 @@ class Processor { /** @var AttachmentCollector $attachmentCollector */ $attachmentCollector = app(AttachmentCollector::class, [$this->job]); + $attachmentCollector->setDates($this->settings['startDate'], $this->settings['endDate']); $attachmentCollector->run(); - $this->files = $this->files->merge($attachmentCollector->getFiles()); + $this->files = $this->files->merge($attachmentCollector->getEntries()); return true; } @@ -95,9 +93,13 @@ class Processor */ public function collectJournals(): bool { - /** @var JournalTaskerInterface $tasker */ - $tasker = app(JournalTaskerInterface::class); - $this->journals = $tasker->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']); + /** @var JournalCollector $collector */ + $collector = app(JournalCollector::class, [$this->job]); + $collector->setDates($this->settings['startDate'], $this->settings['endDate']); + $collector->setAccounts($this->settings['accounts']); + $collector->run(); + $this->journals = $collector->getEntries(); + Log::debug(sprintf('Count %d journals in collectJournals() ', $this->journals->count())); return true; } @@ -111,7 +113,7 @@ class Processor $uploadCollector = app(UploadCollector::class, [$this->job]); $uploadCollector->run(); - $this->files = $this->files->merge($uploadCollector->getFiles()); + $this->files = $this->files->merge($uploadCollector->getEntries()); return true; } @@ -122,22 +124,11 @@ class Processor public function convertJournals(): bool { $count = 0; - /** @var TransactionJournal $journal */ - foreach ($this->journals as $journal) { - $this->exportEntries->push(Entry::fromJournal($journal)); + foreach ($this->journals as $object) { + $this->exportEntries->push(Entry::fromObject($object)); $count++; } - - return true; - } - - /** - * @return bool - */ - public function createConfigFile(): bool - { - $this->configurationMaker = app(ConfigurationFile::class, [$this->job]); - $this->files->push($this->configurationMaker->make()); + Log::debug(sprintf('Count %d entries in exportEntries (convertJournals)', $this->exportEntries->count())); return true; } diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 832be8d57a..f8c20bc747 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -133,7 +133,6 @@ class ExportController extends Controller 'endDate' => new Carbon($request->get('export_end_range')), 'exportFormat' => $request->get('exportFormat'), 'includeAttachments' => intval($request->get('include_attachments')) === 1, - 'includeConfig' => intval($request->get('include_config')) === 1, 'includeOldUploads' => intval($request->get('include_old_uploads')) === 1, 'job' => $job, ]; @@ -177,15 +176,6 @@ class ExportController extends Controller $job->change('export_status_collected_old_uploads'); } - /* - * Generate / collect config file. - */ - if ($settings['includeConfig']) { - $job->change('export_status_creating_config_file'); - $processor->createConfigFile(); - $job->change('export_status_created_config_file'); - } - /* * Create ZIP file: */ diff --git a/app/Repositories/Attachment/AttachmentRepository.php b/app/Repositories/Attachment/AttachmentRepository.php index 4f60e36982..065b5c0167 100644 --- a/app/Repositories/Attachment/AttachmentRepository.php +++ b/app/Repositories/Attachment/AttachmentRepository.php @@ -13,6 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Attachment; +use Carbon\Carbon; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Models\Attachment; use FireflyIII\User; @@ -63,6 +64,24 @@ class AttachmentRepository implements AttachmentRepositoryInterface return $this->user->attachments()->get(); } + /** + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getBetween(Carbon $start, Carbon $end): Collection + { + $query = $this->user + ->attachments() + ->leftJoin('transaction_journals', 'attachments.attachable_id', '=', 'transaction_journals.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->get(['attachments.*']); + + return $query; + } + /** * @param Attachment $attachment * @param array $data diff --git a/app/Repositories/Attachment/AttachmentRepositoryInterface.php b/app/Repositories/Attachment/AttachmentRepositoryInterface.php index 92c86b5203..912dc67190 100644 --- a/app/Repositories/Attachment/AttachmentRepositoryInterface.php +++ b/app/Repositories/Attachment/AttachmentRepositoryInterface.php @@ -13,6 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Attachment; +use Carbon\Carbon; use FireflyIII\Models\Attachment; use Illuminate\Support\Collection; @@ -36,6 +37,14 @@ interface AttachmentRepositoryInterface */ public function get(): Collection; + /** + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getBetween(Carbon $start, Carbon $end): Collection; + /** * @param Attachment $attachment * @param array $attachmentData diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 768413e3fd..0240034055 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -99,7 +99,6 @@ return [ 'export_format_csv' => 'Comma separated values (CSV file)', 'export_format_mt940' => 'MT940 compatible format', 'export_included_accounts' => 'Export transactions from these accounts', - 'include_config_help' => 'For easy re-import into Firefly III', 'include_old_uploads_help' => 'Firefly III does not throw away the original CSV files you have imported in the past. You can include them in your export.', 'do_export' => 'Export', 'export_status_never_started' => 'The export has not started yet', diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index e79b610a04..5cfd1a3688 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -98,7 +98,6 @@ return [ 'export_end_range' => 'End of export range', 'export_format' => 'File format', 'include_attachments' => 'Include uploaded attachments', - 'include_config' => 'Include configuration file', 'include_old_uploads' => 'Include imported data', 'accounts' => 'Export transactions from these accounts', 'delete_account' => 'Delete account ":name"', diff --git a/resources/views/export/index.twig b/resources/views/export/index.twig index c4c4d64077..1b599e19d5 100644 --- a/resources/views/export/index.twig +++ b/resources/views/export/index.twig @@ -81,8 +81,6 @@ {{ ExpandedForm.checkbox('include_attachments','1', true) }} - {{ ExpandedForm.checkbox('include_config','1', true, {helpText: 'include_config_help'|_}) }} - {{ ExpandedForm.checkbox('include_old_uploads','1', false, {helpText: 'include_old_uploads_help'|_}) }}