File indexing completed on 2024-06-16 04:47:22

0001 /***************************************************************************
0002  * SPDX-FileCopyrightText: 2022 S. MANKOWSKI stephane@mankowski.fr
0003  * SPDX-FileCopyrightText: 2022 G. DE BURE support@mankowski.fr
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  ***************************************************************************/
0006 /** @file
0007  * This file is Skrooge plugin for MT940 import / export.
0008  *
0009  * @author Stephane MANKOWSKI / Guillaume DE BURE
0010  */
0011 #include "skgimportpluginmt940.h"
0012 
0013 #include <qfile.h>
0014 
0015 #include <klocalizedstring.h>
0016 #include <kpluginfactory.h>
0017 
0018 #include "skgbankincludes.h"
0019 #include "skgimportexportmanager.h"
0020 #include "skgobjectbase.h"
0021 #include "skgservices.h"
0022 #include "skgtraces.h"
0023 
0024 /**
0025  * This plugin factory.
0026  */
0027 K_PLUGIN_CLASS_WITH_JSON(SKGImportPluginMT940, "metadata.json")
0028 
0029 SKGImportPluginMT940::SKGImportPluginMT940(QObject* iImporter, const QVariantList& iArg)
0030     : SKGImportPlugin(iImporter)
0031 {
0032     SKGTRACEINFUNC(10)
0033     Q_UNUSED(iArg)
0034 }
0035 
0036 SKGImportPluginMT940::~SKGImportPluginMT940()
0037     = default;
0038 
0039 bool SKGImportPluginMT940::isImportPossible()
0040 {
0041     SKGTRACEINFUNC(10)
0042     return (m_importer->getDocument() == nullptr ? true : m_importer->getFileNameExtension() == QStringLiteral("MT940") || m_importer->getFileNameExtension() == QStringLiteral("STA"));
0043 }
0044 
0045 SKGError SKGImportPluginMT940::importFile()
0046 {
0047     if (m_importer->getDocument() == nullptr) {
0048         return SKGError(ERR_ABORT, i18nc("Error message", "Invalid parameters"));
0049     }
0050     SKGError err;
0051     SKGTRACEINFUNCRC(2, err)
0052 
0053     // Begin transaction
0054     err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import %1 file", "MT940"), 2);
0055     IFOK(err) {
0056         // Open file
0057         IFOK(err) {
0058             QFile file(m_importer->getLocalFileName());
0059             if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
0060                 err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Open file '%1' failed", m_importer->getFileName().toDisplayString()));
0061             } else {
0062                 // Read lines
0063                 QStringList lines;
0064                 {
0065                     QTextStream stream(&file);
0066                     if (!m_importer->getCodec().isEmpty()) {
0067                         stream.setCodec(m_importer->getCodec().toLatin1().constData());
0068                     }
0069                     while (!stream.atEnd()) {
0070                         // Read line
0071                         QString line = stream.readLine().trimmed();
0072                         if (!line.isEmpty()) {
0073                             lines.push_back(line);
0074                         }
0075                     }
0076                 }
0077                 // close file
0078                 file.close();
0079 
0080                 // Step 1 done
0081                 IFOKDO(err, m_importer->getDocument()->stepForward(1))
0082 
0083                 // Read lines
0084                 bool importActivated = false;
0085                 bool in86 = false;
0086                 SKGAccountObject account;
0087                 SKGOperationObject operation;
0088                 SKGUnitObject unit;
0089                 QString bankName = QStringLiteral("MT940");
0090 
0091                 int nb = lines.count();
0092                 IFOKDO(err, m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import transactions"), nb))
0093                 for (int i = 0; i < nb && !err; ++i) {
0094                     // Read line
0095                     const QString& line = lines.at(i);
0096                     if (!line.isEmpty()) {
0097                         if (line.startsWith(QLatin1String(":20:"))) {
0098                             importActivated = true;
0099                             in86 = false;
0100                             bankName = line.right(line.count() - 4);
0101                         } else if (importActivated) {
0102                             if (line.startsWith(QLatin1String(":25:"))) {
0103                                 // Account
0104                                 QStringList vals = SKGServices::splitCSVLine(line.right(line.count() - 4), '/');
0105                                 QString name = QStringLiteral("MT940");
0106                                 QString number = name;
0107                                 if (vals.count() == 2) {
0108                                     bankName = vals.at(0);
0109                                     number = vals.at(1);
0110                                     name = number;
0111                                 } else if (vals.count() == 1) {
0112                                     number = vals.at(0);
0113                                     name = number;
0114                                 }
0115 
0116                                 // Search if account is already existing
0117                                 SKGObjectBase::SKGListSKGObjectBase listAccount;
0118                                 err = m_importer->getDocument()->getObjects(QStringLiteral("v_account"), "t_number='" % number % '\'', listAccount);
0119                                 IFOK(err) {
0120                                     if (listAccount.count() == 1) {
0121                                         // Yes ! Only one account found
0122                                         account = listAccount.at(0);
0123                                         err = m_importer->getDocument()->sendMessage(i18nc("An information message",  "Using account '%1' for import", account.getName()));
0124                                     } else {
0125                                         if (listAccount.count() > 1) {
0126                                             err = m_importer->getDocument()->sendMessage(i18nc("An information message",  "More than one possible account found."));
0127                                         }
0128 
0129                                         SKGBankObject bank(m_importer->getDocument());
0130                                         IFOKDO(err, bank.setName(bankName))
0131                                         IFOKDO(err, bank.setNumber(bankName))
0132                                         if (!err && bank.load().isFailed()) {
0133                                             err = bank.save();
0134                                         }
0135                                         IFOKDO(err, bank.addAccount(account))
0136                                         IFOKDO(err, account.setName(name))
0137                                         IFOKDO(err, account.setNumber(number))
0138                                         IFOKDO(err, account.setType(SKGAccountObject::CURRENT))
0139                                         if (!err && account.load().isFailed()) {
0140                                             err = account.save();
0141                                         }
0142                                         IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message",  "Default account '%1' created for import", name)))
0143                                     }
0144                                 }
0145 
0146                                 in86 = false;
0147                             } else if (line.startsWith(QLatin1String(":28C:"))) {
0148                                 in86 = false;
0149                             } else if (line.startsWith(QLatin1String(":60F:")) || line.startsWith(QLatin1String(":60M:"))) {
0150                                 QString val = line.right(line.count() - 5);
0151                                 err = SKGUnitObject::createCurrencyUnit(m_importer->getDocument(), val.mid(7, 3), unit);
0152                                 // Example C040802EUR16,40
0153                                 if (account.getNbOperation() > 1) {
0154                                     IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message", "The initial balance of '%1' has not been set because some transactions are already existing", account.getName()), SKGDocument::Warning))
0155                                 } else {
0156                                     // Set initial balance
0157                                     IFOKDO(err, account.setInitialBalance((val[0] == 'C' ? 1.0 : -1.0) * SKGServices::stringToDouble(val.right(val.count() - 10)), unit))
0158                                     IFOKDO(err, account.save())
0159                                     IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message", "The initial balance of '%1' has been set with MT940 file content", account.getName())))
0160                                 }
0161 
0162                                 in86 = false;
0163                             } else if (line.startsWith(QLatin1String(":62F:")) || line.startsWith(QLatin1String(":62M:"))) {
0164                                 in86 = false;
0165                             } else if (line.startsWith(QLatin1String(":61:"))) {
0166                                 // Example :61:0712280103D000000000200,00FMSCNONREF
0167                                 QString val = line.right(line.count() - 4);
0168                                 QDate date = QDate::fromString(val.left(6), QStringLiteral("yyMMdd"));
0169                                 if (date.year() < 1970) {
0170                                     date = date.addYears(100);
0171                                 }
0172 
0173                                 int index = (val[10] == 'R' ? 11 : 10);
0174                                 double sign = (val[index] == 'C' ? 1.0 : -1.0);
0175                                 ++index;
0176 
0177                                 if (val[index] == 'R') {
0178                                     ++index;
0179                                 }
0180 
0181                                 QString amountString;
0182                                 while (true) {
0183                                     if ((val[index] >= '0' && val[index] <= '9') || val[index] == '-' || val[index] == ',') {
0184                                         amountString += val[index];
0185                                         ++index;
0186                                     } else {
0187                                         break;
0188                                     }
0189                                 }
0190 
0191                                 err = account.addOperation(operation, true);
0192                                 IFOKDO(err, operation.setDate(date))
0193                                 IFOKDO(err, operation.setUnit(unit))
0194                                 IFOKDO(err, operation.setAttribute(QStringLiteral("t_imported"), QStringLiteral("T")))
0195                                 // if(!err) err = operation.setImportID("MT940");
0196                                 IFOKDO(err, operation.save())
0197 
0198                                 SKGSubOperationObject subop;
0199                                 err = operation.addSubOperation(subop);
0200                                 IFOKDO(err, subop.setQuantity(sign * SKGServices::stringToDouble(amountString)))
0201                                 IFOKDO(err, subop.save())
0202 
0203                                 in86 = false;
0204                             } else if (line.startsWith(QLatin1String(":NS:")) && !operation.exist()) {
0205                                 QString val = line.right(line.count() - 4);
0206                                 QString comment = account.getComment();
0207                                 comment += (comment.isEmpty() || val.isEmpty() ? "" : " ") % val;
0208                                 IFOKDO(err, account.setComment(comment))
0209                                 IFOKDO(err, account.save())
0210                             } else if (line.startsWith(QLatin1String(":86:")) || line.startsWith(QLatin1String(":NS:"))) {
0211                                 QString val = line.right(line.count() - 4);
0212                                 QString comment = operation.getComment();
0213                                 comment += (comment.isEmpty() || val.isEmpty() ? "" : " ") % val;
0214                                 IFOKDO(err, operation.setComment(comment))
0215                                 IFOKDO(err, operation.save())
0216 
0217                                 in86 = true;
0218                             } else if (in86) {
0219                                 const QString& val = line;
0220                                 QString comment = operation.getComment();
0221                                 comment += (comment.isEmpty() || val.isEmpty() ? "" : " ") % val;
0222                                 IFOKDO(err, operation.setComment(comment))
0223                                 IFOKDO(err, operation.save())
0224                             }
0225                         }
0226                     }
0227                     IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
0228                 }
0229                 SKGENDTRANSACTION(m_importer->getDocument(),  err)
0230 
0231                 // Step 2 done
0232                 IFOKDO(err, m_importer->getDocument()->stepForward(2))
0233             }
0234         }
0235     }
0236     SKGENDTRANSACTION(m_importer->getDocument(),  err)
0237 
0238     return err;
0239 }
0240 
0241 QString SKGImportPluginMT940::getMimeTypeFilter() const
0242 {
0243     return "*.mt940|" % i18nc("A file format", "MT940 file") % '\n' %
0244            "*.sta|" % i18nc("A file format", "MT940 file");
0245 }
0246 
0247 #include <skgimportpluginmt940.moc>