File indexing completed on 2024-05-26 05:10:34
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 QIF import / export. 0008 * 0009 * @author Stephane MANKOWSKI / Guillaume DE BURE 0010 */ 0011 #include "skgimportpluginqif.h" 0012 0013 #include <klocalizedstring.h> 0014 0015 #include <kpluginfactory.h> 0016 0017 #include <qcryptographichash.h> 0018 #include <qfile.h> 0019 #include <qsavefile.h> 0020 0021 #include "skgbankincludes.h" 0022 #include "skgimportexportmanager.h" 0023 #include "skgservices.h" 0024 #include "skgtraces.h" 0025 0026 /** 0027 * Opening balance string 0028 */ 0029 #define OPENINGBALANCE QStringLiteral("Opening Balance") 0030 0031 /** 0032 * This plugin factory. 0033 */ 0034 K_PLUGIN_CLASS_WITH_JSON(SKGImportPluginQif, "metadata.json") 0035 0036 SKGImportPluginQif::SKGImportPluginQif(QObject* iImporter, const QVariantList& iArg) 0037 : SKGImportPlugin(iImporter) 0038 { 0039 SKGTRACEINFUNC(10) 0040 Q_UNUSED(iArg) 0041 0042 m_importParameters[QStringLiteral("date_format")] = QString(); 0043 m_exportParameters[QStringLiteral("uuid_of_selected_accounts_or_operations")] = QString(); 0044 } 0045 0046 SKGImportPluginQif::~SKGImportPluginQif() 0047 = default; 0048 0049 bool SKGImportPluginQif::isImportPossible() 0050 { 0051 SKGTRACEINFUNC(10) 0052 return isExportPossible(); 0053 } 0054 0055 SKGError SKGImportPluginQif::importFile() 0056 { 0057 if (m_importer->getDocument() == nullptr) { 0058 return SKGError(ERR_ABORT, i18nc("Error message", "Invalid parameters")); 0059 } 0060 0061 SKGError err; 0062 SKGTRACEINFUNCRC(2, err) 0063 0064 // Begin transaction 0065 err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import %1 file", "QIF"), 3); 0066 IFOK(err) { 0067 // Create account if needed 0068 QDateTime now = QDateTime::currentDateTime(); 0069 QString postFix = SKGServices::dateToSqlString(now); 0070 0071 // Step 1 done 0072 IFOKDO(err, m_importer->getDocument()->stepForward(1)) 0073 0074 // Open file 0075 QFile file(m_importer->getLocalFileName()); 0076 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 0077 err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message", "Open file '%1' failed", m_importer->getFileName().toDisplayString())); 0078 } else { 0079 QTextStream stream(&file); 0080 if (!m_importer->getCodec().isEmpty()) { 0081 stream.setCodec(m_importer->getCodec().toLatin1().constData()); 0082 } 0083 0084 // load file in memory 0085 QStringList lines; 0086 QStringList dates; 0087 bool inWrongSection = false; 0088 bool inPriceSection = false; 0089 while (!stream.atEnd()) { 0090 // Read line 0091 // Check line if line is empty or is a commented 0092 QString line = stream.readLine().trimmed().toUtf8(); 0093 if (!line.isEmpty() && line[0] != '#') { 0094 lines.push_back(line); 0095 // Manage !Account section 0096 if (line.startsWith(QLatin1String("!"))) { 0097 inWrongSection = false; 0098 inPriceSection = false; 0099 } 0100 0101 if (QString::compare(line, QStringLiteral("!account"), Qt::CaseInsensitive) == 0 || 0102 QString::compare(line, QStringLiteral("!type:cat"), Qt::CaseInsensitive) == 0 || 0103 QString::compare(line, QStringLiteral("!type:tag"), Qt::CaseInsensitive) == 0 || 0104 QString::compare(line, QStringLiteral("!type:class"), Qt::CaseInsensitive) == 0) { 0105 inWrongSection = true; 0106 } else if (QString::compare(line, QStringLiteral("!type:prices"), Qt::CaseInsensitive) == 0) { 0107 inPriceSection = true; 0108 } 0109 0110 // We try to find automatically the date format 0111 if (!inWrongSection && line[0] == 'D') { 0112 dates.push_back(line.right(line.length() - 1)); 0113 } else if (inPriceSection) { 0114 QStringList vals = SKGServices::splitCSVLine(line, ','); 0115 if (vals.count() == 3) { 0116 dates.push_back(vals.at(2)); 0117 } 0118 } 0119 } 0120 } 0121 0122 // close file 0123 file.close(); 0124 0125 // Select dateformat 0126 QString dateFormat = m_importParameters.value(QStringLiteral("date_format")); 0127 if (dateFormat.isEmpty()) { 0128 dateFormat = SKGServices::getDateFormat(dates); // Automatic detection 0129 } 0130 if (dateFormat.isEmpty()) { 0131 err.setReturnCode(ERR_FAIL).setMessage(i18nc("Error message", "Date format not supported")); 0132 } 0133 IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message", "Import of '%1' with code '%2' and date format '%3'", m_importer->getFileName().toDisplayString(), m_importer->getCodec(), dateFormat))) 0134 0135 // Step 2 done 0136 IFOKDO(err, m_importer->getDocument()->stepForward(2)) 0137 0138 // Treat all lines 0139 IFOK(err) { 0140 SKGAccountObject* account = nullptr; 0141 SKGOperationObject currentOperation; 0142 SKGOperationObject payement; 0143 SKGPayeeObject currentPayee; 0144 SKGTrackerObject currentTracker; 0145 SKGUnitObject currentUnit; 0146 SKGSubOperationObject currentSubOperation; 0147 QDate currentOperationDate; 0148 QString lastTransferAccount; 0149 QList<QString> transferAccount; 0150 QList<double> transferQuantity; 0151 bool addNextAmountToTransferQuantity = false; 0152 QString stringForHash; 0153 QString currentUnitForInvestment; 0154 QChar inSection = 'B'; 0155 bool currentOperationInitialized = false; 0156 bool latestSubCatMustBeRemoved = false; 0157 bool investmentAccount = false; 0158 bool div = false; 0159 bool automaticAccount = true; 0160 int quantityFactor = 1; 0161 double currentUnitPrice = 1; 0162 double checkOperationAmount = 0; 0163 double checkSuboperationsAmount = 0; 0164 bool openingbalancecreated = false; 0165 0166 int nb = lines.size(); 0167 err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import transactions"), nb); 0168 for (int i = 0; !err && i < nb; ++i) { 0169 QString line = lines.at(i); 0170 QString val; 0171 QChar op = line[0]; 0172 if (line.length() > 1) { 0173 val = line.right(line.length() - 1).trimmed(); 0174 } 0175 0176 // Manage !Account section 0177 if (QString::compare(line, QStringLiteral("!type:bank"), Qt::CaseInsensitive) == 0 || 0178 QString::compare(line, QStringLiteral("!type:cash"), Qt::CaseInsensitive) == 0 || 0179 QString::compare(line, QStringLiteral("!type:ccard"), Qt::CaseInsensitive) == 0 || 0180 QString::compare(line, QStringLiteral("!type:oth a"), Qt::CaseInsensitive) == 0 || 0181 QString::compare(line, QStringLiteral("!type:oth l"), Qt::CaseInsensitive) == 0 || 0182 QString::compare(line, QStringLiteral("!type:invst"), Qt::CaseInsensitive) == 0) { 0183 inSection = 'B'; 0184 openingbalancecreated = false; 0185 investmentAccount = (QString::compare(val, QStringLiteral("type:invst"), Qt::CaseInsensitive) == 0); 0186 0187 // Set type of account 0188 if (account == nullptr) { 0189 SKGAccountObject defAccount; 0190 err = m_importer->getDefaultAccount(defAccount); 0191 IFOKDO(err, defAccount.addOperation(currentOperation, true)) 0192 IFOK(err) account = new SKGAccountObject(defAccount); 0193 } 0194 0195 if (!err && (account != nullptr)) { 0196 err = account->setType(QString::compare(line, QStringLiteral("!type:bank"), Qt::CaseInsensitive) == 0 ? SKGAccountObject::CURRENT : 0197 (QString::compare(line, QStringLiteral("!type:ccard"), Qt::CaseInsensitive) == 0 ? SKGAccountObject::CREDITCARD : 0198 (QString::compare(line, QStringLiteral("!type:invst"), Qt::CaseInsensitive) == 0 ? SKGAccountObject::INVESTMENT : 0199 (QString::compare(line, QStringLiteral("!type:oth a"), Qt::CaseInsensitive) == 0 ? SKGAccountObject::ASSETS : SKGAccountObject::OTHER)))); 0200 IFOKDO(err, account->save()) 0201 } 0202 } else if (QString::compare(line, QStringLiteral("!account"), Qt::CaseInsensitive) == 0) { 0203 inSection = 'A'; 0204 openingbalancecreated = false; 0205 automaticAccount = false; 0206 } else if (QString::compare(line, QStringLiteral("!type:cat"), Qt::CaseInsensitive) == 0) { 0207 inSection = 'C'; 0208 openingbalancecreated = false; 0209 IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message", "Categories found and imported"))) 0210 } else if (QString::compare(line, QStringLiteral("!type:prices"), Qt::CaseInsensitive) == 0) { 0211 inSection = 'U'; 0212 openingbalancecreated = false; 0213 IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message", "Units prices found and imported"))) 0214 } else if (QString::compare(line, QStringLiteral("!type:security"), Qt::CaseInsensitive) == 0) { 0215 inSection = 'S'; 0216 IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message", "Units found and imported"))) 0217 } else if (QString::compare(line, QStringLiteral("!type:tag"), Qt::CaseInsensitive) == 0) { 0218 inSection = 'T'; 0219 IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message", "Trackers found and imported"))) 0220 } else if (line.at(0) == '!') { 0221 inSection = '?'; 0222 openingbalancecreated = false; 0223 } else if (inSection == 'U') { 0224 // Unit value creation 0225 openingbalancecreated = false; 0226 QStringList vals = SKGServices::splitCSVLine(line, ','); 0227 if (vals.count() == 3 && !vals.at(0).isEmpty()) { 0228 err = m_importer->getDocument()->addOrModifyUnitValue(vals.at(0), SKGServices::stringToTime(SKGServices::dateToSqlString(vals.at(2), dateFormat)).date(), SKGServices::stringToDouble(vals.at(1))); 0229 } 0230 0231 } else if (inSection == 'T') { 0232 // Tracker creation 0233 if (op == 'N') { 0234 IFOKDO(err, SKGTrackerObject::createTracker(m_importer->getDocument(), val, currentTracker)) 0235 } else if (op == 'D') { 0236 IFOKDO(err, currentTracker.setComment(val)) 0237 IFOKDO(err, currentTracker.save()) 0238 } 0239 } else if (inSection == 'S') { 0240 // Unit creation 0241 if (op == 'N') { 0242 currentUnit = SKGUnitObject(m_importer->getDocument()); 0243 IFOKDO(err, currentUnit.setName(val)) 0244 IFOKDO(err, currentUnit.setSymbol(val)) 0245 IFOKDO(err, currentUnit.setType(SKGUnitObject::CURRENCY)) 0246 IFOKDO(err, currentUnit.setNumberDecimal(2)) 0247 IFOKDO(err, currentUnit.save()) 0248 } else if (op == 'S') { 0249 IFOKDO(err, currentUnit.setSymbol(val)) 0250 IFOKDO(err, currentUnit.save()) 0251 } else if (op == 'T') { 0252 if (QString::compare(val, QStringLiteral("stock"), Qt::CaseInsensitive) == 0) { 0253 IFOKDO(err, currentUnit.setType(SKGUnitObject::SHARE)) 0254 IFOKDO(err, currentUnit.setNumberDecimal(4)) 0255 IFOKDO(err, currentUnit.save()) 0256 } 0257 } 0258 } else if (inSection == 'C') { 0259 // Category creation 0260 openingbalancecreated = false; 0261 if (op == 'N') { 0262 SKGCategoryObject Category; 0263 val.replace('/', OBJECTSEPARATOR); 0264 val.replace(':', OBJECTSEPARATOR); 0265 err = SKGCategoryObject::createPathCategory(m_importer->getDocument(), val, Category); 0266 } 0267 } else if (inSection == 'A') { 0268 // Account creation 0269 openingbalancecreated = false; 0270 if (op == 'N') { 0271 // Check if the account already exist 0272 SKGAccountObject account2; 0273 err = SKGNamedObject::getObjectByName(m_importer->getDocument(), QStringLiteral("account"), val, account2); 0274 IFKO(err) { 0275 // Create account 0276 SKGBankObject bank(m_importer->getDocument()); 0277 err = bank.setName(i18nc("Noun", "Bank for import %1", postFix)); 0278 if (!err && bank.load().isFailed()) { 0279 err = bank.save(false); 0280 } 0281 IFOKDO(err, bank.addAccount(account2)) 0282 IFOKDO(err, account2.setName(val)) 0283 if (!err && account2.load().isFailed()) { 0284 err = account2.save(false); // Save only 0285 } 0286 } 0287 0288 IFOK(err) { 0289 delete account; 0290 account = new SKGAccountObject(account2); 0291 } 0292 } else if (op == 'D') { 0293 if (account != nullptr) { 0294 err = account->setNumber(val); 0295 } 0296 } else if (op == 'T') { 0297 if (account != nullptr) { 0298 err = account->setType(val == QStringLiteral("Bank") ? SKGAccountObject::CURRENT : (val == QStringLiteral("CCard") ? SKGAccountObject::CREDITCARD : (val == QStringLiteral("Invst") ? SKGAccountObject::INVESTMENT : (val == QStringLiteral("Oth A") ? SKGAccountObject::ASSETS : SKGAccountObject::OTHER)))); 0299 } 0300 } else if (op == '^') { 0301 // ^ End of entry 0302 // save 0303 if (account != nullptr) { 0304 err = account->save(); 0305 } 0306 } 0307 } else if (inSection == 'B') { 0308 // Transaction creation 0309 /* 0310 >>>> Items for Non-Investment Accounts <<<< 0311 DONE D Date 0312 DONE T Amount 0313 U Transaction amount (higher possible value than T) 0314 DONE C Cleared status 0315 DONE N Number (check or reference number) 0316 DONE P Payee/description 0317 DONE M Memo 0318 DONE A Address (up to 5 lines; 6th line is an optional message) 0319 DONE L Category (category/class or transfer/class) 0320 DONE S Category in split (category/class or transfer/class) 0321 DONE E Memo in split 0322 DONE $ Dollar amount of split 0323 % Percentage of split if percentages are used 0324 F Reimbursable business expense flag 0325 X Small Business extensions 0326 DONE ^ End of entry 0327 0328 >>>> Items for Investment Accounts <<<< 0329 DONE D Date 0330 N Action 0331 DONE Y Security 0332 DONE I Price 0333 DONE Q Quantity (number of shares or split ratio) 0334 DONE T Transaction amount 0335 DONE C Cleared status 0336 P Text in the first line for transfers and reminders 0337 DONE M Memo 0338 O Commission 0339 L Account for the transfer 0340 $ Amount transferred 0341 ^ End of entry 0342 */ 0343 stringForHash += line; 0344 if (op == 'D') { 0345 // D Date 0346 /* 0347 Dates in US QIF files are usually in the format MM/DD/YY, although 0348 four-digit years are not uncommon. Dates sometimes occur without the 0349 slash separator, or using other separators in place of the slash, 0350 commonly '-' and '.'. US Quicken seems to be using the ' to indicate 0351 post-2000 two-digit years (such as 01/01'00 for Jan 1 2000). Some 0352 banks appear to be using a completely undifferentiated numeric QString 0353 formateed YYYYMMDD in downloaded QIF files. 0354 */ 0355 // Transaction creation 0356 SKGUnitObject unit; 0357 IFOK(err) { 0358 if (account != nullptr) { 0359 err = account->addOperation(currentOperation, true); 0360 if (!openingbalancecreated) { 0361 double initBalance; 0362 account->getInitialBalance(initBalance, unit); 0363 } 0364 } else { 0365 SKGAccountObject defAccount; 0366 err = m_importer->getDefaultAccount(defAccount); 0367 IFOKDO(err, defAccount.addOperation(currentOperation, true)) 0368 if (!openingbalancecreated) { 0369 double initBalance; 0370 defAccount.getInitialBalance(initBalance, unit); 0371 } 0372 } 0373 currentOperationInitialized = true; 0374 } 0375 0376 // Set date 0377 currentOperationDate = SKGServices::stringToTime(SKGServices::dateToSqlString(val, dateFormat)).date(); 0378 IFOKDO(err, currentOperation.setDate(currentOperationDate)) 0379 0380 // Set unit 0381 IFOK(err) { 0382 // Create unit if needed 0383 // If an initial balance is existing for the account then we use the unit else we look for the most appropriate unit 0384 if (!unit.exist()) { 0385 err = m_importer->getDefaultUnit(unit, ¤tOperationDate); 0386 } 0387 IFOKDO(err, currentOperation.setUnit(unit)) 0388 } 0389 0390 IFOKDO(err, currentOperation.save()) 0391 0392 // Create suboperation 0393 IFOKDO(err, currentOperation.addSubOperation(currentSubOperation)) 0394 } else if (op == 'Y') { 0395 // Y Security 0396 if (!div) { 0397 currentUnitForInvestment = val; 0398 0399 SKGUnitObject unit(m_importer->getDocument()); 0400 if (currentUnitForInvestment.isEmpty()) { 0401 IFOKDO(err, err = m_importer->getDefaultUnit(unit)) 0402 } else { 0403 IFOKDO(err, unit.setName(currentUnitForInvestment)) 0404 IFOKDO(err, unit.setSymbol(currentUnitForInvestment)) 0405 if (unit.load().isFailed()) { 0406 IFOKDO(err, unit.setType(investmentAccount ? SKGUnitObject::SHARE : SKGUnitObject::CURRENCY)) 0407 IFOKDO(err, unit.save(false)) 0408 } 0409 } 0410 IFOKDO(err, currentOperation.setUnit(unit)) 0411 } else { 0412 // For dividend, if comment is empty, we set the security in comment 0413 if (currentOperation.getComment().isEmpty()) { 0414 err = currentOperation.setComment(val); 0415 } 0416 } 0417 } else if (op == 'O') { 0418 // O Commission 0419 // Get previous quantity 0420 double quantity = SKGServices::stringToDouble(val); 0421 SKGObjectBase::SKGListSKGObjectBase subops; 0422 payement.getSubOperations(subops); 0423 if (!subops.isEmpty()) { 0424 SKGSubOperationObject subpayement(subops.at(0)); 0425 err = subpayement.setQuantity(subpayement.getQuantity() + quantity); 0426 IFOKDO(err, subpayement.save()) 0427 } 0428 0429 SKGSubOperationObject subcommission; 0430 if (!payement.exist()) { 0431 // We have to create a new transaction 0432 if (account != nullptr) { 0433 err = account->addOperation(payement, true); 0434 } else { 0435 SKGAccountObject defAccount; 0436 err = m_importer->getDefaultAccount(defAccount); 0437 IFOKDO(err, defAccount.addOperation(payement, true)) 0438 } 0439 IFOKDO(err, payement.setDate(currentOperationDate)) 0440 IFOK(err) { 0441 // If an initial balance is existing for the account then we use the unit else we look for the most appropriate unit 0442 SKGUnitObject unit; 0443 if ((account != nullptr) && !openingbalancecreated) { 0444 double initBalance; 0445 account->getInitialBalance(initBalance, unit); 0446 } 0447 if (!unit.exist()) { 0448 err = m_importer->getDefaultUnit(unit, ¤tOperationDate); 0449 } 0450 IFOKDO(err, payement.setUnit(unit)) 0451 } 0452 IFOKDO(err, payement.save()) 0453 } 0454 IFOKDO(err, payement.addSubOperation(subcommission)) 0455 IFOKDO(err, subcommission.setQuantity(-quantity)) 0456 IFOKDO(err, subcommission.save(false, false)) 0457 } else if (op == 'I') { 0458 // I Price 0459 currentUnitPrice = SKGServices::stringToDouble(val); 0460 if ((currentUnitPrice != 0.0) && !currentUnitForInvestment.isEmpty()) { 0461 err = m_importer->getDocument()->addOrModifyUnitValue(currentUnitForInvestment, currentOperationDate, currentUnitPrice); 0462 } 0463 } else if (op == 'N') { 0464 if (investmentAccount) { 0465 // N Action 0466 /* 0467 QIF N Line Notes 0468 ============ ===== 0469 Aktab Same as ShrsOut. 0470 AktSplit Same as StkSplit. 0471 Aktzu Same as ShrsIn. 0472 Buy Buy shares. 0473 BuyX Buy shares. Used with an L line. 0474 Cash Miscellaneous cash transaction. Used with an L line. 0475 CGMid Mid-term capital gains. 0476 CGMidX Mid-term capital gains. For use with an L line. 0477 CGLong Long-term capital gains. 0478 CGLongX Long-term capital gains. For use with an L line. 0479 CGShort Short-term capital gains. 0480 CGShortX Short-term capital gains. For use with an L line. 0481 ContribX Same as XIn. Used for tax-advantaged accounts. 0482 CvrShrt Buy shares to cover a short sale. 0483 CvrShrtX Buy shares to cover a short sale. Used with an L line. 0484 Div Dividend received. 0485 DivX Dividend received. For use with an L line. 0486 Errinerg Same as Reminder. 0487 Exercise Exercise an option. 0488 ExercisX Exercise an option. For use with an L line. 0489 Expire Mark an option as expired. (Uses D, N, Y & M lines) 0490 Grant Receive a grant of stock options. 0491 Int Same as IntInc. 0492 IntX Same as IntIncX. 0493 IntInc Interest received. 0494 IntIncX Interest received. For use with an L line. 0495 K.gewsp Same as CGShort. (German) 0496 K.gewspX Same as CGShortX. (German)2307068 0497 Kapgew Same as CGLong. Kapitalgewinnsteuer.(German) 0498 KapgewX Same as CGLongX. Kapitalgewinnsteuer. (German) 0499 Kauf Same as Buy. (German) 0500 KaufX Same as BuyX. (German) 0501 MargInt Margin interest paid. 0502 MargIntX Margin interest paid. For use with an L line. 0503 MiscExp Miscellaneous expense. 0504 MiscExpX Miscellaneous expense. For use with an L line. 0505 MiscInc Miscellaneous income. 0506 MiscIncX Miscellaneous income. For use with an L line. 0507 ReinvDiv Reinvested dividend. 0508 ReinvInt Reinvested interest. 0509 ReinvLG Reinvested long-term capital gains. 0510 Reinvkur Same as ReinvLG. 0511 Reinvksp Same as ReinvSh. 0512 ReinvMd Reinvested mid-term capital gains. 0513 ReinvSG Same as ReinvSh. 0514 ReinvSh Reinvested short-term capital gains. 0515 Reinvzin Same as ReinvDiv. 0516 Reminder Reminder. (Uses D, N, C & M lines) 0517 RtrnCap Return of capital. 0518 RtrnCapX Return of capital. For use with an L line. 0519 Sell Sell shares. 0520 SellX Sell shares. For use with an L line. 0521 ShtSell Short sale. 0522 ShrsIn Deposit shares. 0523 ShrsOut Withdraw shares. 0524 StkSplit Share split. 0525 Verkauf Same as Sell. (German) 0526 VerkaufX Same as SellX. (German) 0527 Vest Mark options as vested. (Uses N, Y, Q, C & M lines) 0528 WithDrwX Same as XOut. Used for tax-advantaged accounts. 0529 XIn Transfer cash from another account. 0530 XOut Transfer cash to another account. 0531 */ 0532 val = val.toLower(); 0533 if (val.contains(QStringLiteral("div")) && val != QStringLiteral("reinvdiv")) { 0534 // TODO(Stephane MANKOWSKI) err=currentOperation.setProperty ( "SKG_OP_ORIGINAL_AMOUNT", "" ); 0535 div = true; 0536 } else if (val.contains(QStringLiteral("sell")) || 0537 val.contains(QStringLiteral("verkauf")) || 0538 val.contains(QStringLiteral("miscexp")) || 0539 val.contains(QStringLiteral("shrsout")) 0540 ) { 0541 quantityFactor = -1; 0542 } 0543 // Correction 214851 vvvv 0544 // err=currentOperation.setComment ( val ); 0545 // if ( !err ) err=currentOperation.setMode ( i18nc ( "Noun, the title of an item","Title" ) ); 0546 // Correction 214851 ^^^^ 0547 } else { 0548 // N Num (check or reference number) 0549 // Set number 0550 bool ok; 0551 int number = val.toInt(&ok); 0552 if (ok && number != 0) { 0553 err = currentOperation.setNumber(val); 0554 } else { 0555 err = currentOperation.setMode(val); 0556 } 0557 } 0558 } else if (op == 'Q') { 0559 // Q Quantity (number of shares or split ratio) 0560 // Set value 0561 if (!val.isEmpty()) { 0562 double previousQuantity = currentSubOperation.getQuantity(); 0563 if (previousQuantity != 0.0) { 0564 // We have to create a new transaction 0565 if (account != nullptr) { 0566 err = account->addOperation(payement, true); 0567 } else { 0568 SKGAccountObject defAccount; 0569 err = m_importer->getDefaultAccount(defAccount); 0570 IFOKDO(err, defAccount.addOperation(payement, true)) 0571 } 0572 IFOKDO(err, payement.setDate(currentOperationDate)) 0573 IFOK(err) { 0574 // Create unit if needed 0575 // If an initial balance is existing for the account then we use the unit else we look for the most appropriate unit 0576 SKGUnitObject unit; 0577 if ((account != nullptr) && !openingbalancecreated) { 0578 double initBalance; 0579 account->getInitialBalance(initBalance, unit); 0580 } 0581 if (!unit.exist()) { 0582 err = m_importer->getDefaultUnit(unit, ¤tOperationDate); 0583 } 0584 IFOKDO(err, payement.setUnit(unit)) 0585 } 0586 IFOKDO(err, payement.save()) 0587 IFOKDO(err, currentOperation.setGroupOperation(payement)) 0588 0589 SKGSubOperationObject subpayement; 0590 IFOKDO(err, payement.addSubOperation(subpayement)) 0591 IFOKDO(err, subpayement.setQuantity(-previousQuantity)) 0592 IFOKDO(err, subpayement.save()) 0593 } 0594 0595 IFOKDO(err, currentSubOperation.setQuantity(quantityFactor * SKGServices::stringToDouble(val))) 0596 } 0597 } else if (op == 'T') { 0598 // T Amount 0599 // Set value 0600 checkOperationAmount = SKGServices::stringToDouble(val); 0601 err = currentSubOperation.setQuantity(checkOperationAmount / currentUnitPrice); 0602 if (!err && investmentAccount) { 0603 err = currentOperation.setProperty(QStringLiteral("SKG_OP_ORIGINAL_AMOUNT"), val); 0604 } 0605 } else if (op == '$') { 0606 // Dollar amount of split 0607 // Set value 0608 if (!investmentAccount) { 0609 double vald = SKGServices::stringToDouble(val); 0610 checkSuboperationsAmount += vald; 0611 if (addNextAmountToTransferQuantity && !lastTransferAccount.isEmpty()) { 0612 transferQuantity[transferAccount.count() - 1] += vald; 0613 } 0614 addNextAmountToTransferQuantity = false; 0615 lastTransferAccount = QString(); 0616 err = currentSubOperation.setQuantity(vald); 0617 0618 // save 0619 IFOKDO(err, currentSubOperation.save()) 0620 0621 // Create suboperation 0622 IFOKDO(err, currentOperation.addSubOperation(currentSubOperation)) 0623 0624 latestSubCatMustBeRemoved = true; 0625 } 0626 } else if (op == 'P') { 0627 // P Payee 0628 // Set Payee 0629 // Clean QIF coming from bankperfect 0630 val.remove(QStringLiteral("[auto]")); 0631 0632 err = SKGPayeeObject::createPayee(m_importer->getDocument(), val, currentPayee); 0633 IFOKDO(err, currentOperation.setPayee(currentPayee)) 0634 } else if (op == 'A') { 0635 // A Address (up to 5 lines; 6th line is an optional message) 0636 QString add = currentPayee.getAddress(); 0637 if (!add.isEmpty()) { 0638 add += ' '; 0639 } 0640 add += val; 0641 err = currentPayee.setAddress(add); 0642 IFOKDO(err, currentPayee.save()) 0643 } else if (op == 'M') { 0644 // M Memo 0645 // Set Memo 0646 err = currentOperation.setComment(val); 0647 } else if (op == 'E') { 0648 // E Memo in split 0649 // Set Memo 0650 err = currentSubOperation.setComment(val); 0651 } else if (op == 'S' || op == 'L') { 0652 // S Category in split (Category/Transfer/Class) 0653 // L Category (Category/Subcategory/Transfer/Class) 0654 // LCategory of transaction 0655 // L[Transfer account] 0656 // LCategory of transaction/Class of transaction 0657 // L[Transfer account]/Class of transaction// Set Category 0658 if (!val.isEmpty()) { 0659 if (val[0] == '[') { 0660 addNextAmountToTransferQuantity = true; 0661 0662 int pos = val.indexOf(']'); 0663 if (pos != -1) { 0664 SKGPayeeObject payeeObj; 0665 currentOperation.getPayee(payeeObj); 0666 bool opening = (payeeObj.getName().compare(OPENINGBALANCE, Qt::CaseInsensitive) == 0); 0667 0668 // If the very first Bank transaction in the file has a payee of "Opening Balance", the L line contains the name of the account that the file describes. This is not a transfer 0669 if (op == 'L' && automaticAccount && (account != nullptr) && opening) { 0670 QString accountName = val.mid(1, pos - 1); 0671 0672 SKGAccountObject newAccount(m_importer->getDocument()); 0673 err = newAccount.setName(accountName); 0674 IFOK(err) { 0675 if (newAccount.exist()) { 0676 // Oups, the real account is existing and it is another one 0677 err = newAccount.load(); 0678 0679 // We move the transaction in the right account 0680 IFOKDO(err, currentOperation.setParentAccount(newAccount)) 0681 IFOKDO(err, currentOperation.save()) 0682 0683 // We delete the previous account if empty 0684 IFOK(err) { 0685 if (account->getNbOperation() == 0) { 0686 err = account->remove(); 0687 } 0688 delete account; 0689 account = new SKGAccountObject(newAccount); 0690 } 0691 } else { 0692 err = account->setName(accountName); 0693 IFOKDO(err, account->save()) 0694 } 0695 } 0696 } 0697 // if ( op=='L' && currentOperation.getPayee().compare ( "Opening Balance", Qt::CaseInsensitive ) !=0 && !investmentAccount) 0698 if (!opening) { 0699 lastTransferAccount = val.mid(1, pos - 1); 0700 if ((account != nullptr) && lastTransferAccount == account->getName()) { 0701 lastTransferAccount = QString(); 0702 } 0703 0704 if (!lastTransferAccount.isEmpty() && 0705 (transferAccount.count() == 0 || 0706 transferAccount.at(transferAccount.count() - 1) != lastTransferAccount || 0707 transferQuantity.at(transferQuantity.count() - 1) != 0.0 0708 ) 0709 ) { 0710 transferAccount.append(lastTransferAccount); 0711 transferQuantity.append(0.0); 0712 } 0713 } 0714 val = val.mid(pos + 2); 0715 } 0716 } 0717 if (!err && !val.isEmpty()) { 0718 auto cat_tag = SKGServices::splitCSVLine(val, '/', false); 0719 val = cat_tag.at(0); 0720 SKGCategoryObject Category; 0721 val.replace('/', OBJECTSEPARATOR); 0722 val.replace(':', OBJECTSEPARATOR); 0723 val.replace(',', OBJECTSEPARATOR); 0724 val.replace(';', OBJECTSEPARATOR); 0725 err = SKGCategoryObject::createPathCategory(m_importer->getDocument(), val, Category); 0726 IFOKDO(err, currentSubOperation.setCategory(Category)) 0727 0728 if (!err && cat_tag.count() > 1) { 0729 SKGTrackerObject tracker; 0730 err = SKGTrackerObject::createTracker(m_importer->getDocument(), cat_tag.at(1), tracker); 0731 IFOKDO(err, currentSubOperation.setTracker(tracker)) 0732 } 0733 } 0734 } 0735 } else if (op == 'C') { 0736 // C Cleared status 0737 // Set status 0738 err = currentOperation.setStatus((val == QStringLiteral("C") || val == QStringLiteral("*") ? SKGOperationObject::MARKED : (val == QStringLiteral("R") || val == QStringLiteral("X") ? SKGOperationObject::CHECKED : SKGOperationObject::NONE))); 0739 } else if (op == '^') { 0740 // ^ End of entry 0741 // save 0742 0743 if (currentOperationInitialized) { 0744 QByteArray hash = QCryptographicHash::hash(stringForHash.toUtf8(), QCryptographicHash::Md5); 0745 SKGPayeeObject payeeObj; 0746 currentOperation.getPayee(payeeObj); 0747 bool opening = (payeeObj.getName().compare(OPENINGBALANCE, Qt::CaseInsensitive) == 0); 0748 if (!err && opening) { 0749 // Specific values for initial balance 0750 err = currentOperation.setStatus(SKGOperationObject::CHECKED); 0751 IFOKDO(err, currentOperation.setAttribute(QStringLiteral("d_date"), QStringLiteral("0000-00-00"))) 0752 IFOKDO(err, currentSubOperation.setAttribute(QStringLiteral("d_date"), QStringLiteral("0000-00-00"))) 0753 openingbalancecreated = true; 0754 } 0755 0756 IFOKDO(err, currentOperation.setImportID(hash.toHex())) 0757 IFOKDO(err, currentOperation.save()) 0758 if (!latestSubCatMustBeRemoved && !err) { 0759 err = currentSubOperation.save(); 0760 } 0761 0762 // Create transfers if needed 0763 // Get origin op 0764 SKGOperationObject opOrigin(m_importer->getDocument(), currentOperation.getID()); 0765 SKGAccountObject accountOrigin; 0766 IFOKDO(err, opOrigin.getParentAccount(accountOrigin)) 0767 int nbTransfers = transferAccount.count(); 0768 for (int j = 0; !err && j < nbTransfers; ++j) { 0769 bool merged = false; 0770 double tq = transferQuantity.at(j); 0771 const QString& ta = transferAccount.at(j); 0772 // Is the transfert transaction already existing? 0773 double qua = tq == 0.0 && addNextAmountToTransferQuantity ? SKGServices::stringToDouble(opOrigin.getAttribute(QStringLiteral("f_QUANTITY"))) : tq; 0774 QString wc = "t_ACCOUNT='" % SKGServices::stringToSqlString(ta) % 0775 "' AND t_TOACCOUNT='" % SKGServices::stringToSqlString(accountOrigin.getName()) % 0776 "' AND ABS(f_QUANTITY-(" % SKGServices::doubleToString(-qua) % "))<0.0001" 0777 " AND ABS(julianday(d_date) - julianday('" % SKGServices::dateToSqlString(opOrigin.getDate()) % "'))<1" 0778 " ORDER BY ABS(julianday(d_date) - julianday('" % SKGServices::dateToSqlString(opOrigin.getDate()) % "')) ASC"; 0779 SKGObjectBase::SKGListSKGObjectBase obs; 0780 m_importer->getDocument()->getObjects(QStringLiteral("v_operation_display"), wc, obs); 0781 if (!obs.isEmpty()) { 0782 // We have to merge them and we do not need to create the transfer 0783 SKGOperationObject firstOne(obs.at(0)); 0784 0785 // Remove all transaction attached to this transfer 0786 SKGObjectBase::SKGListSKGObjectBase list; 0787 IFOKDO(err, firstOne.getGroupedOperations(list)) 0788 for (const auto& o : qAsConst(list)) { 0789 SKGOperationObject op2(o); 0790 if (op2 != firstOne && op2 != currentOperation) { 0791 IFOKDO(err, op2.setStatus(SKGOperationObject::NONE)) 0792 IFOKDO(err, op2.remove(false, true)) 0793 } 0794 } 0795 0796 // Attach myself 0797 IFOKDO(err, currentOperation.setGroupOperation(firstOne)) 0798 IFOKDO(err, currentOperation.save()) 0799 0800 merged = true; 0801 } else { 0802 // Is the transaction already created as a transfer of an other one? 0803 QString wc = "t_import_id='QIF TRANSFER-" % SKGServices::stringToSqlString(ta) % "' AND t_ACCOUNT='" % SKGServices::stringToSqlString(accountOrigin.getName()) % 0804 "' AND (ABS(f_CURRENTAMOUNT-(" % SKGServices::doubleToString(opOrigin.getCurrentAmount()) % "))<0.0001 OR f_QUANTITY=" % SKGServices::doubleToString(qua) % ")" 0805 " AND ABS(julianday(d_date) - julianday('" % SKGServices::dateToSqlString(opOrigin.getDate()) % "'))<1" 0806 " ORDER BY ABS(julianday(d_date) - julianday('" % SKGServices::dateToSqlString(opOrigin.getDate()) % "')) ASC"; 0807 m_importer->getDocument()->getObjects(QStringLiteral("v_operation_display"), wc, obs); 0808 if (!obs.isEmpty()) { 0809 // We have to merge them and we do not need to create the transfer 0810 SKGOperationObject firstOne(obs.at(0)); 0811 err = opOrigin.setStatus(SKGOperationObject::NONE); // To be sure we can delete it 0812 IFOKDO(err, opOrigin.save()) 0813 IFOKDO(err, firstOne.mergeAttribute(opOrigin)) 0814 0815 SKGObjectBase::SKGListSKGObjectBase list; 0816 IFOKDO(err, currentOperation.getGroupedOperations(list)) 0817 for (const auto& o : qAsConst(list)) { 0818 SKGOperationObject op2(o); 0819 IFOKDO(err, op2.setStatus(SKGOperationObject::NONE)) 0820 IFOKDO(err, op2.remove(false, true)) 0821 } 0822 merged = true; 0823 } 0824 } 0825 0826 if (!merged) { 0827 // Create target account if needed 0828 SKGAccountObject accountTransfer(m_importer->getDocument()); 0829 if (m_accountCache.contains(ta)) { 0830 accountTransfer = m_accountCache[ta]; 0831 } else { 0832 accountTransfer.setName(ta); 0833 if (!accountTransfer.exist()) { 0834 // The account is created in the same bank by default 0835 SKGBankObject bankOrigin; 0836 IFOKDO(err, accountOrigin.getBank(bankOrigin)) 0837 IFOKDO(err, accountTransfer.setBank(bankOrigin)) 0838 IFOKDO(err, accountTransfer.save(false, true)) 0839 } else { 0840 err = accountTransfer.load(); 0841 } 0842 0843 m_accountCache[ta] = accountTransfer; 0844 } 0845 0846 // Create operation 0847 SKGUnitObject unit; 0848 opOrigin.getUnit(unit); 0849 0850 SKGOperationObject opTransfer; 0851 IFOKDO(err, accountTransfer.addOperation(opTransfer, true)) 0852 IFOKDO(err, opTransfer.setDate(opOrigin.getDate())) 0853 IFOKDO(err, opTransfer.setComment(opOrigin.getComment())) 0854 SKGPayeeObject payeeObj2; 0855 opTransfer.getPayee(payeeObj2); 0856 IFOKDO(err, opTransfer.setPayee(payeeObj2)) 0857 IFOKDO(err, opTransfer.setStatus(opOrigin.getStatus())) 0858 IFOKDO(err, opTransfer.setUnit(unit)) 0859 IFOKDO(err, opTransfer.setImportID("QIF TRANSFER-" % accountOrigin.getName())) 0860 IFOKDO(err, opTransfer.save()) // save needed before setGroupOperation 0861 IFOKDO(err, opTransfer.setGroupOperation(opOrigin)) 0862 IFOKDO(err, opOrigin.load()) // Must be reload because of setGroupOperation modified it 0863 IFOKDO(err, opTransfer.save()) 0864 0865 SKGSubOperationObject subopTransfer; 0866 IFOKDO(err, opTransfer.addSubOperation(subopTransfer)) 0867 IFOKDO(err, subopTransfer.setQuantity(-qua)) 0868 IFOKDO(err, subopTransfer.save()) 0869 } 0870 } 0871 } 0872 0873 // Check Sum($)=T for incident 214462 0874 QString checkOperationAmountString = SKGServices::doubleToString(checkOperationAmount); 0875 QString checkSuboperationsAmountString = SKGServices::doubleToString(checkSuboperationsAmount); 0876 if (!err && checkOperationAmount != 0 && checkSuboperationsAmount != 0 && checkOperationAmountString != checkSuboperationsAmountString) { 0877 SKGSubOperationObject suboprepair; 0878 IFOKDO(err, currentOperation.addSubOperation(suboprepair)) 0879 IFOKDO(err, suboprepair.setQuantity(checkOperationAmount - checkSuboperationsAmount)) 0880 IFOKDO(err, suboprepair.setComment(i18nc("An information message", "Auto repaired transaction"))) 0881 IFOKDO(err, suboprepair.save()) 0882 0883 IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message", "The total amount of the transaction (%1) was different to the sum of the sub-transactions (%2). The transaction has been repaired.", checkOperationAmountString, checkSuboperationsAmountString), SKGDocument::Warning)) 0884 } 0885 0886 // Initialize variables 0887 currentOperationInitialized = false; 0888 latestSubCatMustBeRemoved = false; 0889 currentUnitForInvestment = QString(); 0890 quantityFactor = 1; 0891 currentUnitPrice = 1; 0892 stringForHash = QString(); 0893 checkOperationAmount = 0; 0894 checkSuboperationsAmount = 0; 0895 lastTransferAccount = QString(); 0896 transferAccount.clear(); 0897 transferQuantity.clear(); 0898 payement = SKGOperationObject(); 0899 } else { 0900 // A Address (up to five lines; the sixth line is an optional message) 0901 } 0902 } 0903 0904 if (!err && i % 500 == 0) { 0905 err = m_importer->getDocument()->executeSqliteOrder(QStringLiteral("ANALYZE")); 0906 } 0907 IFOKDO(err, m_importer->getDocument()->stepForward(i + 1)) 0908 } 0909 0910 delete account; 0911 account = nullptr; 0912 SKGENDTRANSACTION(m_importer->getDocument(), err) 0913 0914 // Lines treated 0915 IFOKDO(err, m_importer->getDocument()->stepForward(3)) 0916 } 0917 } 0918 } 0919 SKGENDTRANSACTION(m_importer->getDocument(), err) 0920 0921 return err; 0922 } 0923 0924 bool SKGImportPluginQif::isExportPossible() 0925 { 0926 SKGTRACEINFUNC(10) 0927 return (m_importer->getDocument() == nullptr ? true : m_importer->getFileNameExtension() == QStringLiteral("QIF")); 0928 } 0929 0930 SKGError SKGImportPluginQif::exportFile() 0931 { 0932 if (m_importer->getDocument() == nullptr) { 0933 return SKGError(ERR_ABORT, i18nc("Error message", "Invalid parameters")); 0934 } 0935 SKGError err; 0936 SKGTRACEINFUNCRC(2, err) 0937 0938 // Read parameters 0939 auto listUUIDs = SKGServices::splitCSVLine(m_exportParameters.value(QStringLiteral("uuid_of_selected_accounts_or_operations"))); 0940 0941 QStringList listOperationsToExport; 0942 listOperationsToExport.reserve(listUUIDs.count()); 0943 QStringList listAccountsToExport; 0944 listAccountsToExport.reserve(listUUIDs.count()); 0945 for (const auto& uuid : qAsConst(listUUIDs)) { 0946 if (uuid.endsWith(QLatin1String("-operation"))) { 0947 listOperationsToExport.push_back(uuid); 0948 } else if (uuid.endsWith(QLatin1String("-account"))) { 0949 listAccountsToExport.push_back(uuid); 0950 } 0951 } 0952 0953 if ((listAccountsToExport.count() != 0) || (listOperationsToExport.count() != 0)) { 0954 IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message", "Only selected accounts and transactions have been exported"))) 0955 } 0956 0957 // Open file 0958 QSaveFile file(m_importer->getLocalFileName(false)); 0959 if (!file.open(QIODevice::WriteOnly)) { 0960 err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message", "Save file '%1' failed", m_importer->getFileName().toDisplayString())); 0961 } else { 0962 QTextStream stream(&file); 0963 if (!m_importer->getCodec().isEmpty()) { 0964 stream.setCodec(m_importer->getCodec().toLatin1().constData()); 0965 } 0966 0967 err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export %1 file", "QIF"), 3); 0968 IFOK(err) { 0969 // Export categories 0970 SKGObjectBase::SKGListSKGObjectBase categories; 0971 IFOKDO(err, m_importer->getDocument()->getObjects(QStringLiteral("v_category_display_tmp"), QStringLiteral("1=1 ORDER BY t_fullname, id"), categories)) 0972 int nbcat = categories.count(); 0973 if (!err && (nbcat != 0)) { 0974 stream << "!Type:Cat\n"; 0975 err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export categories"), nbcat); 0976 for (int i = 0; !err && i < nbcat; ++i) { 0977 SKGCategoryObject cat(categories.at(i)); 0978 QString catName = cat.getFullName(); 0979 if (!catName.isEmpty()) { 0980 stream << QStringLiteral("N") << catName.replace(OBJECTSEPARATOR, QStringLiteral(":")) << SKGENDL; 0981 if (SKGServices::stringToDouble(cat.getAttribute(QStringLiteral("f_REALCURRENTAMOUNT"))) < 0) { 0982 stream << "E" << SKGENDL; 0983 } else { 0984 stream << "I" << SKGENDL; 0985 } 0986 stream << "^" << SKGENDL; 0987 } 0988 IFOKDO(err, m_importer->getDocument()->stepForward(i + 1)) 0989 } 0990 0991 SKGENDTRANSACTION(m_importer->getDocument(), err) 0992 } 0993 IFOKDO(err, m_importer->getDocument()->stepForward(1)) 0994 0995 SKGServices::SKGUnitInfo primaryUnit = m_importer->getDocument()->getPrimaryUnit(); 0996 0997 // Get transactions 0998 QString currentAccountName; 0999 SKGObjectBase::SKGListSKGObjectBase transactions; 1000 IFOKDO(err, m_importer->getDocument()->getObjects(QStringLiteral("v_operation_display_all"), QStringLiteral("t_template='N' ORDER BY t_ACCOUNT, d_date, id"), transactions)) 1001 int nb = transactions.count(); 1002 IFOK(err) { 1003 err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export transactions"), nb); 1004 for (int i = 0; !err && i < nb; ++i) { 1005 SKGOperationObject operation(transactions.at(i)); 1006 SKGAccountObject a; 1007 operation.getParentAccount(a); 1008 if ((listOperationsToExport.isEmpty() || listOperationsToExport.contains(operation.getUniqueID())) && 1009 (listAccountsToExport.isEmpty() || listAccountsToExport.contains(a.getUniqueID()))) { 1010 // Get account name 1011 QString accountName = operation.getAttribute(QStringLiteral("t_ACCOUNT")); 1012 1013 // In the same account ? 1014 if (accountName != currentAccountName) { 1015 SKGAccountObject account(m_importer->getDocument()); 1016 account.setName(accountName); 1017 account.load(); 1018 1019 SKGBankObject bank; 1020 account.getBank(bank); 1021 1022 // Write header 1023 stream << "!Account\n"; 1024 stream << 'N' << accountName << SKGENDL; 1025 QString type = (account.getType() == SKGAccountObject::CURRENT ? QStringLiteral("Bank") : (account.getType() == SKGAccountObject::CREDITCARD ? QStringLiteral("CCard") : (account.getType() == SKGAccountObject::INVESTMENT ? QStringLiteral("Invst") : (account.getType() == SKGAccountObject::ASSETS ? QStringLiteral("Oth A") : QStringLiteral("Cash"))))); 1026 stream << 'T' << type << SKGENDL; 1027 QString number = bank.getNumber(); 1028 QString bnumber = account.getAgencyNumber(); 1029 QString cnumber = account.getNumber(); 1030 if (!bnumber.isEmpty()) { 1031 if (!number.isEmpty()) { 1032 number += '-'; 1033 } 1034 number += bnumber; 1035 } 1036 if (!cnumber.isEmpty()) { 1037 if (!number.isEmpty()) { 1038 number += '-'; 1039 } 1040 number += cnumber; 1041 } 1042 stream << 'D' << number << SKGENDL; 1043 // stream << "/" Statement balance date 1044 // stream << "$" Statement balance amount 1045 stream << '^' << SKGENDL; 1046 currentAccountName = accountName; 1047 1048 stream << "!Type:" << type << "\n"; 1049 } 1050 1051 1052 // Write operation 1053 /* 1054 DONE D Date 1055 DONE T Amount 1056 N/A U Transaction amount (higher possible value than T) 1057 DONE C Cleared status 1058 DONE N Number (check or reference number) 1059 DONE P Payee/description 1060 DONE M Memo 1061 N/A A Address (up to 5 lines; 6th line is an optional message) 1062 DONE L Category (category/class or transfer/class) 1063 DONE S Category in split (category/class or transfer/class) 1064 DONE E Memo in split 1065 DONE $ Dollar amount of split 1066 N/A % Percentage of split if percentages are used 1067 N/A F Reimbursable business expense flag 1068 N/A X Small Business extensions 1069 DONE Y Security 1070 DONE I Price 1071 DONE Q Quantity (number of shares or split ratio) 1072 N/A O Commission 1073 DONE ^ End of entry 1074 */ 1075 SKGUnitObject unit; 1076 operation.getUnit(unit); 1077 bool investment = false; 1078 bool unitExported = false; 1079 if (unit.getSymbol() != primaryUnit.Symbol && !primaryUnit.Symbol.isEmpty()) { 1080 unitExported = true; 1081 } 1082 if (unit.getType() == SKGUnitObject::SHARE) { 1083 unitExported = true; 1084 investment = true; 1085 } 1086 1087 QString date = SKGServices::dateToSqlString(operation.getDate()); 1088 if (date.isEmpty()) { 1089 // This is an opening balance 1090 date = QStringLiteral("0000-00-00"); 1091 } 1092 stream << 'D' << date << SKGENDL; 1093 if (!unitExported) { 1094 stream << 'T' << SKGServices::doubleToString(operation.getCurrentAmount()) << SKGENDL; 1095 } 1096 1097 if (!investment) { 1098 auto number = operation.getNumber(); 1099 if (!number.isEmpty()) { 1100 stream << 'N' << operation.getNumber() << SKGENDL; 1101 } 1102 } else { 1103 stream << 'N' << (operation.getCurrentAmount() > 0 ? "Buy" : "Sell") << SKGENDL; 1104 } 1105 1106 if (unitExported) { 1107 stream << 'Y' << unit.getSymbol() << SKGENDL; 1108 } 1109 1110 SKGPayeeObject payeeObj; 1111 operation.getPayee(payeeObj); 1112 QString payee = payeeObj.getName(); 1113 QString address = payeeObj.getAddress(); 1114 if (date == QStringLiteral("0000-00-00")) { 1115 payee = OPENINGBALANCE; 1116 } 1117 if (!payee.isEmpty()) { 1118 stream << 'P' << payee << SKGENDL; 1119 } 1120 if (!address.isEmpty()) { 1121 stream << 'A' << address << SKGENDL; 1122 } 1123 1124 QString memo = operation.getMode() % " " % operation.getComment(); 1125 memo = memo.trimmed(); 1126 if (!memo.isEmpty()) { 1127 stream << 'M' << memo << SKGENDL; 1128 } 1129 1130 SKGOperationObject::OperationStatus status = operation.getStatus(); 1131 stream << 'C' << (status == SKGOperationObject::MARKED ? "C" : (status == SKGOperationObject::CHECKED ? "R" : "")) << SKGENDL; 1132 1133 // Get sub transactions 1134 SKGObjectBase::SKGListSKGObjectBase subtransactions; 1135 err = operation.getSubOperations(subtransactions); 1136 IFOK(err) { 1137 int nbSubOps = subtransactions.size(); 1138 QString category; 1139 if (nbSubOps == 1) { 1140 SKGSubOperationObject suboperation(subtransactions.at(0)); 1141 // Dump quantity 1142 if (unitExported) { 1143 stream << 'Q' << SKGServices::doubleToString(qAbs(suboperation.getQuantity())) << SKGENDL; 1144 stream << 'I' << SKGServices::doubleToString(qAbs(operation.getCurrentAmount() / suboperation.getQuantity())) << SKGENDL; 1145 } 1146 1147 // Get category of this simple operation 1148 SKGCategoryObject cat; 1149 suboperation.getCategory(cat); 1150 category = cat.getFullName().replace(OBJECTSEPARATOR, QStringLiteral(":")); 1151 } 1152 1153 // Is it a transfer 1154 SKGOperationObject transfer; 1155 if (operation.isTransfer(transfer)) { 1156 if (!category.isEmpty()) { 1157 category.prepend('/'); 1158 } 1159 1160 SKGAccountObject transferAccount; 1161 err = transfer.getParentAccount(transferAccount); 1162 IFOK(err) category.prepend('[' % transferAccount.getName() % ']'); 1163 } 1164 if (!category.isEmpty()) { 1165 stream << 'L' << category << SKGENDL; 1166 } 1167 1168 if (nbSubOps > 1) { 1169 // Split operation 1170 for (int k = 0; k < nbSubOps; ++k) { 1171 SKGSubOperationObject suboperation(subtransactions.at(k)); 1172 SKGCategoryObject cat; 1173 suboperation.getCategory(cat); 1174 1175 QString category2 = cat.getFullName().replace(OBJECTSEPARATOR, QStringLiteral(":")); 1176 if (!category2.isEmpty()) { 1177 stream << 'S' << category2 << SKGENDL; 1178 } 1179 QString memo2 = suboperation.getComment(); 1180 memo2 = memo2.trimmed(); 1181 if (!memo2.isEmpty()) { 1182 stream << 'E' << memo2 << SKGENDL; 1183 } 1184 stream << '$' << SKGServices::doubleToString(suboperation.getQuantity()) << SKGENDL; 1185 } 1186 } 1187 } 1188 1189 stream << '^' << SKGENDL; 1190 } 1191 IFOKDO(err, m_importer->getDocument()->stepForward(i + 1)) 1192 } 1193 1194 SKGENDTRANSACTION(m_importer->getDocument(), err) 1195 } 1196 IFOKDO(err, m_importer->getDocument()->stepForward(2)) 1197 1198 // Export prices 1199 SKGObjectBase::SKGListSKGObjectBase unitvalues; 1200 IFOKDO(err, m_importer->getDocument()->getObjects(QStringLiteral("v_unitvalue"), QStringLiteral("1=1 ORDER BY (select t_name from unit where v_unitvalue.rd_unit_id=unit.id), d_date"), unitvalues)) 1201 nb = unitvalues.count(); 1202 if (!err && (nb != 0)) { 1203 stream << "!Type:Prices\n"; 1204 err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export units"), nb); 1205 for (int i = 0; !err && i < nb; ++i) { 1206 SKGUnitValueObject unitVal(unitvalues.at(i)); 1207 SKGUnitObject unit; 1208 err = unitVal.getUnit(unit); 1209 IFOK(err) { 1210 QStringList vals; 1211 QString v = unit.getSymbol(); 1212 if (v.isEmpty()) { 1213 v = unit.getName(); 1214 } 1215 vals.push_back(v); 1216 vals.push_back(SKGServices::doubleToString(unitVal.getQuantity())); 1217 vals.push_back(SKGServices::dateToSqlString(unitVal.getDate())); 1218 1219 stream << SKGServices::stringsToCsv(vals) << SKGENDL; 1220 } 1221 IFOKDO(err, m_importer->getDocument()->stepForward(i + 1)) 1222 } 1223 stream << "^" << SKGENDL; 1224 1225 SKGENDTRANSACTION(m_importer->getDocument(), err) 1226 } 1227 IFOKDO(err, m_importer->getDocument()->stepForward(3)) 1228 SKGENDTRANSACTION(m_importer->getDocument(), err) 1229 } 1230 1231 // Close file 1232 file.commit(); 1233 } 1234 1235 return err; 1236 } 1237 1238 QString SKGImportPluginQif::getMimeTypeFilter() const 1239 { 1240 return "*.qif|" % i18nc("A file format", "QIF file"); 1241 } 1242 1243 #include <skgimportpluginqif.moc>