diff --git a/CHANGELOG b/CHANGELOG index 4ab4415d1..464fd892d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,1936 +1,1937 @@ skrooge (2.20.0) *Correction bug 406903: no message when Skrooge can't open .skg file given on command line *Correction bug 406904: skrooge command-line help "--+[URL]" doesn't match its behavior *Correction bug 406741: QFX Date Import + *Feature: New REGEXPCAPTURE operator in "Search & Process" to capture a value by regular expression -- Stephane MANKOWSKI xxxx skrooge (2.19.0) *Correction bug 406321: ERR-8/ERR-5 importing qfx file, "More than one object returned in 'v_account' for 't_number='''" *Correction bug 406243: Skrooge does not import share operations in QIF file correctly *Correction bug 406270: Skrooge does not import investment operations in QIF correctly *Correction bug 406271: Skrooge does not import description of tags in QIF file into Tracker comment *Correction bug 406266: Skrooge does not import shares in QIF correctly *Correction bug 405578: build a nightly debug Skrooge flatpak *Correction bug 406488: crash importing a CSV *Correction bug 406549: Edit menu in Skrooge handbook is missing Skip, adds Find; missing context menu documentation *Correction: Add missing file categories_previous_period_table.html in monthly report *Correction: Migration failure with sqlite >= 3.25.0 due to ALTER TABLE behavior modification *Correction: Migration failure when some i_number are NULL *Correction: build skroogeconvert as a regular ("nongui") executable instead of an app bundle *Correction: prevent runtime discarding of the app icon if icon-from-theme lookup doesn't work (which it doesn't by default on OS X) *Correction: generate a multi-scale icon from all png icons size 256px and smaller *Feature: Tracker with running balance in "Operations" page *Performances: Better performances in payee view to compute recommended categories *Performances: Better performances in dashboard and monthly report when amounts by accounts *Performances: Better performances in dashboard to find all payee without operation *Performances: Better performances in "Delete payees without operation" -- Stephane MANKOWSKI Sat, 20 Apr 2019 14:03:43 +0200 skrooge (2.18.0) *Correction bug 403575: skrooge w/ -DSKG_WEBENGINE=ON: ui_skgtablewithgraph.h:15:10: fatal error: QtWebKitWidgets/QWebView: No such file or directory *Correction bug 403675: App crashes with segfault after second import *Correction bug 402330: error importing QIF File "SKGImportPluginQif::importFile() RC=[ERR-5]: SKGCategoryObject::addCategory failed because linked object is not yet saved in the database. *Correction bug 403725: import Quicken tags from QIF as Skrooge trackers *Correction bug 403985: kgbasegui/skgwebview.h:28:10: fatal error: qwebview.h: No such file or directory *Correction: Import KMYMONEY with non utf8 characters doesn't work (see https://forum.kde.org/viewtopic.php?f=210&t=156506) *Correction: Import KMYMONEY of scheduled operations from the next operation to add instead of the first one (to avoid duplicate) *Correction: Import KMYMONEY of shares with only one split must be done with the unit of the share *Correction: Nb occurrences and last date are not aligned on weekly schedules *Correction: Display all occurrences of a schedule in dashboard not only the first one *Correction: Display the currency symbol at the right place in scheduled operations *Correction: Display correctly the amount in "highlighted operations" in dashboard *Correction: The advice "Advice are very long to compute" can now be rejected *Correction: Add date in "Bank (light)" widget in html mode *Correction: Remove template from "Highlighted operation" widget *Feature: Better help for "Internet code" of sources in unit page *Feature: New MSN source of download for quotes *Feature: File can be made anonymous in a reversible mode *Feature: In "Operations" page, possibility to view all operations of an account + its credit cards associated *Feature: The number of years for forecasts based on scheduled operations can be choosen from settings *Feature: Possibility to choose the max date of schedules in dashboard *Feature: New action to skip scheduled operations *Feature: Possibility to skip scheduled operations from dashboard *Feature: Addition of options (Incomes, Expenditures, Transfers, ...) in contextual menu of graphs in "Account" page *Feature: Addition of options (Days, Weeks, Months, ...) in contextual menu of graphs in "Account" page -- Stephane MANKOWSKI Mon, 11 Feb 2019 21:32:09 +0100 skrooge (2.17.0) *Correction bug 400695: Designer plugins get installed as versioned libraries *Correction bug 400724: Import of Quicken 2012 .QIF file failed due to tags *Correction bug 402031: ERR-4/ERR-5 opening relative path to .skg from command line, then zombie Skrooge *Correction: Transfer created with value at 0. Regression due to 399672. (see https://forum.kde.org/viewtopic.php?f=210&t=155713&p=406978) *Correction: QIF import with transfer on split (see https://forum.kde.org/viewtopic.php?f=210&t=155614) *Correction: Add delay (300 ms) on text filter *Correction: "Search & Process" create a duplicate a duplicated category in some cases *Correction: Compliance with SQLCipher 4 *Correction: Close SQL database in mmb import *Feature: Like on transfer, the ratio is requested when creating a share operation (see https://forum.kde.org/viewtopic.php?f=210&t=155605&p=406979) *Feature: Progress bar in taskbar *Feature: Support only QT >= 5.7.0 -- Stephane MANKOWSKI Sun, 16 Dec 2018 17:50:46 +0100 skrooge (2.16.0) *Correction bug 398683: Periodic crashes on dashboard *Correction bug 397611: [ERR-5]: Format de date non pris en charge importation boobank *Correction bug 399480: Grammar mistake in .po file / - s missing in simple *Correction bug 399482: Wrong plural form in .po-File *Correction bug 399483: Spelling mistake in .po file "Transfert" should be "Transfer" *Correction bug 399672: Modifying multiple selected operations into transfers creates empty category and tracker if not identical *Correction: Ofx import must import FEE as debit *Correction: Document migration fails if format is "d.MM.yy 'г" (see: https://forum.kde.org/viewtopic.php?f=210&t=155575) *Correction: Inconsistency in "Incomes vs Expenditures" on sub operations with trackers *Correction: Build on windows *Correction: Use CPU instead CPU for QML (needed for printing) *Correction: Set background color on print to avoid print preview with black background on dark theme *Feature: Selection above, below or parent when delete an object *Performances: Solve performance issue due to new feature : More tooltips on "Operations" table -- Stephane MANKOWSKI Sun, 04 Nov 2018 14:12:43 +0100 skrooge (2.15.0) *Correction bug 397018: Check sqlcipher installation (issue detected on GENTOO) *Correction: Crash when sort a grouped view *Correction: Avoid to create 2 categories with the same name under the same category by using drag and drop *Correction: Avoid too many computation in SKGAccountObject::getPossibleReconciliations *Feature: Addition of a new option to check if import has been broken *Feature: More tooltips on "Operations" table -- Stephane MANKOWSKI Wed, 15 Aug 2018 22:01:16 +0200 skrooge (2.14.0) *Correction bug 394857: Reports do not graph operations by week correctly *Correction bug 395328: Bad perfo in SKGAccountObject::getPossibleReconciliations when too many operations *Correction: Sort on second column does not work with grouping *Correction: Change bootstrap url in templates *Correction: Better grouping for "Week", "Month", "Semester", ... *Correction: Report with forecast based on budget use the account using the most the category *Correction: Respan of group header must work when auto resize is disabled too *Correction: Import backend of account with "." in name *Correction: Remove error in boobank commands *Correction: Set automatically the bank account only when the icon is changed *Correction: The application is hidden when the dashboard is opened on an empty document *Correction: On operation page, the sort by the balance attribute sorts operations by date+id to have correct balances *Correction: Weboob backend doesn't work with option if account name has "id" *Feature: Addition of "Import date" on account object *Feature: Possibility to merge accounts with or without updating the initial balance *Performances: Improvement of performance of SKGTreeView::selectObjects and SKGTreeView::getState when grouping with many groups -- Stephane MANKOWSKI Sun, 24 Jun 2018 19:00:18 +0200 skrooge (2.13.0) *Correction bug 392828: Choose what to display on selection *Correction: Simple search in "Search & Process" doesn't escape words *Correction: Add notification (sound) on all creations / modifications of objects *Feature: Possibility to open concerned operations when auto reconciliation failed *Feature: Option on report to show decimals or not *Feature: Option "Execute on not checked operations" on "Search & Process" page *Feature: Addition of "Reconciliation balance" attribut on "Account" + icon to check if previous reconciliation has been broken or not *Feature: Addition of a new option to check if reconciliation has been broken -- Stephane MANKOWSKI Mon, 07 May 2018 20:31:26 +0200 skrooge (2.12.0) *Correction bug 389867: Amount Input Changes Value Prematurely *Correction bug 389866: Date Input Field Not Localized *Correction bug 389899: Split dates aren't updated when copying previous entry *Correction bug 390223: Opening d/l QFX file opens new skrooge file instead of importing into existing - opened file *Correction: Remove compilation warning (-Wsign-promo) *Correction: Can't resize the window and its width exceeds the screen (https://forum.kde.org/viewtopic.php?f=210&t=151023) *Correction: Search and process doesn't work in template mode with category used (see: https://forum.kde.org/viewtopic.php?f=210&t=150940) *Correction: No sound notification when an operation is created (see: https://forum.kde.org/viewtopic.php?f=210&t=151296) *Feature: All bookmarked report can be easily added to the dashboard with the + menu *Feature: New "Treemap" graph mode in reports *Feature: Automatic merge of payees, accounts and units when updated with another existing name *Feature: Alpha numerical values are now supported for number of operation (see: https://forum.kde.org/viewtopic.php?f=210&t=150988&p=394706) *Feature: Addition of a new option to displaythe environment variables used by Skrooge -- Stephane MANKOWSKI Thu, 08 Mar 2018 20:06:43 +0100 skrooge (2.11.0) *Correction bug 386942: .py scripts in /usr/bin *Correction bug 388955: Splitting Currency and Amount into separate columns *Correction: Better account number in weboob import *Correction: Weboob import is now importing account with better name (use of "label") *Correction: Weboob import compliant with utf8 *Correction: Failure when launching "skrooge filename.xxx" when filename.xxx is a file to import *Correction: "Switch closed" is available on unit but units are not closable *Correction: When a transfer is created only one of both operation has the tracker set: https://forum.kde.org/viewtopic.php?f=210&t=143127&p=384834#p384834 *Correction: Export of selection when selection size > 1 doesn't work *Correction: Define alternative icons when a "poor" icon set is used (like by default on gnome: Tango) *Feature: Tips of the day are now clickable and in the main page (not more popup panel) *Feature: Skrooge knows now 100 cryptocurrencies and is able to download their quotation *Feature: Enable AA_EnableHighDpiScaling if Qt version >= 5.6 *Performances: Better performances by resizing only visible columns (this is useful on payee table when category is hidden) *Performances: Better performances in the computation of the automatic category for a payee *Performances: Improvement of performance in SKGImportExportManager::findAndGroupTransfers. 11552 ms => 285 ms -- Stephane MANKOWSKI Sat, 03 Feb 2018 11:16:35 +0100 skrooge (2.10.5) *Correction bug 386594: Can not load share prices for French stocks -- Stephane MANKOWSKI Sat, 25 Nov 2017 23:15:27 +0100 skrooge (2.10.0) *Correction bug 383758: Report unreadable because element outlines are to thick *Correction bug 384119: Operation without suboperation after a kmy import *Correction bug 375865: Quick fill: select/enter item does not lock payee *Correction bug 384801: Converting multiple operations with different amounts to "transfer" type changes all amounts to 0 *Correction bug 384802: When creating multiple transfers into a different currency use the correct exchange rate for each date *Correction bug 385002: Display and edit default category for payee *Correction bug 385277: Wrong budget for split-operations with unaligned dates *Correction bug 384803: Creating many transfers to a different currency leads to uninterruptible series of dialog boxes *Correction bug 385990: Altered budgets aren't displayed until you reload the tab *Correction bug 386285: The minimum/maximum limit in account definition is interpreted as Primary Currency *Correction: Bad english in help on rules *Correction: Memory leaks *Correction: The source of unit download is now able to support date in "en_EN" locale event if this is not the system locale. Mandatory for implementing a Google Finance source. *Correction: Don't display the page of operations when the import is canceled *Correction: Better error messages in backend import *Correction: Optimisation in backend import *Correction: Optimise html of monthy reports *Correction: Port from QWebKit to QWebEngine *Correction: Avoid to compute too many combinations (and take hours) when trying to do an automatic reconciliation *Correction: Backend import must match account with id with letter like this 123456A@ldl *Correction: Never merge 2 accounts with same name in backend import *Correction: Crash when selecting many lines in search & process page *Correction: Enable print functions only when at least one page is opened *New feature: New "Google Finance" source of download for units *New feature: The source of unit download can be now an external script *New feature: New import backend "weboob_coming" importing coming operations (can be used for card with deferred debit) *New feature: A payee can be closed. Then it won't be available in combo boxes in operation page *New feature: A category can be closed. Then it won't be available in combo boxes in operation page *New feature: New parameters in boobank backend to be able to import operations only since a specified date *New feature: Export ledger format *New feature: The tooltip of "Search" explains now how the entered value is understood *Performances: Better performances on initial load (avoid to intialise a document before loading one other) -- Stephane MANKOWSKI Thu, 02 Nov 2017 21:17:06 +0100 skrooge (2.9.0) *Correction bug 380235: Reports table view: missing characters *Correction bug 380232: Yahoo api discontinued *Correction bug 380187: Shares always use the current rate/value. On unit page, you can choose if you want to see the history of the unit value or the history of the amount owned *Correction bug 380827: Pie chart alignment *Correction bug 380460: Failure when loading file from skrooge 1.12 *Correction bug 380821: Add keyboard shortcut to rename bookmarks *Correction bug 380820: Custom banks do not use their custom icon/image *Correction bug 380818: Cash account is not associated with a bank *Correction bug 380802: Going back and forth from "fullscreen mode" (Ctrl+Shift+F) loses the focus on "pages" tab on the left sidebar *Correction bug 381056: Dashboard portfolio widget: Wrong percentage values *Correction bug 381562: csv file import: unable to detect matching account *Correction bug 381168: Change tab order of properties editor *Correction bug 381847: Income & Expenditure widget does not match corresponding report *Correction bug 382162: Wrong budget in root categories if subcategories also include subcategories *Correction: Multithreading SQL connections must be compliant with SQLite mode *Correction: Temporary skg file protected by password are not well restore *Correction: Replacement of missing icons *Correction: Auto apply on advice duplicates remaining advice *Correction: The "duplicate" function creates operation with invalid creation date *Correction: Regular expressions for CSV import are now translatable *Correction: Values are not refreshed in amortization table when annual rate or insurance rate are modified *Correction: Zoom on dashboard widget in QML doesn't work *Correction: No refresh of "month" in the title of some dashboard widgets *Correction: In about, the authors of sub plugins are now displayed *New feature: New import backend using aqbanking *New feature: Import backends can now have parameters *New feature: Skrooge is now able to export only the selected accounts or operations *New feature: Bulk mode in import backends *New feature: In "Monthly Report", possibility to get attributes on pointed objects *New feature: Addition of new BitcoinAverage source to be able to download bitcoin rates (due to unavailability of BitcoinCharts) *Performances: Avoid advice computation in dashboard when the dashboard is not the current page *Performances: Better performance in advice "Some payees seem to be identical" *Performances: Better performances on load and save -- Stephane MANKOWSKI Sat, 12 Aug 2017 10:11:37 +0200 skrooge (2.8.1) *Correction: Bad display of information of current account when a second document is opened -- Stephane MANKOWSKI Sat, 15 Apr 2017 20:41:56 +0200 skrooge (2.8.0) *Correction bug 375721: Right-click (context menu) reload view *Correction bug 375875: Changing password does not work *Correction bug 375712: See operation for a payee *Correction bug 376025: Clear fields don't restore currency *Correction bug 375865: Quick fill: select/enter item does not lock payee is not well computed *Correction: The "Other" category in dashboard and report widget name "5 main categories of expenditure" *Correction: Sometimes the sort on date in operation table is not the same than the sort used to compute the balance, this causes troubles *Correction: Create fake operation doesn't work on account not having the primary unit (https://forum.kde.org/viewtopic.php?f=210&t=138803&sid=d0d34f728ef7f41bb6c32764ccf3d8f6) *Correction: "Income vs Expenditure" dashboard widget is not refreshed when data are modified *Correction: Bad display in "5 main categories of expenditure" in QML dashboard widget *Correction: Better performance in "Incomes vs Expenditures" dashboard widget *Correction: Duplication of operation must not duplicate the creation date *New feature 375866: Quick fill find operations in other account *New feature: New columns visible in operation table *New feature: New "budget" dashboard widget in QML *Performances: Better performances by using multithreading and caching *Performances: Better performances in "Apply all recommended corrections" -- Stephane MANKOWSKI Wed, 05 Apr 2017 20:24:45 +0200 skrooge (2.7.0) *Correction: Due to a security hole, sqlcipher is no more an option. If you use password protection, we strongly encourage you to use this version of Skrooge and to change your password *Correction: "Delete" and "Add property" are never activated in "Simulator" page *Correction: Current periods (Quarter, Semester, Year) must be available in some widgets of the dashboard *Correction: Only previous months are relevant in "Personal finance indicator" of the dashboard *Correction: Boobank backend does not import transactions when the date format is YYYY-MM-DD 00:00:00 *Correction: Remove "Stooq monthly history.txt" because it does not work *Correction: Description of SKGPeriodEdit is not correct *New feature: Addition of the "Other" category in dashboard and report widget name "5 main categories of expenditure" *New feature: New grantlee filter to execute the sql order you want on the document. This will improve html reports *New feature: New grantlee filter to convert an integer in file size format (example: 390.2 MB) *New feature: Ability to open a file property by url *New feature: CSV export from report is now able to export raw values (http://forum.kde.org/viewtopic.php?f=210&t=112209&p=290153#p290153) *New feature: Resize of account number in "Accounts" page to be able to entre IBAN number *New feature: "Rename" for all dashboard widget *New feature: "Search & process" is now possible on bank name. This allows to create an alarm based on the deposit guarantee level per bank -- Stephane MANKOWSKI Sun, 22 Jan 2017 17:45:58 +0100 skrooge (2.6.0) *Correction bug 372470: Scheduled transactions on dashboard *Correction bug 372938: merge imported operations wrong warning about different amounts *Correction bug 373147: Dashboard/Scheduled operations typo *Correction: Remove old skrooge icons to use the official breeze icons *Correction: Do not install namelink for private library *Correction: Avoid inconsistency when primary unit is not well defined (https://forum.kde.org/viewtopic.php?f=210&t=136856) *Correction: QIF import fails when non utf8 chars are found *Correction: Display all type of item in "Search & Process" *Correction: Execution of "template" rule after import destroys the template itself (https://forum.kde.org/viewtopic.php?f=210&t=137121&sid=b3336f56f03d79f50ad7d53b9e919076) *Correction: Recovery of the working copy doesn't work in sqlcypher mode *Correction: Better performances and memory use in SKGAccountObject::getPossibleReconciliations *Correction: Better automatic account selection by using bank and agency numbers *Correction: Bad unit symbols in min and max limits in accounts page *Correction: Impossible to duplicate a page not having selection (eg. Dashboard, monthly report) *Correction: The dock "Message" is not reinitialized when a new file is loaded *Correction: In budget module, replacement of "Remove existing budgets" by "Remove existing budgets for xxx" *Correction: BitcoinCharts currency import order must be inverted because CSV file has been inverted *Correction: Better display for "Income vs Expenditure" on QML dashboard *Correction: New limit to history size set to 999 instead of 99 *Correction: To avoid partial import with backend, the import is fully done in case of delta detected *Correction: weboob backend is now searching transactions on date AND rdate to avoid missing transaction *Correction: Skrooge is now able to download addition stuff (monthly report template, quote source) with knewstuff *Correction: Open file property fails when the property name contains the full path (https://forum.kde.org/viewtopic.php?f=210&t=137528) *Correction: Avoid duplicated operation when importing attached accounts with weboob *Correction: Group on suboperations must display min, average and maw amounts (https://forum.kde.org/viewtopic.php?f=210&t=137569) *Correction: The option "Import only operations since the last imported one" must not be applied with backend import *Correction: "Estimated interests" are now in QML in dashboard and are available for monthly reports *Correction: Addition of tooltips on each operator in "Search & Process" *New feature: Addition of "nocolor" to the "money" Grantlee filter *New feature: Remove .skg in backup file name if .skg is added in the prefix (https://forum.kde.org/viewtopic.php?f=210&t=136518&p=366065#p366065) *New feature: "Alarm" dashboard widget is now compliant with QML mode -- Stephane MANKOWSKI Tue, 27 Dec 2016 18:42:32 +0100 skrooge (2.5.0) *Correction bug 364407: Doesn't build with Qt 5.7 *Correction bug 366025: Skrooge perpetually asks me to save updated tab state *Correction bug 368196: Delete sub-operations in edit delete selected operation in list *Correction bug 368356: Show amount sign on lost focus *Correction bug 368195: No lock on target account for a transfer *Correction bug 369090: Transfers sub operation created by template has wrong date *Correction: Migration connect from ui file to cpp file *Correction: Better colors of amounts in tooltips of advices *Correction: The 31 of the month, in budget page, "Previous month" does not work *Correction: When a second instance of Skrooge is launched with a file as argument, the file must be imported in the first instance of Skrooge *Correction: Since QT5.7, no more dependency on KDELibs4Support *correction: Dates wrong on dashboard - date format instead of the date itself *Correction: Keep properties when 2 operations are merged *Correction: Correction in weboob backend to avoid missing transactions and improve performances *New feature: Massive update is now available for amount in "Operation" page *New feature: Massive update is now available for date in "Operation" page *New feature: Capability to set/change order of budget rules *New feature: Tooltip on modified amount of budget to explain the reasons of modifications *New feature: Import of PDF invoice -- Stephane MANKOWSKI Sun, 25 Sep 2016 09:00:24 +0200 skrooge (2.4.0) *Correction bug 359679: Building the v2.3.0 package on Arch Linux dies with an error 'isnan' was not declared in this scope *Correction bug 362231: Lost date in imported split operation *Correction: Corruption of document after removing the password *Correction: In unit page and dashboard widget the annual performance is now computed on a year (not with an extrapolation of the last performance) *Correction: Refresh "Interest" dashboard widget when operations are modified *Correction: Copy needed files in local when a new template (for monthly report) is created *Correction: All advices are not displayed in dashboard *Correction: More setPlaceholderText for better GUI *Correction: Responsive report is now using highchart *New feature: New tutorial grantlee template to learn how to develop a new template *New feature: New option to select if the dashboard must be in html or qml. Qml mode is still experimental -- Stephane MANKOWSKI Fri, 06 May 2016 21:07:29 +0200 skrooge (2.3.0) *Correction bug 357081: No option to disable tray icon *Correction bug 357414: Bad display amount in split editor *Correction bug 358315: Typo in .desktop file leads to KRunner freeze *Correction: Avoid failure in kmy import when sub category already exists *Correction: Better autoreconciliation (the autoreconciliation can be done even when operations are not well sorted) *Correction: Migration to nullptr *Correction: Replace "XXX* yyy = new XXX" by "auto yyy = new XXX" *Correction: Thanks to regular expression, the "word" function is now able to interpret "N:1234" as a 2 words "N" and "1234" *Correction: Better icons (correction of installation for breeze) *Correction: Autoreconciliation doesn't work for accounts in other units than the primary *Correction: Better computation of the preferred currency for QIF import *Correction: After a modification or an creation the focus must be given to the table *Correction: Error when launching Skrooge from krunner *Correction: Bad sums in reports *New feature: New column "Balance import" in account page with an indicator to know if the account is still aligned with import balance *New feature: Check balance of accounts after import with backend *New feature: New "Personal Finance Score" in monthly reports *New feature: New "Personal Finance Score" in dashboard *New feature: Selection sum is now in the status bar *New feature: On tables, when the filter changes, the selection remains visible *New feature: Ctrl+Maj+V allows to validate OR UNVALIDATE the imported operations -- Stephane MANKOWSKI Sun, 21 Feb 2016 21:55:44 +0100 skrooge (2.2.0) *Correction bug 352674: SQLite version of the system not aligned with embedded in Qt when using sqlcipher *Correction bug 349961: Values in "Text" report are clickable to open corresponding operations *Correction bug 349976: Default categories for payees *Correction bug 354139: Pb when opening operations in sub-operation view *Correction bug 341463: Monthly reports include transfers between accounts and shows as income/expenses *Correction bug 354439: Re-open close account with money: wrong warning *Correction bug 354463: Incomes/Expenditures in monthly reports do not account for sub-operations of split operations *Correction bug 354424: Displayed balance is 0,00 and operation is ignored afterwards *Correction bug 354817: Arrows should increment/decrement operation number *Correction bug 355376: Category too small in split editor *Correction bug 355956: Not existent type in dashboard *Correction: Drag and drop file is now able to move a file *Correction: The WORD function of "Search & Replace" is no more sensible of useless space *Correction: Open skrooge document from desktop *Correction: Set open source licence on svg files *Correction: Correction ofx import in ECM_ENABLE_SANITIZERS='address' mode *Correction: Better print of dashboards by printing each widget individually *Correction: Better counting of pages in print function *Correction: Monthly reports with "default" template is now printed in only one page (remove page-break-before: always) *Correction: Monthly reports with "responsive" template must not print url *Correction: Migration from sqlite to sqlcipher mode does not work when the password contains the character " *Correction: In "Search & Process", the between function must work when v1 Sun, 29 Nov 2015 18:48:31 +0100 skrooge (2.1.1) *Correction bug 351977: Missing Comment in desktop file *Correction bug 350722: Sudden date change from 20XX to 19XX *Correction: Upload of knewhotstuff does not work *Correction: Avoid performance issue when an operation is created very far in the future (example: 01/01/8015) *New feature: New source of download "GrandTrunk" better than yahoo finance for currencies -- Stephane MANKOWSKI Fri, 11 Sep 2015 13:10:09 +0200 skrooge (2.1.0) *Correction bug 349349: Allow to enter year with 2 digits only *Correction bug 349328: Bad date on split operation after fast edition *Correction bug 349327: Bad date format for sub-operation display in split tab *Correction bug 349624: Add operator on fast filter *Correction bug 349776: Freeze when splitting and editing a future operation *Correction bug 349329: Tools should display modifications before applying them *Correction bug 349961: Split value of operations in reports/exports *Correction bug 350154: Cannot change rate symbol (%) position in Simulations panel *Correction bug 350722: Sudden date change from 20XX to 19XX *Correction bug 350937: Some words are not translated *Correction bug 340647: Encrypted Skrooge file is decrypted in-place *Correction: Authorize only word characters and space in name of templates of reports *Correction: Authorize only word characters and space in name of source of quotes *Correction: Missing unit symbol in monthly report *Correction: Failure on export *Correction: Bad display in period selector of "Income & Expenditure" dashboard widget *Correction: Bad filters in some open file dialog *Correction: Error when open link with non ASCII characters *Correction: Better detection of unaligne dates between operations and sub operations *Correction: The first update of monthly report must take into account the selected template instead of using the default one *Correction: Add credits page in "About" *Correction: Double click on skrooge document opens skrooge but the document is not loaded *Correction: Code rework for contextual menus *New feature: "Open..." action in contextual menu of reports *New feature: New function to import simple rules (payee=xxx => category=yyy) from csv file *New feature: More period options in dashboard widgets *New feature: "5 main categories of expenditure" in dashboard and report is now clickable *New feature: "Quotes" in dashboard and report is now clickable *New feature: Addition of list of key users in credits *New feature: Messages/errors can have associated action *New feature: "Open operations modified by this transaction" *New feature: Delete items in history -- Stephane MANKOWSKI Thu, 27 Aug 2015 19:20:30 +0200 skrooge (2.0.0) *Correction bug 347762: Invalid error message for SQLite version check after an update of SQLite *Correction bug 324653: Auto-Hide Scrollbars for Report Graphs *Correction bug 348865: Do not show translated tooltip *Correction bug 348490: Split with almost 0 operation *Correction: Porting on KF5 / QT5 *Correction: Use of Q_DECL_OVERRIDE *Correction: Bad account selected in editor when a closed operation is selected *New feature: Close action available in menu and contectual menu -- Stephane MANKOWSKI Sun, 14 Jun 2015 18:16:43 +0200 skrooge (1.12.5) *Correction bug 348619: Freeze amount should freeze unit *Correction bug 348621: Field order with tab key *Correction bug 348620: Display of multiple operations with common fields *Correction bug 348568: Payee with operations in 0 operation group *Correction bug 345994: Bad unit on some operation after search *Correction: Build for QT built with -reduce-relocations -- Stephane MANKOWSKI Wed, 03 Jun 2015 22:07:31 +0200 skrooge (1.12.0) *Correction bug 345974: Skrooge alarms keep firing up regardless of specified alert amount *Correction bug 346151: Fast edition do not fill operation sign *Correction bug 345998: Add tooltip for all history actions *Correction bug 345799: Moved then hiden column displayed *New feature: Automatic point operations created during reconciliation *New feature: "Simple" mode in "Search and process" page *Performances: Better performances on selection change in "operation" page -- Stephane MANKOWSKI Thu, 07 May 2015 20:46:21 +0200 skrooge (1.11.0) *Correction bug 341020: End of text in lineedit hides under the "clear" icon button *Correction bug 341450: Empty skg_bill.csv is created every time I launch Skrooge *Correction bug 341463: Monthly reports include transfers between accounts and shows as income/expenses *Correction bug 342737: Multiple date modifications on scheduled operations *Correction bug 343730: Resize/hide internal filter panel in report *Correction bug 335945: Support making split operations from standard operations by search and process *Correction bug 344250: Bank ING-DIRECT in France has changed its bank number *Correction bug 345580: Share without tax should not be split *Correction bug 345654: Btn Add grayed after entering values information *Correction bug 345661: Selecting multiple operations: amount is not ----- *Correction bug 345719: Enhance fast search/filter on lists *Correction bug 345826: Bas edit fields on list sub-operation after search *Correction bug 345416: Crash when searching on 'type' field *Correction: "Search" and "Report" do not work on properties of suboperations *Correction: Bad tooltips in "Report" for expand and collapse *Correction: Support invalid OFX file having debit with positive amount *Correction: Timeline does not work in some cases *Correction: More flexibility during import of ofx file not having id on transactions *Correction: Anonymized file is renamed to avoid accidental overwrite of the current file *Correction: weboob backend compliant with version 1.0 *Correction: Avoid crash and generate an error when the sqlite version of the system is not aligned with the sqlite version embedded in QT *Correction: Open suboperation from multi selected operations *Correction: Avoid deletion of previous file and backup during save in case of file system full *Correction: When a folder of bookmark a tab doesn't have the right icon *Correction: Stable sort when sorting by date to avoid ununderstandable balance *Correction: In table, when rows are groupes, the row representing the group spans all columns *Correction: Avoid crash in Add properties on suboperations *New feature 343766: Better custom report configuration *New feature: New dock to be able to see all messages displayed *New feature: Alarm messages can be have parameters for the total amount (%1), alarm amount (%2) and difference (%3) *New feature: Show sum of spendings in tables when operations are grouped *New feature: New advice detecting scheduled operations having date not aligned with the last inserted operation *New feature: The target directory of the backuped file can be force *New feature: Now some advices are recommended *New feature: In settings, you can define default comments, payees and categories for fake, commission and tax operations *New feature: New button on advices widget to apply all recommended advices *New feature: Display average, min and max on groups in tables *New feature: WORD function is now able to extract word from the right by using negative parameters *New feature: Addition of "Creation date" on operations -- Stephane MANKOWSKI Sun, 05 Apr 2015 19:18:04 +0200 skrooge (1.10.0) *Correction bug 334626: When doing a transfer, my specification of + or - is ignored *Correction bug 335943: Please add column "number of operations" to categories view *Correction bug 336320: OFX file import duplicates the last 12 transactions *Correction bug 338351: Reconciliation with additional currencies *Correction bug 338993: Change main currency fails to update amounts *Correction bug 338994: Currency rate not saved for transactions *Correction: Open URL does not work in unit page *Correction: Wrong date in message "Operation 'XXX' will be inserted in YYY days" *Correction: Auto repair of qif file not having sum of suboperation amount != operation amount *Correction: Better error message on mny import when java is not installed *Correction: Weboob0g backend is now compliant with weboob version 0h *Correction: Correction in bitcoin quote download *Correction: Remove duplicate in "To Account" field on operations *Correction: Import of mode for mny files *Correction: Better detection of transfers in mny import *Correction: Automatic resize of columns in "Search and Process" edit panel to facilitate the creation/update of rules *Correction: Missing template "categories_month_table" *Correction: For credit card account, the binding of account is optional *Correction: Remove useless "Expand All" and "Collapse All" on history table *Correction: "Add property" and "Delete" is now working on sub operations *Correction: "Highlight" is disabled on sub operations *New feature: The future can be added or not in "Current" or "Last" periods *New feature: Defered credit card management *New feature: Capacity to apply a template on operations *New feature: Capacity to automatically apply a template on operations from "Search and Process" *New feature: Sort of table memorize the previous sort to be able to sort by column 1 and column 2 *New feature: New advice to inform the user when the monetary decimal symbol is not defined in KDE localization settings *New feature: 2 options in graphs, one for limits visibility, one for average visibility *New feature: New advice to detect similar payees *New feature: New advice to detect similar categories *New feature: Add in documentation how to use kdewallet with weboob backend *New feature: Import iif file *Performances: Better performances in "Possible schedule" *Performances: Better performances in dashboard -- Stephane MANKOWSKI Tue, 28 Oct 2014 20:57:41 +0100 skrooge (1.9.0) *Correction bug 327148: Version 1.8.0 doesn't build *Correction bug 328335: Wish for fixed table row header *Correction bug 326764: When printing hard copy of report, only data in view in window is printed, not full report data *Correction bug 329568: Simulator utility problem *Correction bug 329825: When opening crypted file with false password skrooge falsely states the file is corrupted *Correction bug 329876: Weboob import from paypal ignores dates and creates account "0" *Correction bug 330354: Budget forecast duplicates operation creation *Correction bug 330428: Sorting doesn't work on Reconciliation date in Accounts view *Correction bug 331191: Obsolete term „Context“ in docbook *Correction: Remove conflict on CTRL+C shortcut *Correction: Import mny does not work with space in file name *Correction: In case of failure during backup file creation a warning message is displayed instead of an error avoiding to save the file *Correction: Refresh delta during reconciliation *Correction: Better PDF export from Monthly report *Correction: Import CSV unit file with "," as separator *Correction: Better KMyMoney import *Correction: Imported "Wallet" accounts are in bank named '' *Correction: Avoid wrong transfert in Grisbi import *Correction: The option "Import only operations since the last imported one" uses a delay of 4 days to avoid missing operations due to value date *Correction: Avoid error in import of homebank file without parent attribute on categories *Correction: Correction for unit download with Stooq *New feature: More option in "Income & Expenditure" dashboard widget *New feature: New shortcuts for "Expand all" and "Collapse all" *New feature: Operations can be split by date *New feature: Imports (csv, qif) support date format like this: 31Dec2012 *New feature: Import CSV support now "semicolon", "comma" and "tab" as separator *New feature: Monthly report is now able to display reports on months, quarter, semesters and years *New feature: New "responsive" template for monthly reports *New feature: In graph, sums are now all computed *New feature: Pareto curve in graph *New feature: New "quarter" and "semester" period in graph and "Incomes & Expenditures" dashboard widget *New feature: Ability to reorder the suboperations in the split operation *New feature: Better date filtering in operation page *New feature: More operators is "Search" panel *New feature: New statistic plugin -- Stephane MANKOWSKI Sat, 12 Apr 2014 16:11:26 +0200 skrooge (1.8.0) *Correction bug 319706: OFX trntype not imported, rest okay *Correction bug 320114: Wrong bank balance at dashboard *Correction bug 320112: Importing CSV operations with mixed " and ' text field separators *Correction bug 320226: Monthly report does not work due to missing template *Correction bug 320240: Impossible to enter exact amount for given unit in pop-up *Correction bug 320242: Transaction confirmation pop-up doesn't use defined unit value for transaction date for suggested value *Correction bug 320261: Dashboard "Income & expenditure": no values, strange colors *Correction bug 320157: Skrooge not working when .skg file is located on Samba share *Correction bug 320323: Add function ("rest to") In budget *Correction bug 320298: Default automatic format detection while importing a file leads to wrong entries creation *Correction bug 320070: Allow changing operation date through "Search & Process" *Correction bug 320716: Skrooge does not import all operations using weboob *Correction bug 320717: Use rdate instead of date in weboob backend *Correction bug 319993: Please adopt application logo/icon for smaller resolutions ==> skrooge-mini can be choosen for small resolutions *Correction bug 319990: Please fix icon alignment in Pages tab *Correction bug 322306: Skrooge Crash using Forecast Schedule *Correction bug 322069: Designer plugins should be unversioned .so files *Correction bug 323380: Minor date bug when using the stock portfolio widgets *Correction bug 320066: Crash importing KmyMoney exported file from Skrooge *Correction bug 324972: Import fails without an error when account is defined in CSV *Correction bug 325081: Add a « Yesterday » choice in the date picker *Correction bug 325174: Importing from gnucash file doesn't import accounts fo types 'Mutual Fund' nor 'Credit Card' *Correction bug 325223: Crash when exiting skrooge *Correction bug 324008: Erreur when importing .mny file. Error when analysing categories.json *Correction bug 325675: Build error in skgfileplugin.cpp for openSUSE 12.2 KDE 4.9 *Correction: KMyMoney exports does not need a check of integrity anymore *Correction: Addition of all icons in size 256 and 512 *Correction: Block drop of a bookmark under another bookmark *Correction: Bad date format detection when the second value is 9 (example: 3/9/04) *Correction: Remove the "Bank account " string in the name of the account name created by an OFX import *Correction: Icon for "Amount entered" *Correction: Authorize long number for operations like 5490990004 *Correction: Default graph mode = line *Correction: Better detection of duplicate operations after import *Correction: JSON export based on QJson *Correction: The setting "Import only operations since the last imported one" allows importation for the same date (< instead of <=) *Correction: Impossible to change state of operation from suboperation view *Correction: More permissive "Open duplicate" *Correction: Better setting layout for import/export *Correction: Import/Export minimum and maximum balance in KMyMoney importer *Correction: Import minimum balance in Grisbi importer *Correction: Import minimum balance in Homebank importer *Correction: New "weboob0g" backend compliant with the Weboob 0.g *Correction: When a file is selected for the icon of the bank, the file name is displayed in the name of the bank *Correction: In simulation page, the interest are computed with only operations with type=currency, not with shares *Correction: Error when importing skg file by double clicking when skrooge already open *Correction: No more password panel when the user loads an invalid Skrooge file *Correction: In report, the forecast based on budget is displayed even when no operation in account *Correction: Shortcuts in "Show" menus *New feature: Notifications and error messages are now based on KMessageWidget *New feature: The restore file is now base on KMessageWidget *New feature: Option to auto download from backend on opening file (default=false) *New feature: Import Microsoft Money documents (.mny) protected or not *New feature: Tarballs are not uploaded on download.kde.org *New feature: Addition of russian banks *New feature: Reopen last page closed *New feature: Max and min limits on accounts *New feature: Better and new advices based on limits of accounts *New feature: Sound emmission on operation creation *New feature: Import "Budgetary allocation" and "Fiscal year" from Grisbi files as properties *New feature: Information message explaining how to exist full screen mode *New feature: Addition of date of last reconciliation on account *New feature: Amount owned on unit table *New feature: Download date on unit table *New feature: New account type: Pension *New feature: New set of categories for french people *New feature: Addition of "Configure notifications..." in configuration menu *New feature: Download and add bills as property by using boobill (weboob) *New feature: Rename of property *New feature: Export HTML and ODT from tables *New feature: Automatic process to recover a corrupted file *New feature: Possibility to print current page *Performances: Better performances in dashboard *Performances: Better performances in monthly report *Performances: Better performances in import of multi files (or from backend) by applying "search & process" rules only one time *Performances: Better performances in kmy export *Performances: Better performances by avoid to refresh autocompletion on widgets after light transactions -- Stephane MANKOWSKI Sat, 05 Oct 2013 18:54:16 +0200 skrooge (1.7.1) *Correction bug 319565: Bad performances on ubuntu 13.04 *New feature: Open report from dashboard widgets -- Stephane MANKOWSKI Thu, 09 May 2013 22:28:28 +0200 skrooge (1.7.0) *Correction bug 316604: Income and Expense widget on the dashboard does not respect suboperations in split operations *Correction bug 316796: Reports crash when closed normally *Correction bug 318063: New file format (AFB120 CFONB) *Correction bug 279967: When automatically started at login, Skrooge is always displayed on all desktops *Correction bug 319145: Multiple currency support still buggy *Correction: Group operations with more than 2 operations selected *Correction: Group operations with already grouped operations *Correction: Hide internal properties in "Search & process", in operation page and in the function "Add property" *Correction: QIF import does not support mix of date format *Correction: The advice action "Remove groups with only one operation" does not remove the operation but the group *Correction: Better performances in kmymoney import and all other imports *Correction: skroogeconvert does not support files without path (example: skroogeconvert -in t.csv -out t.kmy) *Correction: Better performances of skroogeconvert in case of skg import or export *Correction: Global better performances by using the right index on unit table *Correction: Rename "Undo document" to "Revert document" *Correction: Reduce the number of messages when using backend import *Correction: Possibility to group tables on properties *Correction: Better support of import/export with url *Correction: Better merging of operations in QIF import *New feature: More modification functions in "Search & Process" panel for Payee, Account, Tracker *New feature: New "capitalize" function in "Search & Process" panel *New feature: New attributes "Unit" and "Transfer" in "Search & Process" panel *New feature: All update functions are now available for properties in "Search & Process" panel *New feature: New icon in tab bar to create new page *New feature: 'To account' in operation table and "Search & Process" *New feature: More attributes in line and column in reports (graph) *New feature: Monthly reports and main page use the general font of KDE *New feature: Possibility to force the date format for qif and csv imports *New feature: More functions in tool menu *New feature: More tips of the days *New feature: Dispay option "Hide pointed operations" in operation page *New feature: Possibility to group tables on hidden column *New feature: Facilitate creation of standard currencies with auto completion *New feature: Addition of the new currency "Bitcoin" with automatic download of the rate *New feature: In dashboard and monthly report, banks can be clicked to open corresponding operations *New feature: In report, the graph correction can be done by multiply or divide -- Stephane MANKOWSKI Sat, 04 May 2013 16:49:45 +0200 skrooge (1.6.0) *Correction bug 314389: weboob import does not work *Correction bug 314743: Can not resize window when operations tab is displayed *Correction bug 313926: Version 1.5.1 shows warnings *Correction bug 313948: Skrooge crashed after double clicking on an account after using clean import tool *Correction bug 313928: Warnings during build on OS X > 10.6 *Correction: Splash screen and notifications on windows *Correction: Better error management in weboob import plugin *Correction: Faster and without notification units downloads *Correction: Facilitate toolbar customization *Correction: Bad date format identification in some cases *Correction: Remove traces "Attempt to use QAction 'XXX' with KXMLGUIFactory" *Correction: Activation of link in "Incomes and Expenditures" dashboard widget *Correction: Operations in "Loan" accounts are not more taken into account in budget and "Incomes and Expenditures" dashboard widget *Correction: More point styles in graph and better rendering *Correction: Display of infinity symbol in dashboards and monthly reports *New feature: "Open all" and "Bookmark current page here" in the bookmark button *New feature: Button in bookmark dock to discover the contextual menu *New feature: Buttons on dashboard widgets to discover the contextual menu *New feature: New cursor on dashboard widgets to show that widgets are moveable *New feature: New dashboard (fast remove, fast add, fast move, fast drag & drop) *New feature: ToolButton displaying contextual actions on top right of tables *New feature: "Pages" launchers can be added in the toolbar *New feature: New WORD function in "Search & process" to extract a word of an attribute *New feature: More generic import through backends *New feature: Support values like this "3128/100" in imports -- Stephane MANKOWSKI Mon, 25 Feb 2013 18:06:13 +0100 skrooge (1.5.1) *Correction: Avoid endless "wait cursor" in case of canceled load of protected file *Correction: More robust weboob import for backend not supporting parallel download *Correction: Operations page layout with very long comments -- Stephane MANKOWSKI Tue, 22 Jan 2013 21:09:04 +0100 skrooge (1.5.0) *Correction bug 311252: Creating account with existing name just modify the existing one *Correction bug 312609: Banks widgets at the dashboard show wrong values *Correction bug 312671: Compile warnings *Correction bug 312859: Searching for an empty payee doesn't seem to work *Correction bug 313140: Thousands of compile warnings when using [-pedantic] [PATCH provided by Andi Clemens] *Correction bug 313141: Adjust layout in preferences window [PATCH provided by Andi Clemens] *Correction bug 271292: "Set to upper" process does not work with all characters *Correction bug 263265: Support regexps on imported data *Correction bug 313268: Better layout fot the "comment" field in the operations view *Correction bug 313240: Transfers between accounts with different currencies show as income/expense *Correction: Disable "Pin this page" when no page is opened *Correction: Disable "Print" and "Print preview" when no page is opened *Correction: Correction in the show menu of the operation page when only one account with ";" in the name *Correction: Pb of propagation for budget rule with Period=Current Year *Correction: Avoid duplicate message in notification *Correction: Avoid to set an operation in more than one budget item when "garbage" budgets are used *Correction: Display reconciliation field when the "Reconcile" function is called from the page of accounts *Correction: Transfers ignored by default in" "Incomes & Expenditures" widget and reports *Correction: Adapt the size of the "Account" field to the lenght of the accounts names *Correction: Set focus on "Date:" field after insertion or update of an operation *New feature: New "Process Immediately" method to process immedialetly some scheduled operations *New feature: Multiselection for "Jump to the operation" in scheduled operation page *New feature: New advice to detect non alignment of comments (operation vs suboperation) for simpe operations *New feature: New advice to detect operations without mode *New feature: New option Automatic refresh/Manual refresh in advice dashboard widget *New feature: New advice to switch on "Manual refresh" when advice are very long to compute *New feature: New advice to detect too complex unit definition like this: IBM => $ => £ => € (not supported) *New feature: Possibility to do graphic reports with the bank as column of line *New feature: Automatic import through weboob (http://weboob.org/) *Perfo: Better performances in advices by avoiding to compute dismissed advice *Perfo: Better performances in categories and payees tables *Perfo: Big performances improvement for huge transaction like imports or massive delete of accounts -- Stephane MANKOWSKI Sat, 19 Jan 2013 16:31:49 +0100 skrooge (1.4.0) *Correction bug 306517: "Incomes & expenses" widget displays a wrong sum for expenses *Correction bug 308050: Untranslatable strings in 'Budget' Page *Correction bug 308395: "list of operations" screen : inoperative filters (hide closed operations >1 & 2 weeks) *Correction bug 310372: Word completion in the comment field in standard operation does not work even if it was added via split operation *Correction: Better colors in "Expenditures vs Icomes" dashboard widget *Correction: Support qif file having Type:Class with description *Correction: Remove "All" in operation menu when only one account is created *Correction: Move "Export ..." menu in "File" menu *Correction: Bad display of account having a "&" in operation page *Correction: Move "Processing" actions in the main menu named "Tools" *Correction: Hide title when reset filter in operation page *Correction: Better performances in SKGDocumentBank::computeBalances by using compound query *Correction: Avoid freeze when a very old value (example: 14-11-0012) is entered by error for a unit *Correction: Avoid freeze in reports when a very old operation (example: 14-11-0012) is entered by error *Correction: "Quantity owned" in unit page does not initial amount of accounts *New feature: New button on unit page for opening the web site of the download source *New feature: Warning on closing non null account *New feature: Support for Activities *New feature: "Stock portfolio" in monthly report *New feature: "Stock portfolio" dashboard widget based on monthly report *New feature: Possibility to add hyperlinks in monthly reports *New feature: Export html in monthly reports *New feature: New widgets for dashboard *New feature: 2 new templates for monthly report are delivered by default *New feature: 2 new advices detecting very old values of unit and very old operations *New feature: New advice checking if budgets rules are treated *New feature: New option for import to "Import only operations since the last imported one" -- Stephane MANKOWSKI Sun, 09 Dec 2012 11:51:43 +0100 skrooge (1.3.3) *Correction bug 305880: On the Dashboard, scheduled transactions have wrong number of decimal places *Correction bug 306119: Cannot open document created in 1.3.0 with 1.3.2 *Correction bug 305879: Account panel shows Edition - should be Addition *Correction: Smaller bank icon selector in "Accounts" page *Correction: Set default type as "currency" in "Units" page when primary and secondary units are already defined *New feature: Add "savings" as type of account *New feature: "Tracked" operations in option of "Income vs expenditure" widget of dashboard *New feature: Export JSON -- Stephane MANKOWSKI Mon, 03 Sep 2012 16:25:21 +0200 skrooge (1.3.2) *Correction bug 300228: Failed to import from KMyMoney file *Correction bug 301137: 'To Account' of an existing money transfer is displayed incorrectly in edit area *Correction bug 302388: Gnucash import failes: Better error message in case of negative value for a currency *Correction bug 302502: Message for possible schedule incomplete *Correction bug 303527:Support more time options for ‘show checked operations’ *Correction bug 304003: Crash when removing transaction number of transactions with duplicate account numbers *Correction bug 304313: Importing a KMyMoney file results in error: "[ERR-11]: 'KMYMONEY-KMYMONEY-TEMPORARY-ACCOUNT': You are not authorized to delete this account because it contains some checked operations" *Correction: Rename columns of budget table *Correction: Better CSV and TXT exports of tables grouped by a column *Correction: Better SVG export of tables *Correction: Restore palette in tables after print *Correction: Do not display disabled schedules in dashboard *Correction: Remove text on common icons in toolbar by using QAction::LowPriority *Correction: In "Scheduled operations" page, the button "Jump to the operation" opens the operation page in template mode to authorize updates *Correction: New wording; "To be Checked" instead of "Foreseen" or "In Transit"; "Checked" instead of "cleared" or "validated" *Correction: Better mouse icon when closing pined page *Correction: Remove X confirmation messages when close a document having X pinned pages *Correction: Focus on opened page from bookmarks when the current page is pinned *Correction: Allow massive update in account page *Correction: Avoid bad table display when grouped *New feature: Progress bar in budget table for active budgets *New feature: "Open report" button from search page to open report without saving the query *New feature: Option to ignore operation tracked in reports *New feature: Option to ignore operation grouped in reports -- Stephane MANKOWSKI Mon, 06 Aug 2012 12:24:18 +0200 skrooge (1.3.0) *Correction bug 291512: Show hide checked operations setting are not retained *Correction bug 291550: Advice (dashboard) about operations with empty category : case of transfers *Correction bug 292225: Skrooge 1.2.0 crash on opening report *Correction bug 293398: Display Graph windows during Account Creation *Correction bug 293397: Error on duplicate account name creation *Correction bug 293580: Select/unselect "import state" column will display empty field *Correction bug 293941: Skrooge crashed on deleting split operation *Correction bug 278220: Wrong values in Monthly Report "Amounts in accounts" *Correction bug 296204: add balance entered in Operations *Correction bug 297722: Copy/paste comment in the table of a split operation leads to a wrong behaviour *Correction: creation of subcategories with entered number *Correction: hide templates when open operations from other pages *Correction: Use radio button in "Show" menus when needed *Correction: Support import of gnucash file without "gnc:book" *Correction: Generate an error message when trying to import a missing ofx file *Correction: FastEdition does not work when more than one "operation" page is opened *Correction: Crash on QIF export when the document has 3 operations grouped with op1.value=-op2.value *Correction: Refresh of graphs when unit values are modified *Correction: Install the grantlee filter plugin into the correct location. *Correction: Avoid empty "Account" page when no account exist *Correction: No automatic resize of columns when "auto resize" is not check *Correction: Addition the "rate" in internet code tool tip. *Correction: Amounts are displayed with the right number of digits in "operation" and "account" table. *Correction: Refresh view when a new appearance is selected *Correction: Bad computation of fake operation in case of multi selection *New feature: Better display of "Show" menu in operation table *New feature: Colors in "Incomes and Expenditure" dashboard widget *New feature: "Search & Process" is now working on comments of sub operations *New feature: "Base 100" mode *New feature: Skrooge is now able to import file have amounts with unit symbole (example: -$8.35) *New feature: Possibility to pin/unpin pages -- Stephane MANKOWSKI Mon, 23 Apr 2012 11:45:09 +0200 skrooge (1.2.0) *Correction bug 288683: E: skrooge-common: arch-dependent-file-in-usr-share usr/share/kde4/apps/skrooge/plugins/grantlee/0.2/grantlee_skroogefilters.so *Correction bug 290626: No support of OFX format *Correction: Avoid suboperations pointing on unused category when all categories are deleted *Correction: Hide internal properties in property dock *Correction: A new document is no more considered as modified *Correction: Advice "xxx is an old tracker" modified to use the LASTDATE instead of the FIRSTDATE *Correction: Better display in "Income & Expenditure" dashboard widget + addition of "Saving" *Correction: In "Account" page, "Other ..." in icon list is now working *Correction: Correction for advice "Possible schedule 'xxx'" *Correction: Hide internal properties in graph *Correction: Bug on inline edition on comment & mode *Correction: Optimisation on "anonymize" function for big files *Correction: Highlight switch on category *Correction: In operations tab, provide a "show" menu as for other plugins *Correction: Avoid duplication of some categories when search and process is used *Correction: Avoid modifications in interest table *Correction: Fast edition refreshs the "comment" field *Correction: The autocompletion list for number displays only the numbers of the selected account *Correction: Monthly report create .kde directory event if .kde4 must be used *New feature: Fields of update part of the search & process panel are not editable to facilitate the completion *New feature: Import of non local files (example: http://myserver/document.qif) *New feature: Export of non local files (example: http://myserver/document.qif) *New feature: Dashboard > Shares & Indexes renamed to "Quotes" and provide a possibility to select all available Units (Objects, Currencies etc.) *New feature: Operations > Transfer: do not display a name of a chosen source account in the field of target account *New feature: New mode in reports *New feature: Memorize expanded groups in bookmarks *New feature: "Incomes & Expenditures" widget is clickable *New feature: Highlight on units *New feature: Addition of "Show" menu in scheduled operations *New feature: Hide title in "Scheduled operations" if at least one schedule is existing *New feature: Open "Scheduled operations" when an operation is scheduled *New feature: SKGTreeView scrollbars are sticked on maximum position *New feature: SKGTableWidget scrollbars are sticked on maximum position *New feature: New "Open operations" function in contextual menu (connected with double click - support multi selection) -- Stephane MANKOWSKI Mon, 09 Jan 2012 11:01:12 +0100 skrooge (1.1.1) *Correction: "Highlighted only" is by default disabled in "Account" widget *Correction: Mysterious crash in SKGTreeView::onExpand -- Stephane MANKOWSKI Wed, 23 Nov 2011 21:44:09 +0100 skrooge (1.1.0) *Correction bug 283683: Can not add a new operation after modifying the values on an existing one *Correction bug 284220: Closed account still showing up in accounts combobox of "operations" tab *Correction bug 283840: Skrooge is unable to download units for a past period, but it works for the current day ==> Better warning message *Correction bug 284073: Expressions in splitted operation amount are not calculated *Correction bug 283842: If I add today a past operation in a different unit than the principal, the amount calculated uses the present unit value, and not that of the specific date *Correction bug 284752: Skrooge does not look for Grantlee at build time *Correction bug 284843: OFX import does not work on UTF-8 encoded files *Correction bug 285289: Misspell found: reconciliation instead of reconciliation *Correction bug 285880: Cumulative amount of main categories do not appear *Correction bug 286538: Renaming categories doesn't rename relevant search/process rules (not fixed but better error management) *Correction: Better colors in "5 main variations" widget for dashboard *Correction: Installation of grantlee_skroogefilters.so in only one place and not hardcoded *Correction: No more error when importing skg file with payees *Correction: Addition of an error message on loading of a document generated by a most recent version (example loading on a 1.0.0 document into Skrooge 0.9.1) *Correction: Addition of missing date for x axis on unit graph *Correction: Import homebank set types of accounts *Correction: Possibility to remove an empty account without warning *Correction: Better performances during migration *Correction: Change language of the document on open if the language of KDE has changed *Correction: Better affectation of payees and comments during gnucash import *Correction: Better default size of the "category" panel in the split table to avoid manual resizing *Correction: Better performances during import of multi files by applying "search & process" rules only at the end *New feature: New type of account "Loan", transfer from/to this kind of account are considered as "expenditure" in reports *New feature: New function to remove useless values in unit (the curve is preserved but useless values are removed) *New feature: New tag for grantlee to be able to use standard palette. Colors of the "Default" template are coming from the KDE palette now. *New feature: Possibility to download values of units with regular expressions. *New feature: Possibility to create/modify monthy template directly from skrooge *New feature: More detail in progress bar *New feature: New advice to find and repair operations in groups with only one operation *New feature: New advice to find potential monthly schedule -- Stephane MANKOWSKI Mon, 21 Nov 2011 15:31:03 +0100 skrooge (1.0.0) *Correction bug 280362: Skrooge is not accepting dots or commas *Correction bug 280897: Unit wont set on mt940 import when there are existing operations *Correction bug 280915: The import logic should use an existing account based on bank name read from :20: and the account number read from :25: *Correction bug 282983: Multi-currency transactions not anchored to the exchange rate of the day of the transaction *Correction bug 283246: Some text is hard to read with dark color theme *Correction: Better performances in check box of operation, account, tracker pages *Correction: Grisbi import with split operations does not work *Correction: Capability to open read only file *Correction: Completion on substring available only in "Dropdown list" mode *Correction: To improve performances, the refresh of advices is done when dashboard is activated and only if refresh is needed *Correction: To improve performances, the refresh of models is done for views in current page *Correction: To improve performances, the refresh of graphs is done for graphs in current page *Correction: "Delete unused" of category page tries a delete only on categories really not used *Correction: Various correction in bookmarks (enable/disable in menu, bad management in parent/child relation) *Correction: Cascading delete in categories and bookmarks *New feature: New function to bookmark all opened page in one click *New feature: Addition of "Once a week" for scheduled operations *New feature: Merge of trackers by drag & drop *New feature: "Count" mode in reports *New feature: In table views, when at least one scroll bar is visible, the corner widget allows to display the contextual menu of the header *New feature: Graphs can be corrected by an indice defined in unit page *New feature: Less icon in tool bar by default. New "Fast edit" button in operation editor. *New feature: Add copy menu in contextual menu for cells *New feature: Advice for closed account with amount<>0 *New feature: "Autostart" and "Remove autostart" available in multi selection *New feature: In krunner, A string like "buy 10 ESSO" allows to quickly prepare add a new operation in skrooge. Advices in dashboard allows to finalize the creation *New feature: "Auto start" bookmarks are not opened if the key SHIFT is maintained pressed during the start of Skrooge *New feature: Grouping function in all tables *New feature: New menu to lock/unlock docks *New feature: Allow menu hiding *New feature: "Monthly report" widgets for the dashboard can be set on current month -- Stephane MANKOWSKI Mon, 03 Oct 2011 14:21:04 +0200 skrooge (0.9.1) *Correction bug 271294: Modifying transactions with spaces in their amounts causes them to be reset to 0 *Correction bug 275963: Operations imported in the primary currency unit, not the account unit *Correction bug 278004: Skrooge installs library files with wrong version numbers *Correction bug 278220: Wrong values in Monthly Report "Amounts in accounts" *Correction bug 279421: General reports problems/request *Correction bug 275956: Possibility to display different accounts in different currency units *Correction: Change icons and remove useless icons *Correction: Hide useless "Add" and "Update" buttons in amortization table of "Simulation" page *Correction: No display of initial value (0000) in graph when history mode is not selected *Correction: Now the forecast period is equal to the real period *Correction: Addition of "Add category" in contextual menu of "Category" page *Correction: Differentiation between "Sum" and "Pourcent" mode of PIE graphs *Correction: When "operation" page is an a particular account, the editor is cleaned with this account *Correction: Remove password when anonymize a document *Correction: Better performance by simulating "materialized views" with tables (used for categories and budgets) *Correction: In CSV import, if 2 "amount" columns are found then the first one is considered as a debit and the second one as a credit *Correction: During import, in case of doubt, MM_DD is preferred than DD_MM *Correction: Imports are supporting dates with backslashes like 29\06\2011 *Correction: Better performances *Correction: Saving the state of 'Categories Page' ignores the resized columns with (Auto resize: off) *Correction: CSV import supports multiline file like: date;amount;comment;type "1/1/2010";1000;"transfer 1 from account A to account B";TRANSFER "2/2/2010";2000;"transfer 2 from account A to account C";TRANSFER *Correction: Protection again empty date ==> if the user remove date string then the current date is used *Correction: Toolbar is realy configurable *Correction: Import of encrypted skrooge files *Correction: Better print when many pages are open *Correction: Missing gsb format in import file selection panel *New feature: Graphs in "Bank and Account" page *New feature: Addition of "Jump to the operation" in contextual menu of "Scheduled operations" page *New feature: Properties can be a copy of a file or a link to an existing file *New feature: In all pages, possibility to hide line by using operator - in "Search" field *New feature: Addition of "Highlight" status on catagories and payees *New feature: In reports, the first columns is resizable and has a tooltip for a better display *New feature: "Expand all" and "Collapse all" in category and bookmark trees *New feature: An option to set a frequency of units download (once a day/week/month) *New feature: Properties associated by drag & drop can be copied or linked (ALT+) *New feature: CTRL+C enables copying of selected cells of tables -- Stephane MANKOWSKI Mon, 15 Aug 2011 18:53:00 +0200 skrooge (0.9.0) *Correction bug 273692: Weird overlay in each dashboard widget *Correction bug 268933: Category editing is tedious and confusing *Correction bug 270207: Cannot enter positive value (credit) for operations in my account *Correction bug 270450: Add a wallet as a distinct account *Correction bug 271529: Add a search box to the Categories tab *Correction bug 271708: Wrong account transfer merging when importing QIF files *Correction bug 272863: Allow to choose which wallet store password in *Correction bug 274777: inconsistency between amounts&nb of operations displayed by modules Tiers, Categories and Operations *Correction bug 274993: Generation of forecast in a report depends on the option "auto-write" *Correction: Error during QIF import if unit is already existing *Correction: Open property file with space *Correction: Selected items always on top in graphs *Correction: Symbol in legend when "Point" is the selected mode of graph *Correction: Skrooge crashes on change of icon for folders in bookmark dock *Correction: Show progress bar only when needed *Correction: Bad values when customized dates are changed in reports *Correction: Better draw of pies in graphs *Correction: "Antialiasing" option in settings panel + automatic refresh of graphs after settings modification *Correction: "find and group transfer" does not work if 2 operations with same date and same amount *Correction: Homebank import supports budgets and scheduled operation *Correction: Change "Context" by "Pages" *Correction: Better computation of forecast period for "Scheduled" and "Budget" modes *Correction: Better performance in advice for scheduled operations *Correction: Better proportion in reports in BUBBLE mode *New feature: Formula is autorized in splitted amount (must start by =) *New feature: Ensure visibility of selected items in graph *New feature: New logo with rupee symbol *New feature: New button to reset internal filter in reports *New feature: Settings for "Reports" to select colors *New feature: Information zone in budget to display budgeted amount based on selected budgets *New feature: New contextual action "New category" on categories *New feature: Balance and balance of the day in account widgets (table, dashboard) *New feature: Merge of an imported operation with a manual operation is now possible even if amounts are different *New feature: Button on "Scheduled operations" to jump to the operation template for edition *New feature: New advice to align scheduled operation amount with the last amount inserted *New feature: New kind of graph "stack of columns" *New feature: New kind of graph (values in % of columns) *New feature: New setting to select the color of outline in reports *New feature: New setting to enable/disable balances computation -- Stephane MANKOWSKI Mon, 06 Jun 2011 14:00:49 +0200 skrooge (0.8.1) *Correction bug 259417: Units: graph unreadable when values are missing for some dates ==> "Show origin" allows to show/hide the origin of the graph for a better display *Correction bug 259416: Report: showing limits, average and tendency does not work in mode "Sum" *Correction bug 259414: Feature request: select previously selected tab when closing a tab *Correction bug 259412: Date chooser field: keyboard shortcuts do not work *Correction bug 257324: No icon for Skrooge in the Gnome menu *Correction bug 261001: Better integration on OS X *Correction bug 261318: Amount field calculator gives too many digits *Correction bug 262511: Wrong widget placement in properties subwindow *Correction bug 263263: Separate field for income and expenditure when importing CSV files *Correction bug 263716: Conversion multiple records to "transfer" type cause all have the same ammount *Correction bug 266703: Window exceeds screen size on Netbook *Correction bug 267442: Support for mt940 files *Correction bug 267773: Skrooge always start maximized *Correction bug 267996: Export CSV not unique identifier *Correction: Bad fullname and cumulative amount on categories after a drag and drop *Correction: Import csv with "sign" column before "amount" *Correction: All items are in "Skrooge" category in "Configure shortcuts" *Correction: No symbol for curves of indexes *Correction: "XX is an old tracker" advice does not give the right result when after 31 december *Correction: GNUCash import creates sometimes "wrong" splits with values close to 0 *Correction: Better initial state for the window (docks, maximized) *Correction: Bookmark of report *Correction: Many small correction in "interest" computation (tooltips, items in combo box, computation, ...) *Correction: Bad unit in "unit" page for units having a unit reference *Correction: Better tooltips in report (usefull for huge report where lines title is not visible) *Correction: Better management of codex for exports *Correction: Bad value when update of transfer is done from target account of the transfer *New feature: Import csv with ' as cote (for "Money Manager Ex" exports) *New feature: Import csv is now possible on file without header *New feature: Import csv supports more than one "category" column *New feature: Import "Money Manager EX" file *New feature: Change icon on bookmarks *New feature: "Open potential duplicates" *New feature: Massive update on budgets *New feature: Delete is more secured. *New feature: Force mode on delete. *New feature: Possibility to change the account of operations even when only operations of one account are displayed (useful for credit card) *New feature: New "Search & Process" panel *New feature: Property dock is able to display all properties *New feature: New "About" panel using KDE 4.6 features *New feature: Better mass update on "account", "payee", "tracker" and "unit" pages *New feature: Possibility to merge accounts by using massive update on name of accounts *New feature: Possibility to merge payees by using massive update on name of payees *New feature: Possibility to merge units by using massive update on name of units *New feature: Possibility to merge categories by using massive update on name of categories *New feature: Timeline in reports *New feature: Some fields are directly editable in view *New feature: Completion for all fields with operators (=upper and =lower for the moment) *New feature: New "stacked area" graph type *New feature: New "bubble" graph type *New feature: New button in "operation" page to freeze/unfreeze all fields with text *New feature: Better zoom widget in contextual menu of reports -- Stephane MANKOWSKI Mon, 14 Mar 2011 15:40:34 +0100 skrooge (0.8.0) *Correction bug 250350: Impossible to update an operation of a closed tracker *Correction bug 250403: Dashboard "Shares & Indexes" shows wrong percentaged variation *Correction bug 250677: Bank account choice do not use the country information *Correction bug 246973: Dashboard should be more customizable *Correction bug 251465: Wrong totals with second currency accounts *Correction bug 252869: Skrooge doesn't import KMM bank names correctly *Correction bug 255133: Skrooge crash on ofx import (With wrong data format. Bug in libOFX) *Correction bug 221207: Budget and forecast for accounts *Correction bug 256214: Skrooge after full screen mode I have only StartPage *Correction bug 257322: Installation doesn't depend from "QCA OSSL plugin for libqca2" (libqca2-plugin-ossl is not optional) *Correction bug 258307: comment from main split operation duplicated *Correction bug 258621: Skrooge crashes when KDE session is closed *Correction: Better dashboard display by using a flow layout *Correction: Bookmark of report having "Columns: -- Nothing --" *Correction: Support of "!type:prices" for QIF import and export *Correction: Better export KMyMoney *Correction: Better detection of double during imports *Correction: Protection again sql injection *Correction: Graph redraw even on hidden tabs *Correction: Crash on selection of a cell when graph is hidden in reports *Correction: Refresh of page in case of reset default state *Correction: Boorkmark of "operation" page in template mode *Correction: In report, hide columns or lines without property *New feature: On reports, average and limits are available if "All values in positive" is used too *New feature: Download of all additional information as properties for units (new setting to activate this mode) *New feature: Skrooge is able to store passwords in KDE wallet (new setting to activate this mode) *New feature: Scheduled operations with warning are in bold in dashboard *New feature: Show/hide properties as a column in tables *New feature: Auto point of imported operations to obtain the expected final balance *New feature: Reorder widget in dashboard by drag & drop *New feature: Floating panel in dashboard to remove or zoom widgets *New feature: Accounts can be merged by drag & drop (useful after an import) *New feature: Units can be merged by drag & drop (useful after an import) *New feature: Automatic import of csv columns as properties based on regular expression *New feature: New report capabilities *New feature: Sub total in reports *New feature: New advice for bank without account *New feature: New icons for banks of South Africa (Thank you David) *New feature: OR operator (+) supported in string filters *New feature: New contextual menu in html pages for export as pdf, odt, image and copy *New feature: Possible to show/hide items in context list *New feature: Forecast based on budgets *New feature: Multi selection in report table and graph *New feature: New "show" button *New feature: Better performances on dashboard *New feature: Better performances on tracker page *New feature: New function on operation: "Merge sub operations" *New feature: New advice for budgets *New feature: Possibility to dismiss kind of advices *New feature: Hide tendency line column -- Stephane MANKOWSKI Mon, 06 Dec 2010 15:31:21 +0100 skrooge (0.7.3) *Correction bug 245847: skrooge 0.7.3: 'pow10' was not declared in this scope *Correction bug 213786: Account balance is not displayed *Correction bug 246178: Dashboard "Income & Expenses" widget counting transfers *Correction bug 246408: Skrooge prints hardcopy *Correction bug 246977: In reports from selected data the date selection should be all data *Correction bug 247445: Linker error in skgbasemodelertest/skgtestnodes.cpp *Correction bug 246976: 'Select all' option in menu edit *Correction bug 249955: No password request after hardware crash *Correction: Better management of print option (collate, nb copy, from, to, last page first) *Correction: Do not authorize creation of operation with "-------" *Correction: In report, capability to create history with of property *Correction: In report, do not allow hiding table or graph by resizing *Correction: Import QIF with !Account section containing more than one account and with D attribute (Thank you Dennis) *New feature: Properties with a 'http' or 'file' url as value can be opened from skrooge (useful to set bank site on an account) *New feature: New daskboard component to know money per bank *New feature: Bookmarks in a menu *New feature: Context in a toolbar (useful for small screen) *New feature: Advice widget in dashboard *New feature: Full screen (useful for small screen) *New feature: Addition of file as property by drag and drop *New feature: Tables are scrolled "bottom" or "top" based on sort order -- Stephane MANKOWSKI Sun, 05 Sep 2010 07:36:37 +0200 skrooge (0.7.2) *Correction bug 237498: Skrooge crash on saving preferences (when system tray is not available) *Correction bug 238087: kAmountEdit widget in Operations plugin isn't properly localized *Correction bug 238477: Cannot open base if partition is full *Correction bug 240600: Skrooge must be able to export as a sqlite database *Correction bug 240640: Skrooge crash after password entered - input file DELETED at failure *Correction bug 234597: Gnucash import issues with two accounts with the same name *Correction bug 240601: Skrooge must be able to export an anonymized file *Correction bug 243738: Import of Gnucash file that has CNY currency fails *Correction bug 245254: Category creation failed on first level *Correction: Drag & drop of categories *Correction: Import skg file with tracker or interest simulation *Correction: No XML export of tables sqlite_sequence and sqlite_stat1 *Correction: Layouts compatible with small screen (1024x768) *Correction: Open right page when click on "Estimated interest" in dashboard applet *New feature: Zoom on tables by using CTRL+wheel *New feature: Search button is "Search & process" page to be able to search operations without creating a rule *New feature: Click on a column in "Search & process" editor removes the column *New feature: CTRL+F opens the "Search & process" panel with a condition based on current selection *New feature: Stability of colors in graph *New feature: Selection of colors in graph (saved and restored) *New feature: "Add property" to add easily properties *New feature: skg files saved on a FTP server can be loaded directly from Skrooge. Save is not possible. *New feature: New "Payee" page *New feature: Merge of payees by drag & drop *New feature: Report plugin is able to display graph based on properties *New feature: Possibility to select icon file for a bank *New feature: Any kind of file as property *New feature: Go home -- Stephane MANKOWSKI Sat, 24 Jul 2010 10:00:00 +0200 skrooge (0.7.1) *Correction bug 233895: Switching account from 1 type to another with multi-sel is modifying the initial balance *Correction bug 233930: QIF address field is not imported *Correction bug 234595: Better handling of category deletion ==> Reparent operations on parent category when a category is removed *Correction bug 234608: Some gnucash notes are lost during import phase *Correction bug 234771: Problem importing ofx file *Correction bug 234845: Bad Account after importing ofx *Correction bug 235689: Cannot localize quotation marks *Correction bug 234596: Customisable date format in operations panel *Correction bug 236753: Search but not process on attribute *Correction: Rename "Import CSV unit..." in "Import currency values..." *Correction: Better management of constraints in data model *Correction: Conflict on categories when importing twice a gnc document *Correction: Conflict on categories when importing twice a xhb document *Correction: Conflict on categories when importing twice a gsb document *Correction: Conflict during property update *Correction: Modification of trackers in splitter operations from "Search and process" *Correction: Transfer when signe + is specified before the value *Correction: Various modifications on "Search & Process" *Correction: Replacement of [%1] by '%1' for localization *Correction: No more direct sqlite prerequisit *New feature: Better performances on "File/New" (first Save is longer) *New feature: Usage of KNewPasswordDialog and KPasswordDialog for password protection *New feature: Applet for dashboard to display tip of the day *New feature: CSV import of splits and transfers *New feature: CSV import is able to oncatenate many attributes in "comment" or "payee" *New feature: CSV import is to import unknown attributes as properties *New feature: Export XML *New feature: "Search & Process" is able to search on properties values *New feature: "Search & Process" is able to update, insert and delete properties (prop='' to delete it) *New feature: All currencies are now available in unit page (Thanks to KCurrencyCode) *New feature: Properties are displayed on tooltips *New feature: Open property picture associated to an object -- Stephane MANKOWSKI Mon, 10 May 2010 15:21:33 +0200 skrooge (0.7.0) *Correction bug 225855: Monthly Report: difference in percentage is wrong *Correction bug 225980: Share download not in primary currency : value not correct *Correction bug 226451: .skg file incorrectly identified as password-protected ==> New document icon to identify encrypted files *Correction bug 228808: Gnucash import: scheduled operation at 0 euros *Correction bug 228904: Gnucash import should not take into account recurrent operation containing loan formulas *Correction bug 230175: Sometimes report plugin resizes Skrooge over the screen size *Correction bug 232937: Search & Process : if a category has no operation yet, it doesn't appear in the possible categories for "update" *Correction wis 228901: Manage Liability accounts (loan, etc...) *Correction wis 197409: There's no way to define initial balance of non-OFX account *Correction wis 224933: A legend in the graphics *Correction wis 224042: Possibility to store several CSV import schemes ==> Implemented by regular expression in automatic mapping. *Correction wis 225818: Change skrooge desktop file description *Correction wis 226963: Export CSV of split operations *Correction wis 228170: Skrooge must be able to import skg file *Correction of forecast computation on scheduled operations *Correction of export of "Initial balance" for QIF and CSV *Correction of import of "Initial balance" for QIF and CSV *Correction of import of "Initial balance" for OFX *Correction: Better error message in case of repository of document is RO *Correction on confirmation message *Correction selection on graph (histogram and stack) *New feature: "Weighted moving average" in reports *New feature: Specific font and color for disabled schedule (nbtime = 0) *New feature: Addition of "First date" and "Last date" in trackers *New feature: Amortization table *New feature: import gnucash document *New feature: import kmymoney document *New feature: import skrooge document (can be used to merge documents) *New feature: import grisbi document *New feature: import homebank document *New feature: export kmymoney document *New feature: export skrooge document (without history) *New feature: possible to create a bookmark even when a bookmark is already selected *New feature: button to clean history (reduce the size of the file) *New feature: all settings in only one file. WARNING: check your settings. *New feature: generic modeler is ready to be used by other applications. *New feature: "5 main variations" in monthly report. *New feature: New key accelerator to enable editor of current page -- Stephane MANKOWSKI Mon, 05 Apr 2010 20:00:00 +0200 skrooge (0.6.0) *Correction wis 215668: Dashboard is too big to be a real dashboard *Correction bug 216520: Incorrect import of a transfer when inside a splitted operation (a splitted operation can not be a transfer) *Correction bug 217896: UK Currency and Date formats displayed for Scheduled Transactions in Dashboard *Correction bug 217743: Configuring Banks and Accounts Section (Bank name update issue) *Correction bug 219791: No progress bar during export QIF *Correction bug 219750: Delete key in split table remove the full line *Correction bug 214508: Provide a balance evolution graph *Correction wis 220659: Option to "Apply rules on to imported operations" automatically after an import *Correction cra 220936: Crash on double click on bookmarks *Correction cra 222340: Freezes after some time *Correction bug 222339: CVS import: problem with decimal point and comma *Correction wis 220663: Selected operations must still visible after a sort modification *Correction wis 223848: make libofx an optional dependency *Correction wis 224676: keyboard shortcut for modify and add operation *Correction wis 209314: key scrolling does not work, and click on a line has odd behaviour *Correction bug 224932: Graphic Report SVG export bug + wish on a legend *Possible to define favorite account *Possible to define favorite search *New gui for search and process *Delete icon on each suboperation line *Compatibility with bespin theme *Do not collapse tree after editing one of its items *More options in "Report" page *New function to delete unused categories *Selection in pages list is now synchronized with current page *New option in contextual menu of report to hide bottom tool bar *Edition of splitted operations by single click *Clean button in operation table is renamed "Clear form" *Better export QIF for investment *Better display on reports (average, min and max are displayed if only one curve is drawn) *Better display on reports (linear regression is displayed if only one curve is drawn) *Option to show/hide average and limits in graph *Selection of year for interets computation *Correction in sum of operation computation when suboperations are selected *Graph proportion adapted to view size *"Account position" is replaced by "Ending balance" *Avoid cpu loop when double click on operation fields on KDE 4.4 *Completion based on substrings *Better import of QIF files when both side of the import do not have exactly the same date *Correction on update category and tracker for transfers *Completion on comments -- Stephane MANKOWSKI Sat, 06 Feb 2010 09:00:00 +0100 skrooge (0.5.5) *Disable some functions on sub-operations *Correction on update of a splitted operation by a split returned by Fast Edition *Correction bug 215995: SQL Error when attempting to display a report in history mode *Correction bug 215754: Invest account is incorrectly imported as current account *Correction bug 214809: Incorrect import of a transfer *Correction bug 214851: Incorrect import of 'investment account' QIF file *Correction bug 216348: Account filters synchronisation problem in the operation HMI *Correction bug 216522: Incorrect import of transfer (twice imported) *Correction bug 216551: Click in blank area make 'date' widget to extend its size *Correction bug 216514: Incorrect import of QIF file when account names are similar *Correction bug 216520: Incorrect import of a transfer when inside a splitted operation (transfer of suboperation is not supported) *Correction crash 215992: Skrooge crash when clicking on "rapport" menu *Correction regression on "Open report" (P0) *Invertion of colors in report (more saturation for new operations) -- Stephane MANKOWSKI Sun, 29 Nov 2009 15:00:00 +0100 skrooge (0.5.4) *Correction bug 211579: Irregular percent value in monthly report amounts in accounts *Correction bug 212313: Can not update a lot of operations *Correction bug 211029: Skrooge should have an better account evolution graphic *Correction bug 213783: Account balance computation is inaccurate *Correction bug 214045: Import CSV date format problem *Correction bug 214097: Can not search on multiple criteria. (OR is not supported) *Correction bug 214157: Impossible to import an ofx file from a directory containing special characters *Correction bug 214462: Does not use primary currency unit when importing, and does not warn on import of splitted operation with multiple currencies *Correction bug 214447: Auto save the open document at closing *Correction bug 214794: Incorrect report of warning when importing QIF file *Correction bug 214809: Incorrect import of a transfer *Correction bug 214849: CSV export from grid is not correct *Correction bug 214904: Can not download a stock/share quotation *Correction bug 214851: Incorrect import of 'investment account' QIF file *Correction bug 215114: Button to customize dashboard *Correction bug 215620: Can not import QIF file with skrooge version SVN 1052520 *Correction bug 215656: History reports are wrong if first date is not selected *Correction bug 215658: Some editors are modified when a bookmark is created *Correction on double click of a template of transfer *Update of both side of a transfer is now possible *Only one fast edition. User is be able to lock some fields before. *Interest can be computed with 360 and 365/366 days method *New splashscreen size *New graph gui with "Quarters", "Semesters", "Weeks" and "Days" *Zoom with CTRL+wheel on graph *Dashboard is automatically launched when a new document is created *Use of graph component in unit page *New button to clean editor in operations page *Correction on scheduled transfers *New report widget for dashboard (all reports can be added to dashboard) *Addition of "Del" shortcut to delete suboperations in split panel *New tooltip on operation status *New function "Open sub operations" to display all sub operations. Useful to filter them. *New function to reconciliate an account from bank page *Better QIF import/export of account type -- Stephane MANKOWSKI Mon, 23 Nov 2009 14:00:00 +0100 skrooge (0.5.3) *Correction bug 209451: Display quantity of each unit *Correction bug 209457: Number of decimal for each unit must be different *Correction bug 209453: Impot must avoid double even when operations are not validated *Correction bug 209529: Unable to reconcile. Delta zero but "Validate checked operations" tick still grayed out *Correction bug 208939: "Search and process" does not work anymore *Correction bug 209610: Skrooge doesn not work if "Maximum undo depth" is 0 *Correction bug 209542: Automatic conversion of schedule *Correction bug 209672: Amount of indexes must be displayed without unit symbol *Correction bug 209705: Importing investments from csv doesn't import the dollar amount of shares purchased *Correction bug 209705: Find&Group is now able to detect share purchase/sale *Correction bug 209702: Need the ability to be able to manually group transfers *Correction bug 209914: executable bits set for skgalarmboardwidget.h *Correction bug 209905: currency import is broken *Correction bug 209912: rpmlint warnings after installation: unused-direct-shlib-dependency *Correction bug 209053: Savings account support request *Correction bug 210946: columns are always resized to content when opening operations tab *Save icon in tab bar to overwrite bookmarks or current context *New dashboard plugin for highlighted operations *New dashboard plugin for estimated interests *"Backward" & "Forward" navigation on pages *New unit "Swedish krona" *New command to open operations modified during last user action (useful after an import or a processing) -- Stephane MANKOWSKI Mon, 19 Oct 2009 12:00:00 +0200 skrooge (0.5.2) *Correction bug 207927: skrooge summary field uncomplete bank account name *Correction bug 208130: 'Import CSV Unit' menu puts data into the 'Operations' tab *Correction bug 208210: Multiple clause research/update in french *Correction bug 208194: Skrooge loose all data on file migration *Correction bug 208040: Error when entering shares information *Correction bug 208459: Skrooge crashes when changing tab *Correction bug 209317: key scrolling very long *Better performance in report by using QTimer *Better performance bank page by using QTimer *New SKGDateEdit with more features *Delivery of default categories and bookmarks for pt. Thanks Caio de Campos Ferreira. *Better performance when opening pages *Correction on monthly report for a better display with dark theme *New widget on dashboard for alarms *SKGDateEdit validation on lose focus *Automatic detection of CSV separator during import -- Stephane MANKOWSKI Sun, 04 Oct 2009 16:00:00 +0200 skrooge (0.5.1) *Correction bug 205471: Impossible to know how to check an operation *Correction bug 205466: Date missing in reports *Correction bug 193426: Ability to create from scratch a "recurrent operation" *Correction bug 203787: Balance should be displayed for each operation in the operation context. Correction done: balance is displayed on tooltip because it is better for performances than in column. *Correction bug 206894: Crash second import CSV *Correction bug 206459: "Search & process" must be able to do complex update *Correction bug 199539: Dashboard must be customisable *Correction bug 207249: rpmlint error: debuginfo-without-sources (including a fix) *Correction bug 207246: rpmlint warning shared-lib-calls-exit /usr/lib64/libskgbasemodeler.so.0.5.0 exit@GLIBC_2.2.5 (for fedora packaging) *Correction bug 207232: skrooge must use kfilterproxysearchline *Correction bug 207274: "Fast edit" behavior must be modifiable by settings *Correction bug 191966: Treeviews collapsed when updated *Correction bug 207672: Align amount values on the right for cleaner views *Correction bug 207713: Display some information after applying a process *Correction bug 200790: Search creation doesn't take not validate predicate *New function to create template of operations *New function to schedule templates *New function to create template from existing operations *Double click on template creates the operation *New shortcut for import *Attribute to define the maximum number of occurrence for scheduled operations *Attribute to define the last occurrence date *A setting allows to create automatically a template when an operation is scheduled *Selection is done on duplicated object after a "Duplicate" *Selection is done on created template after a "Create template" *Correction message in history when an object is deleted *Download button always visible on unit plugin *Infinite symbol when number of occurrences is not checked *Addition of "Custom..." on graph dates *New spash screen *New function to overwrite bookmarks after a modification *Filter capability on "search & process" table *Editors are no more clean when the selection is empty. A second click is needed -- Stephane MANKOWSKI Sun, 20 Sep 2009 00:00:00 +0200 skrooge (0.5.0) *Correction bug 201316: import qif extra tag support *Correction bug 201157: Graphs no longer displayed *Correction bug 201289: Compiler error on GCC 4.1.2 (enum with KDE_EXPORT or similar macro) *Correction bug 201451: qif import other liability account type *Correction bug 201697: Pointed operation can be deleted *Correction bug 201800: Filter issue with number formatting *Correction bug 202167: Payee list should be ordered in a case insensitive way *Correction bug 202341: Combobox lists are not sorted in a locale-aware way *Correction bug 202384: Skrooge always wants to update report bookmarks on exit *Correction bug 202636: Hidden columns are sometimes displayed *Correction bug 203894: Skrooge crashes when launched with --nofork *Correction activation of "Values" button in "Units" page *New password is no more displayed when changed *New function to merge an imported operation with an operation manually entered *New refund icon *New widget for dashboard "Shares and indexes" *Modification titles on "operation" pages to differentiate "operations" and "sub operations" *"sub operations" can be identified by a specific font and color (see settings) *Correction bug: Created search is not selected after creation *Correction performance issue on zoom when antialiasing is enable *Addition of 5 main categories of expenditure in monthly reports *Rename "Bookmark" for operation by "Highlight" *Rename "Trait" by "Process" *Set minimum size for amounts fields *Only one "Import..." command for all supported format. *Support of QFX import *New settings for import CSV *Dashboard is now clickable *Dashboard is modifiable (see contextual menu on each widget) and state can be saved *Addition of "Fast edition" without amount modification. It is useful to modify imported operations *Only one instance of skrooge can be launched. *Imports can be done in command line. Example "skrooge myfile.ofx" with skrooge running *If an invalid expression is entered for the amount, the field is written in red, and the expression is kept to allow correction from the user (previously, 0 was displayed). *"New tab" is now managed by Ctrl+Shift+T *Popup to ask if you want to save current state of pages. *Bookmark storage is no more dependant of language translation -- Stephane MANKOWSKI Fri, 28 Aug 2009 21:00:00 +0200 skrooge (0.3.0) *Correction bug 191970: Attach receipts (images) to transactions *Addition of "Trackers" for lines in report *Correction bug 195254: Do not allow to link operations to a closed tracker *Correction bug 195026: A setting must allow to clean history when the application is [saved] *Correction bug 194826: End user must be to validate an import *Correction bug 196444: Can not validate reconciliation (delta = -0.00) *Correction bug 196745: Categories are not added to a split operation *Correction bug 197287: Add backup file management *Correction bug 197288: Manage working document copy *Correction bug 197835: Reconciliation mode : currency problem *Correction bug 198077: Comments do not appear in every Operations windows *Correction bug 198583: foreign currency accounts should display in their native currency in dashboard. *Correction bug 198786: Categories are not easily read *Correction bug 199196: build error in skgbankgui *Correction bug 199543: Splash screen display must be defined by settings *After an import, skrooge is able to execute some rules to clean imported operations *Correction in dashboard *Addition of "Reset state" on tabs to reset default state of plugins *Most important category and refund are displayed for split operations *Display detail of selected operations in information area of "operation" page *Main tab position can be selected in settings -- Stephane MANKOWSKI Sat, 11 Jul 2009 16:00:00 +0200 skrooge (0.2.9) *Correction bug 2787146: Single click on bookmarks *Correction bug 191972: Selection is not well kept *Correction bug 191975: Add Labels for X values in histograms and stack reports *Correction bug 191968: Information string on top of page is too long (no word wrap) *Correction bug 192687: fonts too large on report *Correction bug 192832: Do not take amount sign into account for transfers *Correction bug 191967: Skrooge must be able to display an history global and for each account. *Correction bug 193423: context list scrolls down only after 8 mouse molette rotation *Correction bug 193431: "recurrent operation" trigger can not be set on D day *Correction bug 193421: bugs to be registered via bug database (not email) *Correction bug 194650: Fast edit must work on Tracker attribute *Correction bug 194765: Number of operations by account must be displayed in bank plugin *Addition of bank icon on account combo boxes *Addition of Canadian dollar *Addition of one more view (based on selection) in operation plugin *Better managment of dependency between units *New settings on unit download *Skrooge tries to find the most adapted account during import file without account information inside *Update of nls part of data model when language is changed *"Open report" on an element of a report open a new report with exactly same settings than first one *Addition of "scheduled operations" in dashboard plugin *New operation editor *Better management of messages in status bar *New editor in unit plugin *Middle click is able to open new page from context or bookmarks -- Stephane MANKOWSKI Sun, 31 May 2009 23:00:00 +0200 skrooge (0.2.8) *Correction bug 2782399 :Skrooge must create a backup file *Correction bug 2783111 :Typo *Correction bug 2515571 :New plugin to follow refunds *Correction bug 2785938 :Single click *Correction bug 2786183 :Load standard categories? *Correction bug 2682777 :Label are not well placed *Correction bug 2579926 :Improve category plugin performances *Correction bug 2665265 :Edit names directly in treeview *Correction for amount comparison in validation mode *Correction import ofx when bankid is empty in ofx file *Correction mime type for KDE4 *Better performances on open document with password required -- Stephane MANKOWSKI Mon, 04 May 2009 15:00:00 +0200 skrooge (0.2.7) *Correction bug 2513827 :global print function *Correction bug 2692667 :Automatic icon resize is driven by bookmarks only *Correction bug 2714592 :Skrooge must be able to manage properties on all objects *Correction bug 2733958 :Auto hide *Correction bug 2747379 :Only allow to "pre-check" operations *Correction bug 2750464 :Impossible to update the date of a split operation *Correction bug 2778333 :Enhance hidden panels discoverability *Correction bug 2777697 :bookmark modifications are not kept when closing skrooge *Old method to split an operation is no more available *"Undo document" is no more in toolbar by default *Remove useless icons from tool bar *When "Save" is clicked in a new document, automatically interpret it as "Save as" *New menu with shortcuts to open pages *Better automatic linear scale in reports -- Stephane MANKOWSKI Mon, 27 Apr 2009 15:00:00 +0200 skrooge (0.2.6) *Correction bug 2692651 :antialias on graphs *Correction bug 2692665 :Wrong currency label in stock tab *Correction bug 2692605 :Title of a report for a selection contains only ",,,," *Correction bug 2692648 :New dialog box on exit *Correction bug 2692664 :add support for split & grouped operations in fast edition *Correction bug 2692656 :More details on recurrent operations view *Correction bug 2645610 :Bad performance on "find transfers" *Correction bug 2692681 :Reconciliate mode in operations view *Correction bug 2706987 :no report generated *Correction bug 2716734 :Better category & unit creation during operation creation *Correction bug 2720760 :Popup messages are displayed twice -- Stephane MANKOWSKI Sun, 29 Mar 2009 20:00:00 +0200 skrooge (0.2.5) *Correction bug 2664613 :Not possible to show a column without reseting all *Correction bug 2633375 :2 columns for quantity *Correction bug 2674862 :Bad performances on undo/redo for categories *Correction bug 2675606 :Bookmark reorder doesn't work *Correction bug 2685241 :Add postive sign in amount field for positive operations *Correction bug 2685236 :Option to ask again if modified bookmark should be saved *Correction bug 2685270 :Impossible to modify comment on operation and sub operations *Correction bug 2687115 :filter value is not save whan bookmark a report *Correction bug 2685335 :Pie like in firelight *Debug plugin is able to do a "EXPLAIN QUERY PLAN" *Path for bash is not more hardcoded in script for BSD compatibily -- Stephane MANKOWSKI Mon, 16 Mar 2009 14:00:00 +0100 skrooge (0.2.4) *Correction bug 2608762 :Cannot create a bookmark folder if no bookmark exist yet *Correction bug 2633444 :Add "import std categories" in import menu *Correction bug 2638120 :Unable to import cvs or qif from "la banque postale (fr) *Correction bug 2564982 :Creation of default bookmark *Correction bug 2640959 :Impossible to import OFX file -- Stephane MANKOWSKI Mon, 02 Mar 2009 20:00:00 +0100 skrooge (0.2.3) *Correction bug 2608762 :Creation of category branch in one shoot *Correction bug 2608768 :Crash in category creation *Correction bug 2586878 :skrooge crashes if category too deep *Correction bug 2446175 :better split operations *Correction bug :sum column always displays 0 in category page *Addition of comment on suboperations *Better performances to compute next values for operation numbers *Icon in system tray *"export SKGTRACESQL=xxx" is able to trace all sql order taking more than xxx ms -- Stephane MANKOWSKI Mon, 23 Feb 2009 21:51:12 +0100 skrooge (0.2.2) *Correction bug 2556346 :README should list kdesdk-scripts as buildtime dependency *Correction bug 2556406 :Pound sterling missing in currency list, difficult to add *Correction bug 2556340 :CMakeLists should use correct libraries and check for Sqlite (Thank you lindevel for the patch) *Correction bug 2555861 :"make package_source" contains too many files *Correction bug 2557366 :Wrong password *Correction bug 2556381 :Impossible to change negative quantities into positive ones? *Correction bug 2530932 :Better autostart bookmarks management -- Stephane MANKOWSKI Wed, 11 Feb 2009 21:51:12 +0100 skrooge (0.2.1) *Correction bug 2517386 :Move of category is not done *Correction bug 2519334 :Smooth scolling in category view *Correction bug 2517388 :Selection must be set on created operation *Correction bug 2519847 :Duplicate operation *Correction bug 2520516 :Security of skg file *Correction bug 2545021 :compilation fails because po/listFiles.sh is not executable *Correction bug 2549911 :Import standard categories *Correction bug 2526148 :Double click on graph in report *Correction bug 2520530 :KToolBarPopupAction for undo/redo *Addition of icon for stocks *Bookmark can be deleted from contextual menu *Icons are resized relative to list width -- Stephane MANKOWSKI Sun, 01 Feb 2009 20:00:00 +0100 skrooge (0.2.0) *Correction bug 2489823 :automatically remember changes on columns for bookmarks *Correction bug 2487934 :Specific Column for "scheduled" icon *Correction bug 2487866 :deselect lines in table views *Correction bug 2487981 :add a column for total of operations including subcategories *Monthly report is now base on QWebView and contain google graph *Addition of exports in report tables (TXT, CSV) *Report can be opened from bank, operation or unit page *Report can be opened from report *Better graph *Addition of print *Long transaction can be interrupted *Better warning message with history -- Stephane MANKOWSKI Mon, 19 Jan 2009 20:00:00 +0100 skrooge (0.1.9) *Better contextual menu on tables *New feature in monthly report *Better performances for monthy report generation *Better graphics in report plugin *Addition of exports in all tables *Addition of exports in report graphs (PDF, SVG, picture) -- Stephane MANKOWSKI Sat, 03 Jan 2009 20:00:00 +0100 skrooge (0.1.8) *Correction bug 2299303 :Calcul de balance erroné *Correction bug 2446162 :Distinction for future operations *Correction bug 2446184 :Reduce spacing between rows *Correction bug 2256687 :No more icon in application menu *Correction crach in qtdesigner due to SKGTableView *Addition "open bookmarks" to display all bookmarked operations *Addition of sale of stocks *Addition of "split of stock" -- Stephane MANKOWSKI Tue, 22 Dec 2009 20:00:00 +0100 skrooge (0.1.7) *Correction bug 2385144 :Unexpected display of unit in operations list *Correction bug 2390926 :Problem with Action/$/€ *Correction bug 2390747 :Operations transferred not found *Correction bug 2384834 :Multi-Selection and Ctrl-P does not work *Correction bug 2311993 :Difference between operation and valuation not done *Correction bug 2385059 :Possibility to remove (modify) buggy unit value *Correction bug: Inversion year(s) and day(s) in recurrent operations *Correction wrong mapping on all table when column are reordered *Correction request 2379612: categories edition & creation *Selection is now kept after modifications *New "Monthly report" plugin *Computation of RIB key *Addition contextual menu on all table *Addition contextual menu on tabs *Addition of short date in tooltip on fancy date *Addition of smooth scrolling in table views *Addition of indexes (CAC 40, NASDAQ, ...) in data model -- Stephane MANKOWSKI Tue, 15 Dec 2009 20:00:00 +0100 skrooge (0.1.6) *Support of import unit values in CSV format like: date;value 01/01/2008;50 03/03/2008;60 *Correction bug 2340201 :crash on QIF export *Correction bug in import if 2 operations with same attributes (date, amount, ...) *Correction concerning performance issue in all tables *Correction bug 2299354 :Ability to change and memorize column width *Columns can be switched in "auto resize" mode *Correction bug 2298982 :An account shall have a currency *Preferred table's organisation can be save as default *Better organisation of pies in report plugin -- Stephane MANKOWSKI Tue, 01 Dec 2009 20:00:00 +0100 skrooge (0.1.5) *Correction bug 2307068: Incorrect import of QIF file *Columns order is now saved in bookmark for operation, account, recurrent operations and unit plugins *Sort is now saved in bookmark for operation, account, recurrent operations and unit plugins *Correction bug 2327676: Many import QIF correction *Columns can be show or hidden (see contextual menu) and this state is saved in bookmarks *Better performances (x100) in report. *Correction bug 2316459: skrooge does not support out-of-source build *Correction bug 2298621: Unable to delete a "principal currency". ==> It is now possible to delete a unit except if this unit is already used by at least one operation *Correction bug 2326576: Field empty by default *Correction bug 2306914: "Tip of the day" dialog can not be removed -- Stephane MANKOWSKI Tue, 24 Nov 2009 20:00:00 +0100 skrooge (0.1.4) *New icons *Bookmarks folders are opened sorted and focus is set on the first one *Correction (inversion sign of operations) in transfer creation *Buy stock is now possible *Correction bug 2298773: Unit and amount are not aligned *Correction bug 2298674: Montant en EUR même si unité EUR n'existe pas *Correction bug 2298471: Unable to change type of a unit *Correction bug 2299384: File/Close closes the current page *Correction bug 2299524 *Correction bug 2299519: "Mode" should not be mandatory *Optimisation in open "operation" pages *Correction bug 2299600: Values of a unit are not sorted out *Correction bug 2299947: Unit can not be used if no symbol is defined ==> Symbol is now mandatory *Correction bug 2301337 *Correction bug 2300013:Une ventilation est une catégorie *Correction bug 2278703:Zoom fit when opening report tab *Comment on account *Tab name is now set with the name of the bookmark -- Stephane MANKOWSKI Tue, 17 Nov 2009 20:00:00 +0100 skrooge (0.1.3) *Vertical alignment in all views *Detection of the sign of an operation (during creation) based on category *Average in report *Sum column is now displayed only if more than one column is displayed in report plugin *Correction bug in report when sort is set on last column and the report is refreshed with less columns *Correction abend on delete *Modeler is now able to store messages on transaction. *Display messages by notification *Addition of recurrent operations (creation, update, delete, automatic insertion, warn, ...) *Correction in duplication of recurrent operations: Now, status, bookmark, importation id are not duplicated *Correction on optimisation of undo/redo transaction: optimisation removed :-) *Multi selection is supported by recurrent operations creation icon *Massive update on recurrent operation *Implementation enhancement 2272115 concerning calculator behavior *Correction update of split operations *Set default order in all tables *Sort bookmark by drag and drop -- Stephane MANKOWSKI Sat, 14 Nov 2009 20:00:00 +0100 skrooge (0.1.2) *Support of categories in QIF import *Support of csv file with empty lines *Support of categories in QIF export *Correction bug 2229770: automatic erase of duplicated operations *New design for operation and account plugin *Addition of first draft of recurrent operations -- Stephane MANKOWSKI Tue, 10 Nov 2009 20:00:00 +0100 skrooge (0.1.1) *Optimisation widget refresh after a huge QIF import *Optimisation performance of QIF export *Optimisation performance of CSV export *Optimisation performance of "find and Group transfers" *Addition of "Close", "Close All" and "Close All Other" to manage pages *Correction on download unit *Addition of the name of the file in the title of the man window *Addition of sort in report plugin -- Stephane MANKOWSKI Tue, 03 Nov 2009 20:00:00 +0100 skrooge (0.1.0) * QIF import/export * CSV import/export * Basic graphs * Several tabs to help you organize your work * Infinite undo/redo (even after the file was closed !) * Instant filtering on operations * Infinite levels of categories -- Stephane MANKOWSKI Mon, 02 Nov 2009 20:08:00 +0100 diff --git a/skgbankmodeler/skgruleobject.cpp b/skgbankmodeler/skgruleobject.cpp index 54dde4bfe..1e7f44403 100644 --- a/skgbankmodeler/skgruleobject.cpp +++ b/skgbankmodeler/skgruleobject.cpp @@ -1,861 +1,866 @@ /*************************************************************************** * Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see * ***************************************************************************/ /** @file * This file defines classes SKGRuleObject. * * * @author Stephane MANKOWSKI / Guillaume DE BURE */ #include "skgruleobject.h" #include #include #include "skgdocumentbank.h" #include "skgoperationobject.h" #include "skgtraces.h" #include "skgtransactionmng.h" #include "skgunitvalueobject.h" SKGRuleObject::SKGRuleObject() : SKGRuleObject(nullptr) {} SKGRuleObject::SKGRuleObject(SKGDocument* iDocument, int iID) : SKGObjectBase(iDocument, QStringLiteral("v_rule"), iID) {} SKGRuleObject::SKGRuleObject(const SKGRuleObject& iObject) = default; SKGRuleObject::SKGRuleObject(const SKGObjectBase& iObject) { if (iObject.getRealTable() == QStringLiteral("rule")) { copyFrom(iObject); } else { *this = SKGObjectBase(iObject.getDocument(), QStringLiteral("v_rule"), iObject.getID()); } } SKGRuleObject& SKGRuleObject::operator= (const SKGObjectBase& iObject) { copyFrom(iObject); return *this; } SKGRuleObject::~SKGRuleObject() = default; QString SKGRuleObject::getDisplayName() const { return getSearchDescription(); } SKGError SKGRuleObject::bookmark(bool iBookmark) { return setAttribute(QStringLiteral("t_bookmarked"), iBookmark ? QStringLiteral("Y") : QStringLiteral("N")); } bool SKGRuleObject::isBookmarked() const { return getAttribute(QStringLiteral("t_bookmarked")) == QStringLiteral("Y"); } SKGError SKGRuleObject::save(bool iInsertOrUpdate, bool iReloadAfterSave) { // Do the save SKGError err = SKGObjectBase::save(iInsertOrUpdate, iReloadAfterSave); // Raise alarm if (!err && getActionType() == ALARM) { err = execute(); } return err; } SKGError SKGRuleObject::setXMLSearchDefinition(const QString& iXml) { setSearchDescription(SKGRuleObject::getDescriptionFromXML(getDocument(), iXml, false, SEARCH)); return setAttribute(QStringLiteral("t_definition"), iXml); } QString SKGRuleObject::getXMLSearchDefinition() const { return getAttribute(QStringLiteral("t_definition")); } SKGError SKGRuleObject::setXMLActionDefinition(const QString& iXml) { setActionDescription(SKGRuleObject::getDescriptionFromXML(getDocument(), iXml, false, getActionType())); return setAttribute(QStringLiteral("t_action_definition"), iXml); } QString SKGRuleObject::getXMLActionDefinition() const { return getAttribute(QStringLiteral("t_action_definition")); } SKGError SKGRuleObject::setSearchDescription(const QString& iDescription) { return setAttribute(QStringLiteral("t_description"), iDescription); } QString SKGRuleObject::getSearchDescription() const { return getAttribute(QStringLiteral("t_description")); } SKGError SKGRuleObject::setActionDescription(const QString& iDescription) { return setAttribute(QStringLiteral("t_action_description"), iDescription); } QString SKGRuleObject::getActionDescription() const { return getAttribute(QStringLiteral("t_action_description")); } SKGError SKGRuleObject::setActionType(SKGRuleObject::ActionType iType) { SKGError err = setAttribute(QStringLiteral("t_action_type"), (iType == SEARCH ? QStringLiteral("S") : (iType == UPDATE ? QStringLiteral("U") : (iType == APPLYTEMPLATE ? QStringLiteral("T") : QStringLiteral("A"))))); return err; } SKGRuleObject::ActionType SKGRuleObject::getActionType() const { QString typeString = getAttribute(QStringLiteral("t_action_type")); return (typeString == QStringLiteral("S") ? SEARCH : (typeString == QStringLiteral("U") ? UPDATE : (typeString == QStringLiteral("T") ? APPLYTEMPLATE : ALARM))); } SKGError SKGRuleObject::setOrder(double iOrder) { SKGError err; double order = iOrder; if (order == -1) { order = 1; SKGStringListList result; err = getDocument()->executeSelectSqliteOrder(QStringLiteral("SELECT max(f_sortorder) from rule"), result); if (!err && result.count() == 2) { order = SKGServices::stringToDouble(result.at(1).at(0)) + 1; } } IFOKDO(err, setAttribute(QStringLiteral("f_sortorder"), SKGServices::doubleToString(order))) return err; } QString SKGRuleObject::getSelectSqlOrder(const QString& iAdditionalCondition) const { QString wc = iAdditionalCondition; QString wc2 = SKGRuleObject::getDescriptionFromXML(getDocument(), getXMLSearchDefinition(), true, SEARCH); if (!wc2.isEmpty()) { if (wc.isEmpty()) { wc = wc2; } else { wc = '(' % wc % ") AND (" % wc2 % ')'; } } if (wc.isEmpty()) { wc = QStringLiteral("1=1"); } wc = "t_template='N' AND d_date!='0000-00-00' AND (" % wc % ')'; return wc; } SKGRuleObject::SKGAlarmInfo SKGRuleObject::getAlarmInfo() const { SKGTRACEINFUNC(10); SKGRuleObject::SKGAlarmInfo alarm; alarm.Raised = false; alarm.Message = QLatin1String(""); alarm.Amount = 0.0; alarm.Limit = 0.0; if (getActionType() == SKGRuleObject::ALARM) { // Alarm mode QString wc = getSelectSqlOrder(); if (wc.isEmpty()) { wc = QStringLiteral("1=1"); } SKGDocument* doc = getDocument(); QStringList list = SKGRuleObject::getFromXML(doc, getXMLActionDefinition(), true, ALARM, false); if (!list.isEmpty()) { QString sql = list.at(0); sql.replace(QStringLiteral("#WC#"), wc); SKGStringListList result; doc->executeSelectSqliteOrder(sql, result); if (result.count() == 2) { const auto& r = result.at(1); alarm.Raised = (r.at(0) == QStringLiteral("1")); alarm.Message = r.at(3); alarm.Amount = SKGServices::stringToDouble(r.at(1)); alarm.Limit = SKGServices::stringToDouble(r.at(2)); } } } return alarm; } SKGError SKGRuleObject::execute(ProcessMode iMode) { SKGError err; SKGTRACEINFUNCRC(10, err); if (getActionType() == SKGRuleObject::UPDATE) { // Update mode QString addSql; if (iMode == IMPORTED) { addSql = QStringLiteral("t_imported!='N'"); } else if (iMode == IMPORTEDNOTVALIDATE) { addSql = QStringLiteral("t_imported='P'"); } else if (iMode == IMPORTING) { addSql = QStringLiteral("t_imported='T'"); } else if (iMode == NOTCHECKED) { addSql = QStringLiteral("t_status!='Y'"); } QString wc = getSelectSqlOrder(addSql); SKGDocument* doc = getDocument(); if (doc != nullptr) { QStringList list = SKGRuleObject::getFromXML(doc, getXMLActionDefinition(), true, UPDATE, true); // SQL + SET clause int nb = list.count(); err = doc->beginTransaction("#INTERNAL#" % i18nc("Progression step", "Apply rule"), nb); IFOK(err) { // All sql orders must be executed to be sure than i_tmp is reset SKGError err2; for (int i = 0; i < nb; ++i) { QString sql = list.at(i); sql.replace(QStringLiteral("#WC#"), wc); err2 = doc->executeSqliteOrder(sql); if (!err2) { err2 = doc->stepForward(i + 1); } if (err2 && !err) { err = err2; } } } IFOK(err) { SKGStringListList result; err = doc->executeSelectSqliteOrder(QStringLiteral("SELECT changes()"), result); if (!err && result.count() == 2) { int nbChanges = SKGServices::stringToInt(result.at(1).at(0)); if (nbChanges != 0) { doc->sendMessage(i18np("1 operation modified by %2", "%1 operations modified by %2", nbChanges, getAttribute(QStringLiteral("i_ORDER")))); } } } SKGENDTRANSACTION(doc, err); } } else if (getActionType() == SKGRuleObject::ALARM) { // Alarm mode auto doc = qobject_cast(getDocument()); if (doc != nullptr) { SKGRuleObject::SKGAlarmInfo alarm = getAlarmInfo(); if (!alarm.Message.isEmpty()) { SKGServices::SKGUnitInfo unit = doc->getPrimaryUnit(); // Build the message if (alarm.Message.contains(QLatin1String("%3"))) { alarm.Message = alarm.Message.arg(doc->formatMoney(alarm.Amount, unit, false), doc->formatMoney(alarm.Limit, unit, false), doc->formatMoney(alarm.Amount - alarm.Limit, unit, false)); } else if (alarm.Message.contains(QLatin1String("%2"))) { alarm.Message = alarm.Message.arg(doc->formatMoney(alarm.Amount, unit, false), doc->formatMoney(alarm.Limit, unit, false)); } else if (alarm.Message.contains(QLatin1String("%1"))) { alarm.Message = alarm.Message.arg(doc->formatMoney(alarm.Amount, unit, false)); } getDocument()->sendMessage(alarm.Message); } } } else if (getActionType() == SKGRuleObject::APPLYTEMPLATE) { // Template mode QString addSql; if (iMode == IMPORTED) { addSql = QStringLiteral("t_imported!='N'"); } else if (iMode == IMPORTEDNOTVALIDATE) { addSql = QStringLiteral("t_imported='P'"); } else if (iMode == IMPORTING) { addSql = QStringLiteral("t_imported='T'"); } QString wc = getSelectSqlOrder(addSql); auto doc = qobject_cast(getDocument()); if (doc != nullptr) { QStringList list = SKGRuleObject::getFromXML(doc, getXMLActionDefinition(), true, APPLYTEMPLATE, false); if (!list.isEmpty()) { const QString& id = list.at(0); // Get template SKGOperationObject templateToApply(doc, SKGServices::stringToInt(id)); // Get operations to modify SKGObjectBase::SKGListSKGObjectBase objectsToModify; IFOKDO(err, doc->getObjects(QStringLiteral("v_operation_prop"), wc, objectsToModify)) int nb = objectsToModify.count(); for (int i = 0; !err && i < nb; ++i) { SKGOperationObject operationObj(doc, SKGServices::stringToInt(objectsToModify.at(i).getAttribute(QStringLiteral("i_OPID")))); SKGOperationObject op; IFOKDO(err, templateToApply.duplicate(op)) IFOKDO(err, op.mergeAttribute(operationObj, SKGOperationObject::PROPORTIONAL, false)) } } } } IFKO(err) err.addError(ERR_FAIL, i18nc("Error message", "Rule %1 failed", getAttribute(QStringLiteral("i_ORDER")))); return err; } double SKGRuleObject::getOrder() const { return SKGServices::stringToDouble(getAttribute(QStringLiteral("f_sortorder"))); } QString SKGRuleObject::getDisplayForOperator(const QString& iOperator, const QString& iParam1, const QString& iParam2, const QString& iAtt2) { QString output = iOperator; if (output == QStringLiteral("#ATT# LIKE '%#V1S#%'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "contains '#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT# NOT LIKE '%#V1S#%'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "does not contain '#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT# LIKE '#V1S#%'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "starts with '#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT# NOT LIKE '#V1S#%'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "does not start with '#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT# LIKE '%#V1S#'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "ends with '#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT# NOT LIKE '%#V1S#'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "does not end with '#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT#=''")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is empty"); } else if (output == QStringLiteral("#ATT#!=''")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is not empty"); + } else if (output == QStringLiteral("#ATT#=REGEXPCAPTURE('#V1S#', #ATT2#, #V2#)")) { + output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "=regexpcapture(#ATT2#, '#V1S#', #V2#)").replace(QStringLiteral("#V1S#"), iParam1).replace(QStringLiteral("#V2#"), iParam2).replace(QStringLiteral("#ATT2#"), iAtt2); } else if (output == QStringLiteral("REGEXP('#V1S#', #ATT#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "regexp '#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("NOT(REGEXP('#V1S#', #ATT#))")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "not regexp '#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("WILDCARD('#V1S#', #ATT#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "wildcard '#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("NOT(WILDCARD('#V1S#', #ATT#))")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "not wildcard '#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT#=#V1#")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "=#V1#").replace(QStringLiteral("#V1#"), iParam1); } else if (output == QStringLiteral("#ATT#!=#V1#")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "!=#V1#").replace(QStringLiteral("#V1#"), iParam1); } else if (output == QStringLiteral("#ATT#>#V1#")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", ">#V1#").replace(QStringLiteral("#V1#"), iParam1); } else if (output == QStringLiteral("#ATT#<#V1#")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "<#V1#").replace(QStringLiteral("#V1#"), iParam1); } else if (output == QStringLiteral("#ATT#>=#V1#")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", ">=#V1#").replace(QStringLiteral("#V1#"), iParam1); } else if (output == QStringLiteral("#ATT#<=#V1#")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "<=#V1#").replace(QStringLiteral("#V1#"), iParam1); } else if (output == QStringLiteral("#ATT#='#V1S#'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "='#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT#!='#V1S#'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "!='#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT#>'#V1S#'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", ">'#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT#<'#V1S#'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "<'#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT#>='#V1S#'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", ">='#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT#<='#V1S#'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "<='#V1S#'").replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT#>=#V1# AND #ATT#<=#V2#")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is between #V1# and #V2#").replace(QStringLiteral("#V1#"), iParam1).replace(QStringLiteral("#V2#"), iParam2); } else if (output == QStringLiteral("((#ATT#>='#V1S#' AND #ATT#<='#V2S#') OR (#ATT#>='#V2S#' AND #ATT#<='#V1S#'))")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is between '#V1S#' and '#V2S#'").replace(QStringLiteral("#V1S#"), iParam1).replace(QStringLiteral("#V2S#"), iParam2); } else if (output == QStringLiteral("#ATT#=lower(#ATT#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is set to lower"); } else if (output == QStringLiteral("#ATT#=upper(#ATT#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is set to upper"); } else if (output == QStringLiteral("#ATT#=capitalize(#ATT#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is set to capitalize"); } else if (output == QStringLiteral("#ATT#!=lower(#ATT#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is not lower"); } else if (output == QStringLiteral("#ATT#!=upper(#ATT#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is not upper"); } else if (output == QStringLiteral("#ATT#!=capitalize(#ATT#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is not capitalize"); } else if (output == QStringLiteral("#ATT#= lower(#ATT#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is lower"); } else if (output == QStringLiteral("#ATT#= upper(#ATT#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is upper"); } else if (output == QStringLiteral("#ATT#= capitalize(#ATT#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is capitalize"); } else if (output == QStringLiteral("#ATT#=replace(#ATT2#,'#V1S#','#V2S#')")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "=#ATT2# with '#V1S#' replaced by '#V2S#'").replace(QStringLiteral("#V1S#"), iParam1).replace(QStringLiteral("#V2S#"), iParam2).replace(QStringLiteral("#ATT2#"), iAtt2); } else if (output == QStringLiteral("#ATT#=substr(#ATT2#,'#V1#','#V2#')")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "=substring of #ATT2# from #V1# to #V2#").replace(QStringLiteral("#V1#"), iParam1).replace(QStringLiteral("#V2#"), iParam2).replace(QStringLiteral("#ATT2#"), iAtt2); } else if (output == QStringLiteral("#ATT#=#ATT2#")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "=#ATT2#").replace(QStringLiteral("#ATT2#"), iAtt2); } else if (output == QStringLiteral("#ATT#=WORD(#ATT2#,#V1S#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "=word(#ATT2#,#V1S#)").replace(QStringLiteral("#ATT2#"), iAtt2).replace(QStringLiteral("#V1S#"), iParam1); } else if (output == QStringLiteral("#ATT#=todate(#ATT2#,'#DF#')")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "=#ATT2# as date with format #DF#").replace(QStringLiteral("#ATT2#"), iAtt2).replace(QStringLiteral("#DF#"), iParam2); } else if (output == QStringLiteral("#ATT#=todate(WORD(#ATT2#,#V1S#),'#DF#')")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "=word(#ATT2#,#V1S#) as date with format #DF#").replace(QStringLiteral("#ATT2#"), iAtt2).replace(QStringLiteral("#V1S#"), iParam1).replace(QStringLiteral("#DF#"), iParam2); } else if (output == QStringLiteral("STRFTIME('%Y-%m',#ATT#)=STRFTIME('%Y-%m',date('now'))")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is in current month"); } else if (output == QStringLiteral("STRFTIME('%Y-%m',#ATT#)=STRFTIME('%Y-%m',date('now','start of month','-1 month'))")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is in previous month"); } else if (output == QStringLiteral("STRFTIME('%Y',#ATT#)=STRFTIME('%Y',date('now'))")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is in current year"); } else if (output == QStringLiteral("STRFTIME('%Y',#ATT#)=STRFTIME('%Y',date('now','start of month','-1 year'))")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is in previous year"); } else if (output == QStringLiteral("#ATT#>date('now','-30 day') AND #ATT#<=date('now')")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is in last 30 days"); } else if (output == QStringLiteral("#ATT#>date('now','-3 month') AND #ATT#<=date('now')")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is in last 3 months"); } else if (output == QStringLiteral("#ATT#>date('now','-6 month') AND #ATT#<=date('now')")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is in last 6 months"); } else if (output == QStringLiteral("#ATT#>date('now','-12 month') AND #ATT#<=date('now')")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is in last 12 months"); } else if (output == QStringLiteral("#ATT#>date('now','-2 year') AND #ATT#<=date('now')")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is in last 2 years"); } else if (output == QStringLiteral("#ATT#>date('now','-3 year') AND #ATT#<=date('now')")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is in last 3 years"); } else if (output == QStringLiteral("#ATT#>date('now','-5 year') AND #ATT#<=date('now')")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "is in last 5 years"); } else if (output == QStringLiteral("ABS(TOTAL(#ATT#))#OP##V1#,ABS(TOTAL(#ATT#)), #V1#, '#V2S#'")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "If total(#ATT#)#OP##V1# then send '#V2S#'").replace(QStringLiteral("#V1#"), iParam1).replace(QStringLiteral("#V2S#"), iParam2).replace(QStringLiteral("#OP#"), iAtt2); } else if (output == QStringLiteral("APPLYTEMPLATE(#V1#)")) { output = i18nc("Description of a condition. Do not translate key words (#V1S#, #V1#, ...)", "Apply the template '#V2S#'").replace(QStringLiteral("#V2S#"), iParam2); } return output; } QString SKGRuleObject::getToolTipForOperator(const QString& iOperator) { QString output = iOperator; if (output == QStringLiteral("#ATT# LIKE '%#V1S#%'")) { output = i18nc("Help for a condition", "To find out if the attribute contains a given string"); } else if (output == QStringLiteral("#ATT# NOT LIKE '%#V1S#%'")) { output = i18nc("Help for a condition", "To find out if the attribute doesn't contain a given string"); } else if (output == QStringLiteral("#ATT# LIKE '#V1S#%'")) { output = i18nc("Help for a condition", "To find out if the attribute is starting by a given string"); } else if (output == QStringLiteral("#ATT# NOT LIKE '#V1S#%'")) { output = i18nc("Help for a condition", "To find out if the attribute is not starting by a given string"); } else if (output == QStringLiteral("#ATT# LIKE '%#V1S#'")) { output = i18nc("Help for a condition", "To find out if the attribute is ending by a given string"); } else if (output == QStringLiteral("#ATT# NOT LIKE '%#V1S#'")) { output = i18nc("Help for a condition", "To find out if the attribute is not ending by a given string"); } else if (output == QStringLiteral("#ATT#=''")) { output = i18nc("Help for a condition", "To find out if the attribute is empty"); } else if (output == QStringLiteral("#ATT#!=''")) { output = i18nc("Help for a condition", "To find out if the attribute is not empty"); + } else if (output == QStringLiteral("#ATT#=REGEXPCAPTURE('#V1S#', #ATT2#, #V2#)")) { + output = i18nc("Help for a condition", "To get a captured value by a given regular expression (eg. \"(\\d+)(\\s*)(cm|inch(es)?)\")"); } else if (output == QStringLiteral("REGEXP('#V1S#', #ATT#)")) { output = i18nc("Help for a condition", "To find out if the attribute is matching a given regular expression (eg. \"^[H,h]ello$\")"); } else if (output == QStringLiteral("NOT(REGEXP('#V1S#', #ATT#))")) { output = i18nc("Help for a condition", "To find out if the attribute is not matching a given regular expression (eg. \"^[H,h]ello$\")"); } else if (output == QStringLiteral("WILDCARD('#V1S#', #ATT#)")) { output = i18nc("Help for a condition", "To find out if the attribute is matching a given wildcard expression (eg. \"_ello\")"); } else if (output == QStringLiteral("NOT(WILDCARD('#V1S#', #ATT#))")) { output = i18nc("Help for a condition", "To find out if the attribute is not matching a given wildcard expression (eg. \"_ello\")"); } else if (output == QStringLiteral("#ATT#=#V1#")) { output = i18nc("Help for a condition", "To find out if the attribute is equal to a given value"); } else if (output == QStringLiteral("#ATT#!=#V1#")) { output = i18nc("Help for a condition", "To find out if the attribute is not equal to a given value"); } else if (output == QStringLiteral("#ATT#>#V1#")) { output = i18nc("Help for a condition", "To find out if the attribute is greater than a given value"); } else if (output == QStringLiteral("#ATT#<#V1#")) { output = i18nc("Help for a condition", "To find out if the attribute is smaller than a given value"); } else if (output == QStringLiteral("#ATT#>=#V1#")) { output = i18nc("Help for a condition", "To find out if the attribute is greater or equal to a given value"); } else if (output == QStringLiteral("#ATT#<=#V1#")) { output = i18nc("Help for a condition", "To set the attribute with a given value"); } else if (output == QStringLiteral("#ATT#='#V1S#'")) { output = i18nc("Help for a condition", "To find out if the attribute is equal to a given string"); } else if (output == QStringLiteral("#ATT#!='#V1S#'")) { output = i18nc("Help for a condition", "To find out if the attribute is not equal to a given string"); } else if (output == QStringLiteral("#ATT#>'#V1S#'")) { output = i18nc("Help for a condition", "To find out if the attribute is greater than a given string"); } else if (output == QStringLiteral("#ATT#<'#V1S#'")) { output = i18nc("Help for a condition", "To find out if the attribute is smaller than a given string"); } else if (output == QStringLiteral("#ATT#>='#V1S#'")) { output = i18nc("Help for a condition", "To find out if the attribute is greater or equal to a given string"); } else if (output == QStringLiteral("#ATT#<='#V1S#'")) { output = i18nc("Help for a condition", "To find out if the attribute is smaller or equal to a given string"); } else if (output == QStringLiteral("#ATT#>=#V1# AND #ATT#<=#V2#")) { output = i18nc("Help for a condition", "To find out if the attribute is between two given values"); } else if (output == QStringLiteral("((#ATT#>='#V1S#' AND #ATT#<='#V2S#') OR (#ATT#>='#V2S#' AND #ATT#<='#V1S#'))")) { output = i18nc("Help for a condition", "To find out if the attribute is between two given strings"); } else if (output == QStringLiteral("#ATT#=lower(#ATT#)")) { output = i18nc("Help for a condition", "To set the attribute in lower case (eg. hello)"); } else if (output == QStringLiteral("#ATT#=upper(#ATT#)")) { output = i18nc("Help for a condition", "To set the attribute in upper case (eg. HELLO)"); } else if (output == QStringLiteral("#ATT#=capitalize(#ATT#)")) { output = i18nc("Help for a condition", "To set the attribute in capitalized case (eg. Hello)"); } else if (output == QStringLiteral("#ATT#!=lower(#ATT#)")) { output = i18nc("Help for a condition", "To find out if the attribute is not in lower case (eg. hello)"); } else if (output == QStringLiteral("#ATT#!=upper(#ATT#)")) { output = i18nc("Help for a condition", "To find out if the attribute is not in upper case (eg. HELLO)"); } else if (output == QStringLiteral("#ATT#!=capitalize(#ATT#)")) { output = i18nc("Help for a condition", "To find out if the attribute is not in capitalized case (eg. Hello)"); } else if (output == QStringLiteral("#ATT#= lower(#ATT#)")) { output = i18nc("Help for a condition", "To find out if the attribute is in lower case (eg. hello)"); } else if (output == QStringLiteral("#ATT#= upper(#ATT#)")) { output = i18nc("Help for a condition", "To find out if the attribute is in upper case (eg. HELLO)"); } else if (output == QStringLiteral("#ATT#= capitalize(#ATT#)")) { output = i18nc("Help for a condition", "To find out if the attribute is in capitalized case (eg. Hello)"); } else if (output == QStringLiteral("#ATT#=replace(#ATT2#,'#V1S#','#V2S#')")) { output = i18nc("Help for a condition", "To set the attribute with the value of another attribute where a value is replaced by another one"); } else if (output == QStringLiteral("#ATT#=substr(#ATT2#,'#V1#','#V2#')")) { output = i18nc("Help for a condition", "To set the attribute with a part of the value of another attribute"); } else if (output == QStringLiteral("#ATT#=#ATT2#")) { output = i18nc("Help for a condition", "To set the attribute with the value of another attribute"); } else if (output == QStringLiteral("#ATT#=WORD(#ATT2#,#V1S#)")) { output = i18nc("Help for a condition", "To set the attribute with a word of the value of another attribute converted in date format"); } else if (output == QStringLiteral("#ATT#=todate(#ATT2#,'#DF#')")) { output = i18nc("Help for a condition", "To set the date attribute with the value of another attribute"); } else if (output == QStringLiteral("#ATT#=todate(WORD(#ATT2#,#V1S#),'#DF#')")) { output = i18nc("Help for a condition", "To set the date attribute with a word of another attribute converted in date format"); } else if (output == QStringLiteral("STRFTIME('%Y-%m',#ATT#)=STRFTIME('%Y-%m',date('now'))")) { output = i18nc("Help for a condition", "To find out if the date of the operation is today"); } else if (output == QStringLiteral("STRFTIME('%Y-%m',#ATT#)=STRFTIME('%Y-%m',date('now','start of month','-1 month'))")) { output = i18nc("Help for a condition", "To find out if the date of the operation is in previous month"); } else if (output == QStringLiteral("STRFTIME('%Y',#ATT#)=STRFTIME('%Y',date('now'))")) { output = i18nc("Help for a condition", "To find out if the date of the operation is in current year"); } else if (output == QStringLiteral("STRFTIME('%Y',#ATT#)=STRFTIME('%Y',date('now','start of month','-1 year'))")) { output = i18nc("Help for a condition", "To find out if the date of the operation is in previous year"); } else if (output == QStringLiteral("#ATT#>date('now','-30 day') AND #ATT#<=date('now')")) { output = i18nc("Help for a condition", "To find out if the date of the operation is in last 30 days"); } else if (output == QStringLiteral("#ATT#>date('now','-3 month') AND #ATT#<=date('now')")) { output = i18nc("Help for a condition", "To find out if the date of the operation is in last 3 months"); } else if (output == QStringLiteral("#ATT#>date('now','-6 month') AND #ATT#<=date('now')")) { output = i18nc("Help for a condition", "To find out if the date of the operation is in last 6 months"); } else if (output == QStringLiteral("#ATT#>date('now','-12 month') AND #ATT#<=date('now')")) { output = i18nc("Help for a condition", "To find out if the date of the operation is in last 12 months"); } else if (output == QStringLiteral("#ATT#>date('now','-2 year') AND #ATT#<=date('now')")) { output = i18nc("Help for a condition", "To find out if the date of the operation is in last 2 years"); } else if (output == QStringLiteral("#ATT#>date('now','-3 year') AND #ATT#<=date('now')")) { output = i18nc("Help for a condition", "To find out if the date of the operation is in last 3 years"); } else if (output == QStringLiteral("#ATT#>date('now','-5 year') AND #ATT#<=date('now')")) { output = i18nc("Help for a condition", "To find out if the date of the operation is in last 5 years"); } return output; } QStringList SKGRuleObject::getListOfOperators(SKGServices::AttributeType iAttributeType, SKGRuleObject::ActionType iType) { QStringList output; if (iType == UPDATE) { // Mode update if (iAttributeType == SKGServices::TEXT) { output.push_back(QStringLiteral("#ATT#='#V1S#'")); output.push_back(QStringLiteral("#ATT#=lower(#ATT#)")); output.push_back(QStringLiteral("#ATT#=upper(#ATT#)")); output.push_back(QStringLiteral("#ATT#=capitalize(#ATT#)")); output.push_back(QStringLiteral("#ATT#=replace(#ATT2#,'#V1S#','#V2S#')")); + output.push_back(QStringLiteral("#ATT#=REGEXPCAPTURE('#V1S#', #ATT2#, #V2#)")); } else if (iAttributeType == SKGServices::INTEGER || iAttributeType == SKGServices::FLOAT) { output.push_back(QStringLiteral("#ATT#=#V1#")); } else if (iAttributeType == SKGServices::DATE || iAttributeType == SKGServices::BOOL || iAttributeType == SKGServices::TRISTATE) { output.push_back(QStringLiteral("#ATT#='#V1S#'")); } if (iAttributeType == SKGServices::DATE) { output.push_back(QStringLiteral("#ATT#=todate(#ATT2#,'#DF#')")); output.push_back(QStringLiteral("#ATT#=todate(WORD(#ATT2#,#V1S#),'#DF#')")); } else if (iAttributeType != SKGServices::BOOL && iAttributeType != SKGServices::TRISTATE) { output.push_back(QStringLiteral("#ATT#=substr(#ATT2#,'#V1#','#V2#')")); output.push_back(QStringLiteral("#ATT#=#ATT2#")); output.push_back(QStringLiteral("#ATT#=WORD(#ATT2#,#V1S#)")); } } else if (iType == SEARCH) { // Mode query if (iAttributeType == SKGServices::TEXT) { output.push_back(QStringLiteral("#ATT# LIKE '%#V1S#%'")); output.push_back(QStringLiteral("#ATT# NOT LIKE '%#V1S#%'")); output.push_back(QStringLiteral("#ATT# LIKE '#V1S#%'")); output.push_back(QStringLiteral("#ATT# NOT LIKE '#V1S#%'")); output.push_back(QStringLiteral("#ATT# LIKE '%#V1S#'")); output.push_back(QStringLiteral("#ATT# NOT LIKE '%#V1S#'")); output.push_back(QStringLiteral("#ATT#=''")); output.push_back(QStringLiteral("#ATT#!=''")); output.push_back(QStringLiteral("#ATT#= lower(#ATT#)")); output.push_back(QStringLiteral("#ATT#!=lower(#ATT#)")); output.push_back(QStringLiteral("#ATT#= upper(#ATT#)")); output.push_back(QStringLiteral("#ATT#!=upper(#ATT#)")); output.push_back(QStringLiteral("#ATT#= capitalize(#ATT#)")); output.push_back(QStringLiteral("#ATT#!=capitalize(#ATT#)")); output.push_back(QStringLiteral("REGEXP('#V1S#', #ATT#)")); output.push_back(QStringLiteral("NOT(REGEXP('#V1S#', #ATT#))")); output.push_back(QStringLiteral("WILDCARD('#V1S#', #ATT#)")); output.push_back(QStringLiteral("NOT(WILDCARD('#V1S#', #ATT#))")); } if (iAttributeType == SKGServices::INTEGER || iAttributeType == SKGServices::FLOAT) { output.push_back(QStringLiteral("#ATT#=#V1#")); output.push_back(QStringLiteral("#ATT#!=#V1#")); output.push_back(QStringLiteral("#ATT#>#V1#")); output.push_back(QStringLiteral("#ATT#<#V1#")); output.push_back(QStringLiteral("#ATT#>=#V1#")); output.push_back(QStringLiteral("#ATT#<=#V1#")); output.push_back(QStringLiteral("#ATT#>=#V1# AND #ATT#<=#V2#")); } if (iAttributeType == SKGServices::BOOL || iAttributeType == SKGServices::TRISTATE || iAttributeType == SKGServices::TEXT || iAttributeType == SKGServices::DATE) { output.push_back(QStringLiteral("#ATT#='#V1S#'")); } if (iAttributeType == SKGServices::TRISTATE || iAttributeType == SKGServices::TEXT || iAttributeType == SKGServices::DATE) { output.push_back(QStringLiteral("#ATT#!='#V1S#'")); } if (iAttributeType == SKGServices::TEXT || iAttributeType == SKGServices::DATE) { output.push_back(QStringLiteral("#ATT#>'#V1S#'")); output.push_back(QStringLiteral("#ATT#<'#V1S#'")); output.push_back(QStringLiteral("#ATT#>='#V1S#'")); output.push_back(QStringLiteral("#ATT#<='#V1S#'")); output.push_back(QStringLiteral("((#ATT#>='#V1S#' AND #ATT#<='#V2S#') OR (#ATT#>='#V2S#' AND #ATT#<='#V1S#'))")); } if (iAttributeType == SKGServices::DATE) { output.push_back(QStringLiteral("STRFTIME('%Y-%m',#ATT#)=STRFTIME('%Y-%m',date('now'))")); output.push_back(QStringLiteral("STRFTIME('%Y-%m',#ATT#)=STRFTIME('%Y-%m',date('now','start of month','-1 month'))")); output.push_back(QStringLiteral("STRFTIME('%Y',#ATT#)=STRFTIME('%Y',date('now'))")); output.push_back(QStringLiteral("STRFTIME('%Y',#ATT#)=STRFTIME('%Y',date('now','start of month','-1 year'))")); output.push_back(QStringLiteral("#ATT#>date('now','-30 day') AND #ATT#<=date('now')")); output.push_back(QStringLiteral("#ATT#>date('now','-3 month') AND #ATT#<=date('now')")); output.push_back(QStringLiteral("#ATT#>date('now','-6 month') AND #ATT#<=date('now')")); output.push_back(QStringLiteral("#ATT#>date('now','-12 month') AND #ATT#<=date('now')")); output.push_back(QStringLiteral("#ATT#>date('now','-2 year') AND #ATT#<=date('now')")); output.push_back(QStringLiteral("#ATT#>date('now','-3 year') AND #ATT#<=date('now')")); output.push_back(QStringLiteral("#ATT#>date('now','-5 year') AND #ATT#<=date('now')")); } } else if (iType == ALARM) { output.push_back(QStringLiteral("ABS(TOTAL(#ATT#))#OP##V1#,ABS(TOTAL(#ATT#)), #V1#, '#V2S#'")); } else if (iType == APPLYTEMPLATE) { output.push_back(QStringLiteral("APPLYTEMPLATE(#V1#)")); } return output; } QStringList SKGRuleObject::getFromXML(SKGDocument* iDocument, const QString& iXML, bool iSQL, SKGRuleObject::ActionType iType, bool iFullUpdate) { QStringList output; if (iFullUpdate) { // Add markers output.push_back(QStringLiteral("UPDATE v_operation_prop set i_tmp=1 WHERE #WC#")); } QDomDocument doc(QStringLiteral("SKGML")); doc.setContent(iXML); QDomElement root = doc.documentElement(); if (root.tagName() == QStringLiteral("element") || iType != SEARCH) { // Mode advanced QDomNode l = root.firstChild(); while (!l.isNull()) { QDomElement elementl = l.toElement(); if (!elementl.isNull()) { QString lineDescription; QDomNode n = elementl.firstChild(); bool parenthesisNeeded = false; while (!n.isNull()) { QDomElement element = n.toElement(); if (!element.isNull()) { // Build element description QString elementDescription; QString attribute = element.attribute(QStringLiteral("attribute")); QString propName; if (iSQL) { attribute = SKGServices::stringToSqlString(attribute); if (attribute.startsWith(QLatin1String("p_"))) { // Case property propName = attribute.right(attribute.length() - 2); if (iType == SEARCH) { attribute = "i_PROPPNAME='" % SKGServices::stringToSqlString(propName) % "' AND i_PROPVALUE"; } else { attribute = QStringLiteral("t_value"); } } QString part = element.attribute(QStringLiteral("operator")); part = part.replace(QStringLiteral("#V1#"), SKGServices::stringToSqlString(element.attribute(QStringLiteral("value")))); part = part.replace(QStringLiteral("#V1S#"), SKGServices::stringToSqlString(element.attribute(QStringLiteral("value")))); part = part.replace(QStringLiteral("#V2#"), SKGServices::stringToSqlString(element.attribute(QStringLiteral("value2")))); part = part.replace(QStringLiteral("#V2S#"), SKGServices::stringToSqlString(element.attribute(QStringLiteral("value2")))); part = part.replace(QStringLiteral("#DF#"), SKGServices::stringToSqlString(element.attribute(QStringLiteral("value2")))); part = part.replace(QStringLiteral("#OP#"), SKGServices::stringToSqlString(element.attribute(QStringLiteral("operator2")))); elementDescription += part; } else { attribute = "operation." % attribute; if (iDocument != nullptr) { attribute = iDocument->getDisplay(attribute); } if (iType != ALARM && iType != APPLYTEMPLATE) { elementDescription = attribute; elementDescription += ' '; } QString tmp = element.attribute(QStringLiteral("att2s")); if (tmp.isEmpty()) { tmp = element.attribute(QStringLiteral("operator2")); } QString part = SKGRuleObject::getDisplayForOperator(element.attribute(QStringLiteral("operator")), element.attribute(QStringLiteral("value")), element.attribute(QStringLiteral("value2")), tmp); elementDescription += part; } elementDescription = elementDescription.replace(QStringLiteral("#ATT#"), attribute); // Att2 QString attribute2 = element.attribute(QStringLiteral("att2")); if (iSQL) { attribute2 = SKGServices::stringToSqlString(attribute2); if (attribute2.startsWith(QLatin1String("p_"))) { QString propertyName = attribute2.right(attribute2.length() - 2); attribute2 = "IFNULL((SELECT op2.i_PROPVALUE FROM v_operation_prop op2 WHERE op2.id=v_operation_prop.id AND op2.i_PROPPNAME='" % SKGServices::stringToSqlString(propertyName) % "'),'')"; } if (element.attribute(QStringLiteral("attribute")).startsWith(QLatin1String("p_"))) { attribute2 = "(SELECT " % attribute2 % " FROM v_operation_prop WHERE i_PROPPID=parameters.id)"; } } elementDescription = elementDescription.replace(QStringLiteral("#ATT2#"), attribute2); // Add it to line description if (iSQL) { if (iType == UPDATE) { if (!iFullUpdate) { output.push_back(elementDescription); } else { if (attribute == QStringLiteral("t_REALCATEGORY")) { elementDescription.replace(attribute, QStringLiteral("t_fullname")); QString parentcat; QString cat = element.attribute(QStringLiteral("value")); bool stop = false; while (!stop) { int posSeparator = cat.indexOf(OBJECTSEPARATOR); if (posSeparator == -1) { if (parentcat.isEmpty()) { output.push_back(QStringLiteral("UPDATE category SET rd_category_id=0 WHERE rd_category_id IS NULL OR rd_category_id=''")); output.push_back("INSERT OR IGNORE INTO category (t_name, rd_category_id) VALUES ('" % SKGServices::stringToSqlString(cat) % "', 0)"); } else { output.push_back("INSERT OR IGNORE INTO category (t_name, rd_category_id) VALUES ('" % SKGServices::stringToSqlString(cat) % "',(SELECT id FROM category WHERE t_fullname='" % SKGServices::stringToSqlString(parentcat) % "'))"); } stop = true; } else { // Get first and second parts of the branch QString first = cat.mid(0, posSeparator); QString second = cat.mid(posSeparator + QString(OBJECTSEPARATOR).length(), cat.length() - posSeparator - QString(OBJECTSEPARATOR).length()); if (parentcat.isEmpty()) { output.push_back(QStringLiteral("UPDATE category SET rd_category_id=0 WHERE rd_category_id IS NULL OR rd_category_id=''")); output.push_back("INSERT OR IGNORE INTO category (t_name, rd_category_id) VALUES ('" % SKGServices::stringToSqlString(first) % "', 0)"); } else { output.push_back("INSERT OR IGNORE INTO category (t_name, rd_category_id) VALUES ('" % SKGServices::stringToSqlString(first) % "',(SELECT id FROM category WHERE t_fullname='" % SKGServices::stringToSqlString(parentcat) % "'))"); } if (parentcat.isEmpty()) { parentcat = first; } else { parentcat = parentcat % OBJECTSEPARATOR % first; } cat = second; } } output.push_back("UPDATE suboperation set r_category_id=(SELECT id FROM category WHERE " % elementDescription % ") WHERE i_tmp=1"); } else if (element.attribute(QStringLiteral("attribute")).startsWith(QLatin1String("p_"))) { output.push_back("INSERT OR IGNORE INTO parameters (t_uuid_parent, t_name, i_tmp) SELECT o.id||'-operation', '" % SKGServices::stringToSqlString(propName) % "', 1 FROM operation o WHERE o.i_tmp=1"); output.push_back("UPDATE parameters set " % elementDescription % " WHERE i_tmp=1 AND t_name='" % SKGServices::stringToSqlString(propName) % "' AND NOT(" % elementDescription % ')'); output.push_back("DELETE FROM parameters WHERE i_tmp=1 AND t_name='" % SKGServices::stringToSqlString(propName) % "' AND t_value=''"); } else { output.push_back("UPDATE v_operation_prop set " % elementDescription % " WHERE i_tmp=1 AND NOT(" % elementDescription % ')'); } } } else if (iType == ALARM) { output.push_back("SELECT " % elementDescription % " FROM v_operation_prop WHERE #WC#"); } else if (iType == APPLYTEMPLATE) { output.push_back(element.attribute(QStringLiteral("value"))); } } if (!lineDescription.isEmpty()) { lineDescription += (iType == UPDATE ? QStringLiteral(" , ") : (iSQL ? QStringLiteral(" AND ") : i18nc("logical operator in a search query", " and "))); parenthesisNeeded = true; } lineDescription += elementDescription; } n = n.nextSibling(); } if (!(iType != SEARCH && iSQL) && !lineDescription.isEmpty()) { if (iType == SEARCH && parenthesisNeeded) { lineDescription = '(' % lineDescription % ')'; } output.push_back(lineDescription); } } l = l.nextSibling(); } } else { output.push_back(root.attribute(iSQL ? QStringLiteral("sql") : QStringLiteral("query"))); } if (iFullUpdate) { // Remove markers output.push_back(QStringLiteral("UPDATE v_operation_prop set i_tmp=0 WHERE i_tmp=1")); } return output; } QString SKGRuleObject::getDescriptionFromXML(SKGDocument* iDocument, const QString& iXML, bool iSQL, SKGRuleObject::ActionType iType) { QString output; QStringList list = getFromXML(iDocument, iXML, iSQL, iType); int nb = list.count(); for (int i = 0; i < nb; ++i) { output.append(list.at(i)); if (i < nb - 1) { output.append(iType != SEARCH ? QStringLiteral(" , ") : (iSQL ? QStringLiteral(" OR ") : i18nc("logical operator in a search query", " or "))); } } return output; } SKGError SKGRuleObject::createPayeeCategoryRule(SKGDocument* iDocument, const QString& iPayee, const QString& iCategory, SKGRuleObject& oRule) { SKGError err; oRule = SKGRuleObject(iDocument); // TODO(Stephane MANKOWSKI): escape values IFOKDO(err, oRule.setActionType(SKGRuleObject::UPDATE)) IFOKDO(err, oRule.setSearchDescription(iDocument->getDisplay(QStringLiteral("t_PAYEE")) % ' ' % getDisplayForOperator(QStringLiteral("#ATT#='#V1S#'"), iPayee, QString(), QString()))) IFOKDO(err, oRule.setXMLSearchDefinition(" ")) IFOKDO(err, oRule.setActionDescription(iDocument->getDisplay(QStringLiteral("t_REALCATEGORY")) % ' ' % getDisplayForOperator(QStringLiteral("#ATT#='#V1S#'"), iCategory, QString(), QString()))) IFOKDO(err, oRule.setXMLActionDefinition(" ")) IFOKDO(err, oRule.save()) return err; } diff --git a/skgbasemodeler/skgdocument.cpp b/skgbasemodeler/skgdocument.cpp index 87611e222..cff787197 100644 --- a/skgbasemodeler/skgdocument.cpp +++ b/skgbasemodeler/skgdocument.cpp @@ -1,3474 +1,3504 @@ /*************************************************************************** * Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see * ***************************************************************************/ /** @file * This file implements classes SKGDocument. * * @author Stephane MANKOWSKI / Guillaume DE BURE */ #include "skgdocument.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "skgdocumentprivate.h" #include "skgerror.h" #include "skgpropertyobject.h" #include "skgreport.h" #include "skgservices.h" #include "skgtraces.h" #include "skgtransactionmng.h" #define SQLDRIVERNAME QStringLiteral("SKGSQLCIPHER") /** * Custom sqlite function. */ static void sleepFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); if (Q_LIKELY(data1)) { int s = SKGServices::stringToInt(QString(reinterpret_cast(data1), len1 / sizeof(QChar))); QThread::sleep(s); sqlite3_result_text(context, "OK", 2, SQLITE_TRANSIENT); } } /** * Custom sqlite function. */ static void periodFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); int len2 = sqlite3_value_bytes16(argv[ 1 ]); const void* data2 = sqlite3_value_text16(argv[ 1 ]); if (Q_LIKELY(data1 && data2)) { QDate date = SKGServices::stringToTime(QString(reinterpret_cast(data1), len1 / sizeof(QChar))).date(); QString format = QString::fromRawData(reinterpret_cast(data2), len2 / sizeof(QChar)).toUpper(); QString period = SKGServices::dateToPeriod(date, format); QByteArray output = period.toUtf8(); sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } } /** * Custom sqlite function. */ static void formattedDateFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); int len2 = sqlite3_value_bytes16(argv[ 1 ]); const void* data2 = sqlite3_value_text16(argv[ 1 ]); if (Q_LIKELY(data1 && data2)) { QString string(reinterpret_cast(data1), len1 / sizeof(QChar)); QString format = QString::fromRawData(reinterpret_cast(data2), len2 / sizeof(QChar)); QString date = QDate::fromString(string, QStringLiteral("yyyy-MM-dd")).toString(format); QByteArray output = date.toUtf8(); sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } } /** * Custom sqlite function. */ static void dateFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); int len2 = sqlite3_value_bytes16(argv[ 1 ]); const void* data2 = sqlite3_value_text16(argv[ 1 ]); if (Q_LIKELY(data1 && data2)) { QString string(reinterpret_cast(data1), len1 / sizeof(QChar)); QString format = QString::fromRawData(reinterpret_cast(data2), len2 / sizeof(QChar)); QString date = SKGServices::dateToSqlString(string, format).trimmed(); if (date.isEmpty()) { date = QDate::currentDate().toString(QStringLiteral("yyyy-MM-dd")); } QByteArray output = date.toUtf8(); sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } } /** * Custom sqlite function. */ static void currencyFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); int len2 = sqlite3_value_bytes16(argv[ 1 ]); const void* data2 = sqlite3_value_text16(argv[ 1 ]); if (Q_LIKELY(data1 && data2)) { double string = SKGServices::stringToDouble(QString::fromRawData(reinterpret_cast(data1), len1 / sizeof(QChar))); QString symbol = QString::fromRawData(reinterpret_cast(data2), len2 / sizeof(QChar)); QString currency = SKGServices::toCurrencyString(string, symbol); QByteArray output = currency.toUtf8(); sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } } /** * Custom sqlite function. */ static void xorFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); int len2 = sqlite3_value_bytes16(argv[ 1 ]); const void* data2 = sqlite3_value_text16(argv[ 1 ]); if (Q_LIKELY(data1 && data2)) { auto string = QString::fromRawData(reinterpret_cast(data1), len1 / sizeof(QChar)).toUtf8(); auto key = QString::fromRawData(reinterpret_cast(data2), len2 / sizeof(QChar)).toUtf8(); if (string.startsWith(QByteArray("# "))) { // Decrypt string = QByteArray::fromHex(string.right(string.length() - 2)); QByteArray estring; for (int i = 0; i < string.size(); ++i) { estring += static_cast(string[i] ^ key[i % key.size()]); } QByteArray output = estring; sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } else { // Encrypt QByteArray estring; for (int i = 0; i < string.size(); ++i) { estring += static_cast(string[i] ^ key[i % key.size()]); } QByteArray output = "# " + estring.toHex(); sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } } } static void xordoubleFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); int len2 = sqlite3_value_bytes16(argv[ 1 ]); const void* data2 = sqlite3_value_text16(argv[ 1 ]); if (Q_LIKELY(data1 && data2)) { auto d = SKGServices::stringToDouble(QString::fromRawData(reinterpret_cast(data1), len1 / sizeof(QChar)).toUtf8()); auto key = QString::fromRawData(reinterpret_cast(data2), len2 / sizeof(QChar)).toUtf8(); int kk = 0; for (int i = 0; i < key.size(); ++i) { kk += key[i]; } QByteArray output = SKGServices::doubleToString(static_cast(kk) - d).toUtf8(); sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } } /** * Custom sqlite function. */ static void wordFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); int len2 = sqlite3_value_bytes16(argv[ 1 ]); const void* data2 = sqlite3_value_text16(argv[ 1 ]); if (Q_LIKELY(data1 && data2)) { QString string1(reinterpret_cast(data1), len1 / sizeof(QChar)); string1 = string1.simplified(); QRegularExpression re(QStringLiteral("(\\w+)")); QRegularExpressionMatchIterator i = re.globalMatch(string1); QStringList list; while (i.hasNext()) { QRegularExpressionMatch match = i.next(); QString word = match.captured(1); list << word; } int pos = SKGServices::stringToInt(QString::fromRawData(reinterpret_cast(data2), len2 / sizeof(QChar))); if (pos == 0) { pos = 1; } else if (pos > list.count()) { pos = list.count(); } else if (pos < -list.count()) { pos = 1; } else if (pos < 0) { pos = list.count() + pos + 1; } QByteArray output = list[pos - 1].toUtf8(); sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } } /** * Custom sqlite function. */ static void wildcardFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); int len2 = sqlite3_value_bytes16(argv[ 1 ]); const void* data2 = sqlite3_value_text16(argv[ 1 ]); if (Q_LIKELY(data1 && data2)) { QString string1(reinterpret_cast(data1), len1 / sizeof(QChar)); QString string2 = QString::fromRawData(reinterpret_cast(data2), len2 / sizeof(QChar)); QRegExp pattern(string1, Qt::CaseInsensitive, QRegExp::Wildcard); if (pattern.isValid()) { sqlite3_result_int(context, static_cast(pattern.exactMatch(string2))); } else { sqlite3_result_error(context, pattern.errorString().toUtf8().constData(), -1); } } } /** * Custom sqlite function. */ static void regexpFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); int len2 = sqlite3_value_bytes16(argv[ 1 ]); const void* data2 = sqlite3_value_text16(argv[ 1 ]); if (Q_LIKELY(data1 && data2)) { QString string1(reinterpret_cast(data1), len1 / sizeof(QChar)); QString string2 = QString::fromRawData(reinterpret_cast(data2), len2 / sizeof(QChar)); QRegExp pattern(string1, Qt::CaseInsensitive, QRegExp::RegExp2); if (pattern.isValid()) { sqlite3_result_int(context, static_cast(pattern.exactMatch(string2))); } else { sqlite3_result_error(context, pattern.errorString().toUtf8().constData(), -1); } } } +/** + * Custom sqlite function. + */ +static void regexpCaptureFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) +{ + int len1 = sqlite3_value_bytes16(argv[ 0 ]); + const void* data1 = sqlite3_value_text16(argv[ 0 ]); + int len2 = sqlite3_value_bytes16(argv[ 1 ]); + const void* data2 = sqlite3_value_text16(argv[ 1 ]); + int len3 = sqlite3_value_bytes16(argv[ 2 ]); + const void* data3 = sqlite3_value_text16(argv[ 2 ]); + if (Q_LIKELY(data1 && data2 && data3)) { + + int pos = SKGServices::stringToInt(QString::fromRawData(reinterpret_cast(data3), len3 / sizeof(QChar))); + + QString string1(reinterpret_cast(data1), len1 / sizeof(QChar)); + QString string2 = QString::fromRawData(reinterpret_cast(data2), len2 / sizeof(QChar)); + + QRegExp pattern(string1, Qt::CaseInsensitive, QRegExp::RegExp2); + if (pattern.isValid()) { + pattern.indexIn(string2); + QByteArray output = pattern.capturedTexts().value(pos).toUtf8(); + sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); + } else { + sqlite3_result_error(context, pattern.errorString().toUtf8().constData(), -1); + } + } +} + /** * Custom sqlite function. */ static void upperFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); if (Q_LIKELY(data1)) { QByteArray output = QString::fromRawData(reinterpret_cast(data1), len1 / sizeof(QChar)).toUpper().toUtf8(); sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } } /** * Custom sqlite function. */ static void nextFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); if (Q_LIKELY(data1)) { QByteArray output = SKGServices::getNextString(QString::fromRawData(reinterpret_cast(data1), len1 / sizeof(QChar))).toUtf8(); sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } } /** * Custom sqlite function. */ static void lowerFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); if (Q_LIKELY(data1)) { QByteArray output = QString::fromRawData(reinterpret_cast(data1), len1 / sizeof(QChar)).toLower().toUtf8(); sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } } /** * Custom sqlite function. */ static void capitalizeFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) { int len1 = sqlite3_value_bytes16(argv[ 0 ]); const void* data1 = sqlite3_value_text16(argv[ 0 ]); if (Q_LIKELY(data1)) { QString str = QString::fromRawData(reinterpret_cast(data1), len1 / sizeof(QChar)); QByteArray output = (str.at(0).toUpper() + str.mid(1).toLower()).toUtf8(); sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); } } static SKGError addSqliteAddon(QSqlDatabase* iDb) { SKGError err; auto* sqlite_handle = iDb->driver()->handle().value(); if (sqlite_handle != nullptr) { sqlite3_create_function(sqlite_handle, "REGEXP", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, ®expFunction, nullptr, nullptr); + sqlite3_create_function(sqlite_handle, "REGEXPCAPTURE", 3, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, ®expCaptureFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "WILDCARD", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &wildcardFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "WORD", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &wordFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "TODATE", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &dateFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "TOFORMATTEDDATE", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &formattedDateFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "PERIOD", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &periodFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "SLEEP", 1, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &sleepFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "TOCURRENCY", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, ¤cyFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "XOR", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &xorFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "XORD", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &xordoubleFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "UPPER", 1, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &upperFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "LOWER", 1, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &lowerFunction, nullptr, nullptr); sqlite3_create_function(sqlite_handle, "NEXT", 1, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &nextFunction, nullptr, nullptr); int rc = sqlite3_create_function(sqlite_handle, "CAPITALIZE", 1, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &capitalizeFunction, nullptr, nullptr); if (rc != SQLITE_OK) { err = SKGError(SQLLITEERROR + rc, QStringLiteral("sqlite3_create_function failed")); } } else { SKGTRACE << "WARNING: Custom sqlite functions not added" << endl; } return err; } SKGDocument::SKGDocument() : d(new SKGDocumentPrivate()) { SKGTRACEINFUNC(10); // Set the QThreadPool // QThreadPool::globalInstance()->setMaxThreadCount(3*QThread::idealThreadCount()); // DBUS registration QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject(QStringLiteral("/skg/skgdocument"), this, QDBusConnection::ExportAllContents); dbus.registerService(QStringLiteral("org.skg")); // Initialisation of undoable tables SKGListNotUndoable.push_back(QStringLiteral("T.doctransaction")); SKGListNotUndoable.push_back(QStringLiteral("T.doctransactionitem")); SKGListNotUndoable.push_back(QStringLiteral("T.doctransactionmsg")); // Database unique identifier ++SKGDocumentPrivate::m_databaseUniqueIdentifier; d->m_databaseIdentifier = "SKGDATABASE_" % SKGServices::intToString(SKGDocumentPrivate::m_databaseUniqueIdentifier); // Initialisation of backup file parameters setBackupParameters(QLatin1String(""), QStringLiteral(".old")); // 320157 vvvv // Disable OS lock sqlite3_vfs* vfs = sqlite3_vfs_find("unix-none"); if (Q_LIKELY(vfs)) { sqlite3_vfs_register(vfs, 1); } else { SKGTRACE << "WARNING: Impossible to use the 'unix-none' vfs mode of sqlite3. Use:'" << sqlite3_vfs_find(nullptr)->zName << "'" << endl; } // 320157 ^^^^ } SKGDocument::~SKGDocument() { SKGTRACEINFUNC(10); close(); d->m_progressFunction = nullptr; d->m_progressData = nullptr; d->m_checkFunctions.clear(); for (auto w : qAsConst(d->m_watchers)) { delete w; } d->m_watchers.clear(); delete d->m_cacheSql; d->m_cacheSql = nullptr; delete d; d = nullptr; } QString SKGDocument::getUniqueIdentifier() const { return d->m_uniqueIdentifier; } QString SKGDocument::getDatabaseIdentifier() const { return d->m_databaseIdentifier; } SKGError SKGDocument::setProgressCallback(FuncProgress iProgressFunction, void* iData) { d->m_progressFunction = iProgressFunction; d->m_progressData = iData; return SKGError(); } SKGError SKGDocument::addEndOfTransactionCheck(SKGError(*iCheckFunction)(SKGDocument*)) { d->m_checkFunctions.append(iCheckFunction); return SKGError(); } SKGError SKGDocument::stepForward(int iPosition, const QString& iText) { SKGError err; // Increase the step for the last transaction if (Q_LIKELY(getDepthTransaction())) { d->m_posStepForTransaction.pop_back(); d->m_posStepForTransaction.push_back(iPosition); } // Check if a callback function exists if (Q_LIKELY(d->m_progressFunction)) { // YES ==> compute double min = 0; double max = 100; bool emitevent = true; auto nbIt = d->m_nbStepForTransaction.constBegin(); auto posIt = d->m_posStepForTransaction.constBegin(); for (; emitevent && nbIt != d->m_nbStepForTransaction.constEnd(); ++nbIt) { int p = *posIt; int n = *nbIt; if (Q_UNLIKELY(p < 0 || p > n)) { p = n; } if (Q_LIKELY(n != 0)) { double pmin = min; double pmax = max; min = pmin + (pmax - pmin) * (static_cast(p) / static_cast(n)); max = pmin + (pmax - pmin) * (static_cast(p + 1) / static_cast(n)); if (Q_UNLIKELY(max > 100)) { max = 100; } } else { emitevent = false; } ++posIt; } int posPercent = rint(min); // Call the call back if (emitevent) { d->m_inProgress = true; QString text; qint64 time = QDateTime::currentMSecsSinceEpoch() - d->m_timeBeginTransaction; if (Q_UNLIKELY(time > 3000)) { text = iText; if (text.isEmpty()) { text = d->m_nameForTransaction.at(d->m_nameForTransaction.count() - 1); } } if (Q_LIKELY(d->m_progressFunction(posPercent, time, text, d->m_progressData) != 0)) { err.setReturnCode(ERR_ABORT).setMessage(i18nc("User interrupted something that Skrooge was performing", "The current operation has been interrupted")); // Remove all untransactionnal messaged m_unTransactionnalMessages.clear(); } d->m_inProgress = false; } } return err; } SKGError SKGDocument::beginTransaction(const QString& iName, int iNbStep, const QDateTime& iDate, bool iRefreshViews) { SKGError err; SKGTRACEINFUNCRC(5, err); SKGTRACEL(10) << "Input parameter [name]=[" << iName << "] [nb step]=[" << iNbStep << "] [refresh]=[" << (iRefreshViews ? QStringLiteral("Y") : QStringLiteral("N")) << ']' << endl; bool overrideCursor = false; if (d->m_nbStepForTransaction.isEmpty()) { // Open SQLtransaction err = executeSqliteOrder(QStringLiteral("BEGIN;")); IFOK(err) { overrideCursor = true; // Create undo redo transaction err = executeSqliteOrder(QStringLiteral("insert into doctransaction (d_date, t_name, i_parent") % (!iRefreshViews ? ", t_refreshviews" : "") % ") values " "('" % SKGServices::timeToString(iDate) % "','" % SKGServices::stringToSqlString(iName) % "', " % SKGServices::intToString(getTransactionToProcess(SKGDocument::UNDO)) % (!iRefreshViews ? ",'N'" : "") % ");"); addValueInCache(QStringLiteral("SKG_REFRESH_VIEW"), (iRefreshViews ? QStringLiteral("Y") : QStringLiteral("N"))); d->m_currentTransaction = getTransactionToProcess(SKGDocument::UNDO); d->m_timeBeginTransaction = QDateTime::currentMSecsSinceEpoch(); } } else { // A transaction already exists // Check if the child transaction is a opened in the progress callback if (d->m_inProgress) { err.setReturnCode(ERR_FAIL).setMessage(i18nc("Something went wrong with SQL transactions", "A transaction cannot be started during execution of another one")); } } IFOK(err) { d->m_nbStepForTransaction.push_back(iNbStep); d->m_posStepForTransaction.push_back(iNbStep); QString n = iName; n = n.remove(QStringLiteral("#INTERNAL#")); if (n.isEmpty() && !d->m_nameForTransaction.isEmpty()) { n = d->m_nameForTransaction.at(d->m_nameForTransaction.count() - 1); } d->m_nameForTransaction.push_back(n); if (iNbStep > 0) { err = stepForward(0); } } else { executeSqliteOrder(QStringLiteral("ROLLBACK;")); } if (Q_LIKELY(overrideCursor && !err && qobject_cast(qApp) != nullptr)) { // clazy:excludeall=unneeded-cast QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } return err; } SKGError SKGDocument::checkExistingTransaction() const { SKGError err; if (d->m_nbStepForTransaction.isEmpty()) { err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "A transaction must be opened to do this action")); } return err; } SKGError SKGDocument::endTransaction(bool succeedded) { SKGError err; SKGError errOverwritten; SKGTRACEINFUNCRC(5, err); if (Q_UNLIKELY(d->m_nbStepForTransaction.empty())) { err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "Closing transaction failed because too many transactions ended")); } else { stepForward(d->m_nbStepForTransaction.at(d->m_nbStepForTransaction.count() - 1)); // =100% if (Q_LIKELY(d->m_nbStepForTransaction.size())) { // This test is needed. It is a security in some cases. d->m_nbStepForTransaction.pop_back(); d->m_posStepForTransaction.pop_back(); d->m_nameForTransaction.pop_back(); } QString currentTransactionString = SKGServices::intToString(getCurrentTransaction()); if (d->m_nbStepForTransaction.empty()) { QStringList listModifiedTables; // Check if (succeedded) { auto cachepointer = d->m_cache; d->m_cache = QHash(); for (auto check : qAsConst(d->m_checkFunctions)) { errOverwritten = check(this); IFKO(errOverwritten) { succeedded = false; SKGTRACEL(5) << "Transaction cancelled by a check" << endl; break; } } d->m_cache = cachepointer; } if (succeedded) { // Link items on current transaction IFOK(err) { err = executeSqliteOrder("UPDATE doctransactionitem set rd_doctransaction_id=" % currentTransactionString % " WHERE rd_doctransaction_id=0;"); } // Optimization of the current transaction IFOK(err) { SKGStringListList listTmp; err = executeSelectSqliteOrder("SELECT count(1) FROM doctransactionitem where rd_doctransaction_id=" % currentTransactionString, listTmp); IFOK(err) { int nbItem = SKGServices::stringToInt(listTmp.at(1).at(0)); if (nbItem == 0) { // Optimization is needed // Get non hidden messages SKGMessageList messages; getMessages(getCurrentTransaction(), messages, false); // Delete current transaction err = executeSqliteOrder("DELETE FROM doctransaction WHERE id=" % currentTransactionString); int nb = messages.count(); for (int i = 0; i < nb; ++i) { m_unTransactionnalMessages.push_back(messages.at(i)); } } } } // Optimization 2: remove duplicate orders IFOK(err) { QString wc = "DELETE FROM doctransactionitem WHERE id IN " "(SELECT a.id FROM doctransactionitem a INDEXED BY idx_doctransactionitem_optimization, doctransactionitem b INDEXED BY idx_doctransactionitem_optimization " "WHERE a.rd_doctransaction_id=" % currentTransactionString % " AND b.rd_doctransaction_id=" % currentTransactionString % " AND a.i_object_id=b.i_object_id AND a.t_object_table=b.t_object_table AND b.t_action=a.t_action AND b.t_sqlorder=a.t_sqlorder AND a.id>b.id );"; err = executeSqliteOrder(wc); } // Get current transaction information to be able to emit envent in case of SKG_UNDO_MAX_DEPTH=0 IFOK(err) { err = this->getDistinctValues(QStringLiteral("doctransactionitem"), QStringLiteral("t_object_table"), "rd_doctransaction_id=" % currentTransactionString, listModifiedTables); } // Remove oldest transaction IFOK(err) { QString maxdepthstring = getParameter(QStringLiteral("SKG_UNDO_MAX_DEPTH")); if (maxdepthstring.isEmpty()) { maxdepthstring = QStringLiteral("-1"); } int maxdepth = SKGServices::stringToInt(maxdepthstring); if (maxdepth >= 0) { err = executeSqliteOrder("delete from doctransaction where id in (select id from doctransaction limit max(0,((select count(1) from doctransaction)-(" % maxdepthstring % "))))"); } } // Remove SKGDocument::REDO transactions if we are not in a undo / redo transaction if (d->m_inundoRedoTransaction == 0) { int i = 0; while (((i = getTransactionToProcess(SKGDocument::REDO)) != 0) && !err) { err = executeSqliteOrder("delete from doctransaction where id=" % SKGServices::intToString(i)); } } // Commit the transaction IFOK(err) { err = executeSqliteOrder(QStringLiteral("COMMIT;")); } } // clean cache sql (must be done before event emit) d->m_cacheSql->clear(); if (!succeedded || err) { // Rollback the transaction SKGError err2 = executeSqliteOrder(QStringLiteral("ROLLBACK;")); // delete the transaction IFOKDO(err2, executeSqliteOrder("delete from doctransaction where id=" % currentTransactionString)); if (err2) { err.addError(err2.getReturnCode(), err2.getMessage()); } } else { // For better performance, events are submitted only for the first recursive undo if (Q_UNLIKELY(d->m_inundoRedoTransaction <= 1)) { // Is it a light transaction? bool lightTransaction = (getCachedValue(QStringLiteral("SKG_REFRESH_VIEW")) != QStringLiteral("Y")); // Emit modification events QStringList tablesRefreshed; tablesRefreshed.reserve(listModifiedTables.count()); for (const auto& table : qAsConst(listModifiedTables)) { Q_EMIT tableModified(table, getCurrentTransaction(), lightTransaction); tablesRefreshed.push_back(table); } // Remove temporary transaction if needed IFOKDO(err, executeSqliteOrder(QStringLiteral("delete from doctransaction where t_name LIKE '#INTERNAL#%';"))) Q_EMIT tableModified(QStringLiteral("doctransaction"), getCurrentTransaction(), lightTransaction); Q_EMIT tableModified(QStringLiteral("doctransactionitem"), getCurrentTransaction(), lightTransaction); // WARNING: list is modified during treatement for (int i = 0; !err && i < listModifiedTables.count(); ++i) { QString table = listModifiedTables.at(i); QStringList toAdd = getImpactedViews(table); int nbToAdd = toAdd.count(); for (int j = 0; !err && j < nbToAdd; ++j) { const QString& toAddTable = toAdd.at(j); if (!listModifiedTables.contains(toAddTable)) { // Compute materialized view of modified table if (!lightTransaction) { err = computeMaterializedViews(toAddTable); } listModifiedTables.push_back(toAddTable); } } } // Emit events for (int i = tablesRefreshed.count(); i < listModifiedTables.count(); ++i) { Q_EMIT tableModified(listModifiedTables.at(i), 0, lightTransaction); } Q_EMIT transactionSuccessfullyEnded(getCurrentTransaction()); } } // clean cache d->m_cache.clear(); d->m_currentTransaction = 0; if (Q_LIKELY(qobject_cast(qApp) != nullptr)) { // clazy:excludeall=unneeded-cast QApplication::restoreOverrideCursor(); } } } IFOK(err) { err = errOverwritten; } return err; } SKGError SKGDocument::removeAllTransactions() { SKGError err; SKGTRACEINFUNCRC(10, err); // Check if a transaction is still opened err = checkExistingTransaction(); IFOK(err) err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "Remove of transactions is forbidden inside a transaction")); else { err = SKGDocument::beginTransaction(QStringLiteral("#INTERNAL#")); IFOKDO(err, executeSqliteOrder(QStringLiteral("delete from doctransaction"))) SKGENDTRANSACTION(this, err); // Force the save d->m_lastSavedTransaction = -1; } return err; } SKGError SKGDocument::computeMaterializedViews(const QString& iTable) const { SKGError err; SKGTRACEINFUNCRC(5, err); // Compute additional where clause QStringList tables; if (d->m_MaterializedViews.contains(iTable)) { tables = d->m_MaterializedViews[iTable]; } else { QString wc; if (!iTable.isEmpty()) { QString t = iTable; if (t.startsWith(QLatin1String("v_"))) { t.replace(0, 2, QStringLiteral("vm_")); } wc = " AND name='" % t % '\''; } // Get list of materialized table err = getDistinctValues(QStringLiteral("sqlite_master"), QStringLiteral("name"), "type='table' AND name LIKE 'vm_%' " % wc, tables); d->m_MaterializedViews[iTable] = tables; } // Refresh tables int nb = tables.count(); for (int i = 0; !err && i < nb; ++i) { const QString& table = tables.at(i); QString view = table; view.replace(0, 3, QStringLiteral("v_")); // Remove previous table { SKGTRACEINRC(5, "SKGDocument::computeMaterializedViews-drop-" % table, err); err = executeSqliteOrder("DROP TABLE IF EXISTS " % table); } { // Recreate table SKGTRACEINRC(5, "SKGDocument::computeMaterializedViews-create-" % table, err); IFOKDO(err, executeSqliteOrder("CREATE TABLE " % table % " AS SELECT * FROM " % view)) } } return err; } SKGError SKGDocument::sendMessage(const QString& iMessage, MessageType iMessageType, const QString& iAction) { SKGError err; SKGTRACEINFUNCRC(10, err); // Associate message with transaction if (!checkExistingTransaction()) { SKGObjectBase msg(this, QStringLiteral("doctransactionmsg")); err = msg.setAttribute(QStringLiteral("rd_doctransaction_id"), SKGServices::intToString(getCurrentTransaction())); IFOKDO(err, msg.setAttribute(QStringLiteral("t_message"), iMessage)) IFOKDO(err, msg.setAttribute(QStringLiteral("t_type"), iMessageType == SKGDocument::Positive ? QStringLiteral("P") : iMessageType == SKGDocument::Information ? QStringLiteral("I") : iMessageType == SKGDocument::Warning ? QStringLiteral("W") : iMessageType == SKGDocument::Error ? QStringLiteral("E") : QStringLiteral("H"))); IFOKDO(err, msg.save()) } if (checkExistingTransaction() || !iAction.isEmpty()) { // Addition message in global variable in case of no transaction opened bool found = false; for (const auto& m : qAsConst(m_unTransactionnalMessages)) { if (m.Text == iMessage) { found = true; } } if (iMessageType != SKGDocument::Hidden && !found) { SKGMessage m; m.Text = iMessage; m.Type = iMessageType; m.Action = iAction; m_unTransactionnalMessages.push_back(m); } } return err; } SKGError SKGDocument::removeMessages(int iIdTransaction) { SKGError err; SKGTRACEINFUNCRC(10, err); if (!checkExistingTransaction()) { err = executeSqliteOrder("DELETE FROM doctransactionmsg WHERE rd_doctransaction_id=" % SKGServices::intToString(iIdTransaction)); } m_unTransactionnalMessages.clear(); return err; } SKGError SKGDocument::getMessages(int iIdTransaction, SKGMessageList& oMessages, bool iAll) { SKGError err; SKGTRACEINFUNCRC(10, err); oMessages = m_unTransactionnalMessages; SKGStringListList listTmp; err = executeSelectSqliteOrder( QStringLiteral("SELECT t_message, t_type FROM doctransactionmsg WHERE ") % (iAll ? "" : "t_type<>'H' AND ") % "rd_doctransaction_id=" % SKGServices::intToString(iIdTransaction) % " ORDER BY id ASC", listTmp); int nb = listTmp.count(); for (int i = 1; !err && i < nb ; ++i) { QString msg = listTmp.at(i).at(0); QString type = listTmp.at(i).at(1); bool found = false; for (const auto& m : qAsConst(m_unTransactionnalMessages)) { if (m.Text == msg) { found = true; } } if (!found) { SKGMessage m; m.Text = msg; m.Type = type == QStringLiteral("P") ? SKGDocument::Positive : type == QStringLiteral("I") ? SKGDocument::Information : type == QStringLiteral("W") ? SKGDocument::Warning : type == QStringLiteral("E") ? SKGDocument::Error : SKGDocument::Hidden; oMessages.push_back(m); } } m_unTransactionnalMessages.clear(); return err; } SKGError SKGDocument::getModifications(int iIdTransaction, SKGObjectModificationList& oModifications) const { SKGError err; SKGTRACEINFUNCRC(10, err); oModifications.clear(); SKGStringListList listTmp; err = executeSelectSqliteOrder( "SELECT i_object_id,t_object_table,t_action FROM doctransactionitem WHERE rd_doctransaction_id=" % SKGServices::intToString(iIdTransaction) % " ORDER BY id ASC", listTmp); int nb = listTmp.count(); for (int i = 1; !err && i < nb ; ++i) { SKGObjectModification mod; mod.id = SKGServices::stringToInt(listTmp.at(i).at(0)); mod.table = listTmp.at(i).at(1); QString type = listTmp.at(i).at(2); mod.type = (type == QStringLiteral("D") ? I : (type == QStringLiteral("I") ? D : U)); // Normal because in database we have to sql order to go back. mod.uuid = listTmp.at(i).at(0) % '-' % mod.table; oModifications.push_back(mod); } return err; } QStringList SKGDocument::getImpactedViews(const QString& iTable) const { SKGTRACEINFUNC(10); if (Q_UNLIKELY(d->m_ImpactedViews.isEmpty())) { // Get list of tables and views QStringList tables; SKGStringListList result; executeSelectSqliteOrder(QStringLiteral("SELECT tbl_name FROM sqlite_master WHERE tbl_name NOT LIKE '%_delete' AND type IN ('table', 'view')"), result); int nb = result.count(); tables.reserve(nb); for (int i = 1; i < nb; ++i) { tables.push_back(result.at(i).at(0)); } // First computation executeSelectSqliteOrder(QStringLiteral("SELECT tbl_name, sql FROM sqlite_master WHERE tbl_name NOT LIKE '%_delete' AND type='view'"), result); nb = result.count(); for (int i = 1; i < nb; ++i) { const QStringList& line = result.at(i); const QString& name = line.at(0); const QString& sql = line.at(1); QStringList words = SKGServices::splitCSVLine(sql, ' ', false); words.push_back(QStringLiteral("parameters")); int nbWords = words.count(); for (int j = 0; j < nbWords; ++j) { QString word = words.at(j); word = word.remove(','); if (word.startsWith(QLatin1String("vm_"))) { word.replace(0, 3, QStringLiteral("v_")); } if (word != name && tables.contains(word, Qt::CaseInsensitive)) { QStringList l = d->m_ImpactedViews.value(word); if (!l.contains(name)) { l.push_back(name); } d->m_ImpactedViews[word] = l; } } } // Now, we have some thing like this // d->m_ImpactedViews[A]={ B, C, D} // d->m_ImpactedViews[B]={ E, F} // We must build d->m_ImpactedViews[A]={ B, C, D, E, F} QStringList keys = d->m_ImpactedViews.keys(); for (const auto& k : qAsConst(keys)) { QStringList l = d->m_ImpactedViews.value(k); for (int i = 0; i < l.count(); ++i) { // Warning: the size of l will change in the loop QString item = l.at(i); if (d->m_ImpactedViews.contains(item)) { // No qAsConst here, item is already const for (const auto& name : d->m_ImpactedViews.value(item)) { if (!l.contains(name)) { l.push_back(name); } } } } d->m_ImpactedViews[k] = l; } } return d->m_ImpactedViews.value(iTable); } SKGError SKGDocument::groupTransactions(int iFrom, int iTo) { SKGError err; SKGTRACEINFUNCRC(5, err); ++d->m_inundoRedoTransaction; // It is a kind of undo redo // Check if a transaction is still opened err = checkExistingTransaction(); IFOK(err) err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "Creation of a group of transactions is forbidden inside a transaction")); else { int iidMaster = qMax(iFrom, iTo); QString smin = SKGServices::intToString(qMin(iFrom, iTo)); QString smax = SKGServices::intToString(iidMaster); // Get transaction SKGStringListList transactions; err = executeSelectSqliteOrder( QStringLiteral("SELECT id, t_name, t_mode, i_parent FROM doctransaction WHERE id BETWEEN ") % smin % " AND " % smax % " ORDER BY id ASC", transactions); // Check and get main parameter for the group int nb = transactions.count(); QString transactionMode; QString communParent; QString name; for (int i = 1; !err && i < nb; ++i) { // We forget header const QStringList& transaction = transactions.at(i); const QString& mode = transaction.at(2); if (!name.isEmpty()) { name += ','; } name += transaction.at(1); if (!transactionMode.isEmpty() && mode != transactionMode) { err = SKGError(ERR_INVALIDARG, QStringLiteral("Undo and Redo transactions cannot be grouped")); } else { transactionMode = mode; } if (i == 1) { communParent = transaction.at(3); } } // Group IFOK(err) { err = SKGDocument::beginTransaction(QStringLiteral("#INTERNAL#")); // Group items IFOKDO(err, executeSqliteOrder( QStringLiteral("UPDATE doctransactionitem set rd_doctransaction_id=") % smax % " where rd_doctransaction_id BETWEEN " % smin % " AND " % smax)); IFOKDO(err, executeSqliteOrder( QStringLiteral("UPDATE doctransaction set i_parent=") % communParent % ", t_name='" % SKGServices::stringToSqlString(name) % "' where id=" % smax)); IFOKDO(err, executeSqliteOrder( QStringLiteral("DELETE FROM doctransaction WHERE id BETWEEN ") % smin % " AND " % SKGServices::intToString(qMax(iFrom, iTo) - 1))); SKGENDTRANSACTION(this, err); } } --d->m_inundoRedoTransaction; return err; } SKGError SKGDocument::undoRedoTransaction(UndoRedoMode iMode) { SKGError err; SKGTRACEINFUNCRC(5, err); // Check if a transaction is still opened err = checkExistingTransaction(); IFOK(err) err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "Undo / Redo is forbidden inside a transaction")); else { if (iMode == SKGDocument::UNDOLASTSAVE) { // Create group SKGStringListList transactions; err = executeSelectSqliteOrder( QStringLiteral("SELECT id, t_savestep FROM doctransaction WHERE t_mode='U' ORDER BY id DESC"), transactions); int nb = transactions.count(); int min = 0; int max = 0; for (int i = 1; !err && i < nb; ++i) { const QStringList& transaction = transactions.at(i); if (i == 1) { max = SKGServices::stringToInt(transaction.at(0)); } if (i != 1 && transaction.at(1) == QStringLiteral("Y")) { break; } min = SKGServices::stringToInt(transaction.at(0)); } if (min == 0) { min = max; } if (!err && min != max && min != 0) { err = groupTransactions(min, max); } } else { err = SKGError(); // To ignore error generated by checkExistingTransaction. } // Get ID of the transaction to undo IFOK(err) { QString name; bool saveStep = false; QDateTime date; bool refreshViews; int id = getTransactionToProcess(iMode, &name, &saveStep, &date, &refreshViews); if (id == 0) { // No transaction found ==> generate an error err = SKGError(ERR_INVALIDARG, QStringLiteral("No transaction found. Undo / Redo impossible.")); } else { // Undo transaction SKGTRACEL(5) << "Undoing transaction [" << id << "]- [" << name << "]..." << endl; SKGStringListList listSqlOrder; err = executeSelectSqliteOrder( "SELECT t_sqlorder FROM doctransactionitem WHERE rd_doctransaction_id=" % SKGServices::intToString(id) % " ORDER BY id DESC", listSqlOrder); IFOK(err) { int nb = listSqlOrder.count(); err = SKGDocument::beginTransaction(name, nb + 3, date, refreshViews); IFOK(err) { ++d->m_inundoRedoTransaction; // Because we will be in a undo/redo transaction // Normal the first element is ignored because it is the header for (int i = 1; !err && i < nb ; ++i) { err = executeSqliteOrder(listSqlOrder.at(i).at(0)); IFOKDO(err, stepForward(i)) } IFOK(err) { // Set the NEW transaction in redo mode int lastredo = getTransactionToProcess((iMode == SKGDocument::UNDO || iMode == SKGDocument::UNDOLASTSAVE ? SKGDocument::REDO : SKGDocument::UNDO)); int newredo = getTransactionToProcess(iMode); IFOKDO(err, executeSqliteOrder( QStringLiteral("UPDATE doctransaction set t_mode=") % (iMode == SKGDocument::UNDO || iMode == SKGDocument::UNDOLASTSAVE ? QStringLiteral("'R'") : QStringLiteral("'U'")) % ", i_parent=" % SKGServices::intToString(lastredo) % " where id=" % SKGServices::intToString(newredo))); IFOKDO(err, stepForward(nb)) // Move messages from previous transaction to new one IFOKDO(err, executeSqliteOrder( "UPDATE doctransactionmsg set rd_doctransaction_id=" % SKGServices::intToString(getCurrentTransaction()) % " where rd_doctransaction_id=" % SKGServices::intToString(id))); IFOKDO(err, stepForward(nb + 1)) // delete treated transaction IFOKDO(err, executeSqliteOrder( "DELETE from doctransaction where id=" % SKGServices::intToString(id))); IFOKDO(err, stepForward(nb + 2)) // Check that new transaction has exactly the same number of item /* IFOK (err) { SKGStringListList listSqlOrder; err=executeSelectSqliteOrder( "SELECT count(1) FROM doctransactionitem WHERE rd_doctransaction_id=" % SKGServices::intToString(getCurrentTransaction()), listSqlOrder); if (!err && SKGServices::stringToInt(listSqlOrder.at(1).at(0))!=nb-1) { err=SKGError(ERR_ABORT, i18nc("Error message", "Invalid number of item after undo/redo. Expected (%1) != Result (%2)",nb-1,listSqlOrder.at(1).at(0))); } }*/ IFOKDO(err, stepForward(nb + 3)) } SKGENDTRANSACTION(this, err); --d->m_inundoRedoTransaction; // We left the undo / redo transaction } } } } } return err; } int SKGDocument::getDepthTransaction() const { return d->m_nbStepForTransaction.size(); } int SKGDocument::getNbTransaction(UndoRedoMode iMode) const { SKGTRACEINFUNC(10); int output = 0; if (Q_LIKELY(getMainDatabase())) { QString sqlorder = QStringLiteral("select count(1) from doctransaction where t_mode='"); sqlorder += (iMode == SKGDocument::UNDO || iMode == SKGDocument::UNDOLASTSAVE ? QStringLiteral("U") : QStringLiteral("R")); sqlorder += '\''; QSqlQuery query = getMainDatabase()->exec(sqlorder); if (query.next()) { output = query.value(0).toInt(); } } return output; } int SKGDocument::getTransactionToProcess(UndoRedoMode iMode, QString* oName, bool* oSaveStep, QDateTime* oDate, bool* oRefreshViews) const { SKGTRACEINFUNC(10); // initialisation int output = 0; if (oName != nullptr) { *oName = QLatin1String(""); } if (Q_LIKELY(getMainDatabase())) { QString sqlorder = QStringLiteral("select A.id , A.t_name, A.t_savestep, A.d_date, A.t_refreshviews from doctransaction A where " "NOT EXISTS(select 1 from doctransaction B where B.i_parent=A.id) " "and A.t_mode='"); sqlorder += (iMode == SKGDocument::UNDO || iMode == SKGDocument::UNDOLASTSAVE ? QStringLiteral("U") : QStringLiteral("R")); sqlorder += '\''; QSqlQuery query = getMainDatabase()->exec(sqlorder); if (query.next()) { output = query.value(0).toInt(); if (oName != nullptr) { *oName = query.value(1).toString(); } if (oSaveStep != nullptr) { *oSaveStep = (query.value(2).toString() == QStringLiteral("Y")); } if (oDate != nullptr) { *oDate = SKGServices::stringToTime(query.value(3).toString()); } if (oRefreshViews != nullptr) { *oRefreshViews = (query.value(4).toString() == QStringLiteral("Y")); } } } return output; } int SKGDocument::getCurrentTransaction() const { SKGTRACEINFUNC(10); return d->m_currentTransaction; } QString SKGDocument::getPassword() const { if (!d->m_password_got) { d->m_password = getParameter(QStringLiteral("SKG_PASSWORD")); d->m_password_got = true; } return d->m_password; } SKGError SKGDocument::changePassword(const QString& iNewPassword) { SKGError err; SKGTRACEINFUNCRC(10, err); IFOK(checkExistingTransaction()) err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "Change password is forbidden inside a transaction")); else { IFOKDO(err, executeSqliteOrder("PRAGMA REKEY = '" % SKGServices::stringToSqlString(iNewPassword.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : iNewPassword) % "'")) IFOKDO(err, beginTransaction(QStringLiteral("#INTERNAL#"), 0, QDateTime::currentDateTime(), false)) IFOKDO(err, setParameter(QStringLiteral("SKG_PASSWORD"), iNewPassword)) IFOKDO(err, setParameter(QStringLiteral("SKG_PASSWORD_LASTUPDATE"), SKGServices::dateToSqlString(QDate::currentDate()))) IFOKDO(err, sendMessage(iNewPassword.isEmpty() ? i18nc("Inform the user that the password protection was removed", "The document password has been removed.") : i18nc("Inform the user that the password was successfully changed", "The document password has been modified."), SKGDocument::Positive)) SKGENDTRANSACTION(this, err); // Force the save IFOK(err) { d->m_lastSavedTransaction = -1; d->m_password = iNewPassword; d->m_password_got = true; // Close all thread connection auto conNameMainConnection = getMainDatabase()->connectionName(); const auto conNames = QSqlDatabase::connectionNames(); for (const auto& conName : conNames) { if (conName.startsWith(conNameMainConnection % "_")) { /* NO NEED { auto con = QSqlDatabase::database(conName, false); con.close(); }*/ QSqlDatabase::removeDatabase(conName); } } } } return err; } SKGError SKGDocument::setLanguage(const QString& iLanguage) { SKGError err; SKGTRACEINFUNCRC(5, err); QString previousLanguage = getParameter(QStringLiteral("SKG_LANGUAGE")); if (previousLanguage != iLanguage) { // Save language into the document IFOKDO(err, beginTransaction(QStringLiteral("#INTERNAL#"), 0, QDateTime::currentDateTime(), false)) IFOKDO(err, setParameter(QStringLiteral("SKG_LANGUAGE"), iLanguage)) // Migrate view for new language IFOKDO(err, refreshViewsIndexesAndTriggers()) // close temporary transaction SKGENDTRANSACTION(this, err); } return err; } SKGError SKGDocument::initialize() { SKGError err; SKGTRACEINFUNCRC(5, err); err = load(QLatin1String(""), QLatin1String("")); return err; } SKGError SKGDocument::recover(const QString& iName, const QString& iPassword, QString& oRecoveredFile) { SKGError err; SKGTRACEINFUNCRC(5, err); SKGTRACEL(10) << "Input parameter [name]=[" << iName << ']' << endl; QString sqliteFile = QString(iName % "_recovered.sqlite").replace(QStringLiteral(".skg_"), QStringLiteral("_")); oRecoveredFile = QString(iName % "_recovered.skg").replace(QStringLiteral(".skg_"), QStringLiteral("_")); bool mode; err = SKGServices::cryptFile(iName, sqliteFile, iPassword, false, getDocumentHeader(), mode); IFOK(err) { QFile(oRecoveredFile).remove(); QString cmd = "echo .dump | sqlite3 \"" % sqliteFile % "\" | sed -e 's/ROLLBACK; -- due to errors/COMMIT;/g' | sqlite3 \"" % oRecoveredFile % '"'; QProcess p; p.start(QStringLiteral("sh"), QStringList() << QStringLiteral("-c") << cmd); if (!p.waitForFinished(1000 * 60 * 2) || p.exitCode() != 0) { err.setReturnCode(ERR_FAIL).setMessage(i18nc("Error message", "The following command line failed with code %2:\n'%1'", cmd, p.exitCode())); } // Try to load the recovered file IFOKDO(err, load(oRecoveredFile, QLatin1String(""))); IFOK(err) { SKGBEGINTRANSACTION(*this, i18nc("Noun", "Recovery"), err); IFOKDO(err, refreshViewsIndexesAndTriggers(true)) } IFOKDO(err, save()) // Reset the current document initialize(); // Clean useless file IFOK(err) { // We keep only the recovered QFile(sqliteFile).remove(); } else { // We keep the sqlite file in case of QFile(oRecoveredFile).remove(); err.addError(ERR_FAIL, i18nc("Error message", "Impossible to recover this file")); } } return err; } SKGError SKGDocument::load(const QString& iName, const QString& iPassword, bool iRestoreTmpFile, bool iForceReadOnly) { // Close previous document SKGError err; SKGTRACEINFUNCRC(5, err); SKGTRACEL(10) << "Input parameter [name]=[" << iName << ']' << endl; SKGTRACEL(10) << "Input parameter [iRestoreTmpFile]=[" << (iRestoreTmpFile ? "TRUE" : "FALSE") << ']' << endl; SKGTRACEL(10) << "Input parameter [iForceReadOnly]=[" << (iForceReadOnly ? "TRUE" : "FALSE") << ']' << endl; d->m_lastSavedTransaction = -1; // To avoid double event emission d->m_modeSQLCipher = true; d->m_blockEmits = true; err = close(); d->m_blockEmits = false; IFOK(err) { if (!iName.isEmpty()) { // File exist QFileInfo fi(iName); d->m_modeReadOnly = iForceReadOnly || !fi.permission(QFile::WriteUser); // Temporary file d->m_temporaryFile = SKGDocument::getTemporaryFile(iName, d->m_modeReadOnly); bool temporaryFileExisting = QFile(d->m_temporaryFile).exists(); SKGTRACEL(10) << "Temporary file: [" << d->m_temporaryFile << ']' << endl; SKGTRACEL(10) << "Temporary file existing: [" << (temporaryFileExisting ? "TRUE" : "FALSE") << ']' << endl; if (!iRestoreTmpFile || !temporaryFileExisting) { SKGTRACEL(10) << "Create the temporary file" << endl; QFile::remove(d->m_temporaryFile); // Must remove it to be able to copy err = SKGServices::cryptFile(iName, d->m_temporaryFile, iPassword, false, getDocumentHeader(), d->m_modeSQLCipher); } else { SKGTRACEL(10) << "The temporary file is existing, try a restore but we must check if the file is password protected first" << endl; // 249955: Check if password protected vvv // Temporary file will be loaded but first we must check if original document is password protected QString temporaryFile2 = d->m_temporaryFile % '2'; err = SKGServices::cryptFile(iName, temporaryFile2, iPassword, false, getDocumentHeader(), d->m_modeSQLCipher); // Try an open to check if well descrypted IFOK(err) { QSqlDatabase tryOpen(QSqlDatabase::addDatabase(SQLDRIVERNAME, QStringLiteral("tryOpen"))); tryOpen.setDatabaseName(temporaryFile2); if (!tryOpen.open()) { // Set error message QSqlError sqlErr = tryOpen.lastError(); err = SKGError(SQLLITEERROR + sqlErr.number(), sqlErr.text()); } if (d->m_modeSQLCipher) { IFOKDO(err, SKGServices::executeSqliteOrder(tryOpen, "PRAGMA KEY = '" % SKGServices::stringToSqlString(iPassword.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : iPassword) % "'")); IFKO(err) { SKGTRACEL(10) << "Wrong installation of sqlcipher (doesn't support encryption)" << endl; err = SKGError(ERR_ENCRYPTION, i18nc("Error message", "Wrong installation")); } // Migrate to the last version of SQLCipher IFOKDO(err, SKGServices::executeSqliteOrder(tryOpen, QStringLiteral("PRAGMA cipher_migrate"))); // Test the password IFOKDO(err, SKGServices::executeSqliteOrder(tryOpen, QStringLiteral("SELECT count(*) FROM sqlite_master"))); IFKO(err) { SKGTRACEL(10) << "Wrong password in restore mode" << endl; err = SKGError(ERR_ENCRYPTION, i18nc("Error message", "Wrong password")); } } IFOKDO(err, SKGServices::executeSqliteOrder(tryOpen, QStringLiteral("PRAGMA synchronous = OFF"))) } QSqlDatabase::removeDatabase(QStringLiteral("tryOpen")); QFile::remove(temporaryFile2); // To avoid deletion of temporary file during next try IFKO(err) d->m_temporaryFile = QLatin1String(""); // 249955: Check if password protected ^^^ } // Create file database IFOK(err) { d->m_currentDatabase = QSqlDatabase::addDatabase(SQLDRIVERNAME, d->m_databaseIdentifier); d->m_currentDatabase.setDatabaseName(d->m_temporaryFile); if (!d->m_currentDatabase.open()) { // Set error message QSqlError sqlErr = d->m_currentDatabase.lastError(); err = SKGError(SQLLITEERROR + sqlErr.number(), sqlErr.text()); } d->m_directAccessDb = true; if (QUrl::fromUserInput(iName).isLocalFile()) { d->m_currentFileName = iName; } } } else { // Temporary file d->m_temporaryFile = QDir::tempPath() % "/skg_" % QUuid::createUuid().toString() % ".skg"; // Create memory database d->m_currentDatabase = QSqlDatabase::addDatabase(SQLDRIVERNAME, d->m_databaseIdentifier); d->m_currentDatabase.setConnectOptions(QStringLiteral("QSQLITE_OPEN_URI")); d->m_currentDatabase.setDatabaseName(QStringLiteral("file:") + d->m_databaseIdentifier + QStringLiteral("?mode=memory&cache=shared")); if (!d->m_currentDatabase.open()) { // Set error message QSqlError sqlErr = d->m_currentDatabase.lastError(); err = SKGError(SQLLITEERROR + sqlErr.number(), sqlErr.text()); } d->m_directAccessDb = false; } if (d->m_modeSQLCipher) { // This is an encrypted data base IFOKDO(err, executeSqliteOrder("PRAGMA KEY = '" % SKGServices::stringToSqlString(iPassword.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : iPassword) % "'")); IFKO(err) { SKGTRACEL(10) << "Wrong installation of sqlcipher (doesn't support encryption)" << endl; err = SKGError(ERR_ENCRYPTION, i18nc("Error message", "Wrong installation")); } // Migrate to the last version of SQLCipher IFOKDO(err, executeSqliteOrder(QStringLiteral("PRAGMA cipher_migrate"))); // Test the password IFOKDO(err, executeSqliteOrder(QStringLiteral("SELECT count(*) FROM sqlite_master"))); IFKO(err) { SKGTRACEL(10) << "Wrong password on temporary file" << endl; err = SKGError(ERR_ENCRYPTION, i18nc("Error message", "Wrong password")); } } // Check if the database is correct IFOK(err) { IFOKDO(err, executeSqliteOrder(QStringLiteral("PRAGMA journal_mode=MEMORY"))) IFKO(err) { err.addError(ERR_CORRUPTION, i18nc("Error message", "Oups, this file seems to be corrupted")); } } // Optimization QStringList optimization; optimization << QStringLiteral("PRAGMA case_sensitive_like=true") << QStringLiteral("PRAGMA journal_mode=MEMORY") << QStringLiteral("PRAGMA temp_store=MEMORY") // << QStringLiteral("PRAGMA locking_mode=EXCLUSIVE") << QStringLiteral("PRAGMA synchronous = OFF") << QStringLiteral("PRAGMA legacy_alter_table=ON") // For migration on sqlite >=3.25 (see https://sqlite.org/lang_altertable.html) << QStringLiteral("PRAGMA recursive_triggers=true"); IFOKDO(err, executeSqliteOrders(optimization)) // Add custom sqlite functions IFOKDO(err, addSqliteAddon(getMainDatabase())); if (!d->m_directAccessDb) { // Create parameter and undo redo table /** * This constant is used to initialized the data model (table creation) */ QStringList InitialDataModel; // ================================================================== // Table parameters InitialDataModel << QStringLiteral("CREATE TABLE parameters " "(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," "t_uuid_parent TEXT NOT NULL DEFAULT ''," "t_name TEXT NOT NULL," "t_value TEXT NOT NULL DEFAULT ''," "b_blob BLOB," "d_lastmodifdate DATE NOT NULL DEFAULT CURRENT_TIMESTAMP," "i_tmp INTEGER NOT NULL DEFAULT 0" ")") // ================================================================== // Table node << "CREATE TABLE node (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," "t_name TEXT NOT NULL DEFAULT '' CHECK (t_name NOT LIKE '%" % OBJECTSEPARATOR % "%')," "t_fullname TEXT," "t_icon TEXT DEFAULT ''," "f_sortorder FLOAT," "t_autostart VARCHAR(1) DEFAULT 'N' CHECK (t_autostart IN ('Y', 'N'))," "t_data TEXT," "rd_node_id INT CONSTRAINT fk_id REFERENCES node(id) ON DELETE CASCADE)" // ================================================================== // Table doctransaction << QStringLiteral("CREATE TABLE doctransaction (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," "t_name TEXT NOT NULL," "t_mode VARCHAR(1) DEFAULT 'U' CHECK (t_mode IN ('U', 'R'))," "d_date DATE NOT NULL," "t_savestep VARCHAR(1) DEFAULT 'N' CHECK (t_savestep IN ('Y', 'N'))," "t_refreshviews VARCHAR(1) DEFAULT 'Y' CHECK (t_refreshviews IN ('Y', 'N'))," "i_parent INTEGER)") // ================================================================== // Table doctransactionitem << QStringLiteral("CREATE TABLE doctransactionitem (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " "rd_doctransaction_id INTEGER NOT NULL," "i_object_id INTEGER NOT NULL," "t_object_table TEXT NOT NULL," "t_action VARCHAR(1) DEFAULT 'I' CHECK (t_action IN ('I', 'U', 'D'))," "t_sqlorder TEXT NOT NULL DEFAULT '')") << QStringLiteral("CREATE TABLE doctransactionmsg (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " "rd_doctransaction_id INTEGER NOT NULL," "t_message TEXT NOT NULL DEFAULT ''," "t_type VARCHAR(1) DEFAULT 'I' CHECK (t_type IN ('P', 'I', 'W', 'E', 'H')))"); // Positive, Information, Warning, Error, Hidden IFOKDO(err, executeSqliteOrders(InitialDataModel)) IFOKDO(err, SKGDocument::refreshViewsIndexesAndTriggers()) } } // migrate IFOK(err) { bool mig = false; err = migrate(mig); if (!err && getParameter(QStringLiteral("SKG_DATABASE_TYPE")) != SQLDRIVERNAME && !getPassword().isEmpty()) { err = sendMessage(i18nc("Information message", "This document is protected by a password but the database is still in SQLite mode.\nDo you know that the SQLCipher mode is more secured because even the temporary file is encrypted?"), SKGDocument::Warning, QStringLiteral("skg://migrate_sqlcipher")); } if (!err && getParameter(QStringLiteral("SKG_PASSWORD_LASTUPDATE")) == QLatin1String("") && !getPassword().isEmpty()) { err = sendMessage(i18nc("Information message", "A security hole has been detected and corrected on this version of the application. We strongly encourage you to change your password."), SKGDocument::Warning, QStringLiteral("skg://file_change_password")); } // To authorize manual repair of document in case of error during migration // the error is not caught if traces are activated if (err && (SKGTraces::SKGLevelTrace != 0)) { err = sendMessage(i18nc("Popup message", "The migration failed but the document has been loaded without error because debug mode is activated"), SKGDocument::Warning); } if (!err && mig && !iName.isEmpty()) { err = sendMessage(i18nc("The document has been upgraded to the latest Skrooge version format", "The document has been migrated"), SKGDocument::Positive); } } // Optimization IFOK(err) { d->m_lastSavedTransaction = getTransactionToProcess(SKGDocument::UNDO); executeSqliteOrder(QStringLiteral("ANALYZE")); } // Creation undo/redo triggers IFOKDO(err, createUndoRedoTemporaryTriggers()) IFOK(err) { QString sqliteQtVersion = getParameter(QStringLiteral("SKG_SQLITE_LAST_VERSION")); QString sqliteSystemVersion(sqlite3_libversion()); QProcess sqlite3Process; QString mode; sqlite3Process.start(QStringLiteral("sqlcipher"), QStringList() << QStringLiteral("-version")); mode = QStringLiteral("SQLCipher"); if (sqlite3Process.waitForFinished()) { sqliteSystemVersion = SKGServices::splitCSVLine(sqlite3Process.readAll(), ' ').value(0); } SKGTRACEL(5) << "SQLite version of Qt :" << sqliteQtVersion << endl; SKGTRACEL(5) << "SQLite version of the system:" << sqliteSystemVersion << endl; if (!sqliteQtVersion.isEmpty() && !sqliteSystemVersion.isEmpty() && sqliteQtVersion != sqliteSystemVersion) { QString message = i18nc("Error message", "This application can not run correctly because the %3 version of the system (%1) is not aligned with the %4 version embedded in Qt (%2). You should rebuild Qt with the option -system-sqlite.", sqliteSystemVersion, sqliteQtVersion, mode, mode); err = sendMessage(message, Warning); SKGTRACE << "WARNING:" << message << endl; } } if (err && !iName.isEmpty()) { close(); } else { // Send event d->m_uniqueIdentifier = QUuid::createUuid().toString(); d->m_password = iPassword; d->m_password_got = true; Q_EMIT tableModified(QLatin1String(""), 0, false); Q_EMIT modified(); } return err; } bool SKGDocument::isReadOnly() const { return d->m_modeReadOnly; } bool SKGDocument::isFileModified() const { // Get last executed transaction int last = getTransactionToProcess(SKGDocument::UNDO); // if (nbStepForTransaction.size()) --last; return (d->m_lastSavedTransaction != last); } void SKGDocument::setFileNotModified() const { d->m_lastSavedTransaction = getTransactionToProcess(SKGDocument::UNDO); } QString SKGDocument::getCurrentFileName() const { return d->m_currentFileName; } SKGError SKGDocument::save() { SKGError err; SKGTRACEINFUNCRC(5, err); if (d->m_currentFileName.isEmpty()) { err = SKGError(ERR_INVALIDARG, i18nc("Error message: Can not save a file if it has no name yet", "Save not authorized because the file name is not yet defined")); } else { // save err = saveAs(d->m_currentFileName, true); } return err; } SKGError SKGDocument::saveAs(const QString& iName, bool iOverwrite) { SKGError err; SKGTRACEINFUNCRC(5, err); SKGTRACEL(10) << "Input parameter [name]=[" << iName << ']' << endl; bool simulateFileSystemFull = !SKGServices::getEnvVariable(QStringLiteral("SKGFILESYSTEMFULL")).isEmpty(); // Check if a transaction is still opened err = checkExistingTransaction(); IFOK(err) err.setReturnCode(ERR_ABORT).setMessage(i18nc("Cannot save the file while the application is still performing an SQL transaction", "Save is forbidden if a transaction is still opened")); else { err = SKGError(); if (getParameter(QStringLiteral("SKG_UNDO_CLEAN_AFTER_SAVE")) == QStringLiteral("Y")) { err = executeSqliteOrder(QStringLiteral("delete from doctransaction")); } // No transaction opened ==> it is ok // We mark the last transaction as a save point IFOKDO(err, executeSqliteOrder(QStringLiteral("update doctransaction set t_savestep='Y' where id in (select A.id from doctransaction A where " "NOT EXISTS(select 1 from doctransaction B where B.i_parent=A.id) " "and A.t_mode='U')"))); Q_EMIT tableModified(QStringLiteral("doctransaction"), 0, false); // Optimization IFOK(err) { err = executeSqliteOrder(QStringLiteral("VACUUM;")); IFOK(err) { // Check if file already exist if (!iOverwrite && QFile(iName).exists()) { err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("There is already a file with the same name", "File '%1' already exist", iName)); } else { // Get backup file name bool backupFileMustBeRemoved = false; QString backupFileName = getBackupFile(iName); if (backupFileName.isEmpty()) { backupFileName = iName % ".tmp"; backupFileMustBeRemoved = true; } // Create backup file QFile::remove(backupFileName % '~'); QFile::rename(backupFileName, backupFileName % '~'); if (QFile(iName).exists() && (simulateFileSystemFull || !QFile(iName).copy(backupFileName))) { this->sendMessage(i18nc("Error message: Could not create a backup file", "Creation of backup file %1 failed", backupFileName), Warning); } // Save database IFOK(err) { QFile::remove(iName % '~'); QFile::rename(iName, iName % '~'); // To be sure that db is flushed IFOKDO(err, executeSqliteOrder(QStringLiteral("PRAGMA synchronous = FULL"))) QString pwd = getPassword(); // Copy memory to tmp db if (!d->m_directAccessDb && !err) { QFile::remove(d->m_temporaryFile); auto fileDb = QSqlDatabase::addDatabase(SQLDRIVERNAME, d->m_databaseIdentifier % "_tmp"); fileDb.setDatabaseName(d->m_temporaryFile); if (!fileDb.open()) { // Set error message QSqlError sqlErr = fileDb.lastError(); err = SKGError(SQLLITEERROR + sqlErr.number(), sqlErr.text()); } else { IFOKDO(err, SKGServices::executeSqliteOrder(fileDb, "PRAGMA KEY = '" % SKGServices::stringToSqlString(pwd.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : pwd) % "'")); addSqliteAddon(&fileDb); IFOKDO(err, SKGServices::copySqliteDatabase(fileDb, d->m_currentDatabase, false, pwd.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : pwd)); } fileDb.close(); QSqlDatabase::removeDatabase(d->m_databaseIdentifier % "_tmp"); } // To simulate a file system full if (!err && simulateFileSystemFull) { err = SKGError(ERR_WRITEACCESS, i18nc("Error message: writing a file failed", "Write file '%1' failed", iName)); } // Crypt the file if (!err) { bool mode; err = SKGServices::cryptFile(d->m_temporaryFile, iName, pwd, true, getDocumentHeader(), mode); } if (!d->m_directAccessDb && !err) { QFile(d->m_temporaryFile).remove(); } // For performances IFOKDO(err, executeSqliteOrder(QStringLiteral("PRAGMA synchronous = OFF"))) } if (backupFileMustBeRemoved) { QFile::remove(backupFileName); } IFOK(err) { // The document is not modified QString oldtemporaryFile = d->m_temporaryFile; d->m_currentFileName = iName; d->m_modeReadOnly = false; d->m_temporaryFile = getTemporaryFile(d->m_currentFileName); if (oldtemporaryFile != d->m_temporaryFile) { QFile(oldtemporaryFile).rename(d->m_temporaryFile); } d->m_lastSavedTransaction = getTransactionToProcess(SKGDocument::UNDO); // Commit save QFile::remove(backupFileName % '~'); QFile::remove(iName % '~'); } else { // Rollback file QFile::remove(backupFileName); QFile::rename(backupFileName % '~', backupFileName); QFile::remove(iName); QFile::rename(iName % '~', iName); } } } } Q_EMIT transactionSuccessfullyEnded(0); } return err; } SKGError SKGDocument::close() { SKGTRACEINFUNC(5); if (getMainDatabase() != nullptr) { QString conNameMainConnection = getMainDatabase()->connectionName(); const auto& conNames = QSqlDatabase::connectionNames(); for (const auto& conName : conNames) { if (conName.startsWith(conNameMainConnection % "_")) { /* NO NEED { auto con = QSqlDatabase::database(conName, false); con.close(); }*/ QSqlDatabase::removeDatabase(conName); } } getMainDatabase()->close(); d->m_currentDatabase = QSqlDatabase(); // To avoid warning on remove QSqlDatabase::removeDatabase(d->m_databaseIdentifier); } if (!d->m_temporaryFile.isEmpty()) { QFile(d->m_temporaryFile).remove(); d->m_temporaryFile = QLatin1String(""); } // Emit events ? bool emitEvent = (d->m_lastSavedTransaction != -1); // Init fields d->m_currentFileName = QLatin1String(""); d->m_lastSavedTransaction = 0; d->m_nbStepForTransaction.clear(); d->m_posStepForTransaction.clear(); d->m_nameForTransaction.clear(); d->m_password.clear(); d->m_password_got = false; // Send event if (!d->m_blockEmits && emitEvent && qApp && !qApp->closingDown()) { Q_EMIT tableModified(QLatin1String(""), 0, false); Q_EMIT transactionSuccessfullyEnded(0); Q_EMIT modified(); } return SKGError(); } SKGError SKGDocument::dropViewsAndIndexes(const QStringList& iTables) const { SKGError err; // Drop all views SKGStringListList list; err = executeSelectSqliteOrder(QStringLiteral("SELECT tbl_name, name, type FROM sqlite_master WHERE type IN ('view','index')"), list); int nb = list.count(); for (int i = 1; !err && i < nb; ++i) { QString name = list.at(i).at(1); QString table = SKGServices::getRealTable(list.at(i).at(0)); QString type = list.at(i).at(2); if (iTables.contains(table)) { QString sql = "DROP " % type % " IF EXISTS " % name; err = this->executeSqliteOrder(sql); } } return err; } #include "skgdocument2.cpp" SKGError SKGDocument::migrate(bool& oMigrationDone) { SKGError err; SKGTRACEINFUNCRC(5, err); oMigrationDone = false; { SKGBEGINPROGRESSTRANSACTION(*this, "#INTERNAL#" % i18nc("Progression step", "Migrate document"), err, 3); if (getParameter(QStringLiteral("SKG_UNDO_MAX_DEPTH")).isEmpty()) { IFOKDO(err, setParameter(QStringLiteral("SKG_UNDO_MAX_DEPTH"), SKGServices::intToString(SKG_UNDO_MAX_DEPTH))); } if (getParameter(QStringLiteral("SKG_UNDO_CLEAN_AFTER_SAVE")).isEmpty()) { IFOKDO(err, setParameter(QStringLiteral("SKG_UNDO_CLEAN_AFTER_SAVE"), QStringLiteral("N"))); } if (!err && getParameter(QStringLiteral("SKG_DATABASE_TYPE")) != (d->m_modeSQLCipher ? SQLDRIVERNAME : QStringLiteral("QSQLITE"))) { IFOKDO(err, setParameter(QStringLiteral("SKG_DATABASE_TYPE"), d->m_modeSQLCipher ? SQLDRIVERNAME : QStringLiteral("QSQLITE"))); } QString version = getParameter(QStringLiteral("SKG_DB_VERSION")); QString initialversion = version; QString lastversion = QStringLiteral("1.6"); if (!err && version.isEmpty()) { // First creation SKGTRACEL(10) << "Migration from 0 to " << lastversion << endl; // Set new version version = lastversion; IFOKDO(err, setParameter(QStringLiteral("SKG_DB_VERSION"), version)) // Set sqlite creation version SKGStringListList listTmp; IFOKDO(err, executeSelectSqliteOrder(QStringLiteral("select sqlite_version()"), listTmp)) if (!err && listTmp.count() == 2) { err = setParameter(QStringLiteral("SKG_SQLITE_CREATION_VERSION"), listTmp.at(1).at(0)); } oMigrationDone = true; } if (!err && SKGServices::stringToDouble(version) > SKGServices::stringToDouble(lastversion)) { err = SKGError(ERR_ABORT, i18nc("Error message", "Impossible to load a document generated by a more recent version")); } { // Migration steps if (!err && version == QStringLiteral("0.1")) { // Migration from version 0.1 to 0.2 SKGTRACEL(10) << "Migration from 0.1 to 0.2" << endl; // ================================================================== // Table doctransactionmsg QStringList sqlOrders; sqlOrders << QStringLiteral("DROP TABLE IF EXISTS doctransactionmsg") << QStringLiteral("CREATE TABLE doctransactionmsg (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " "rd_doctransaction_id INTEGER NOT NULL," "t_message TEXT NOT NULL DEFAULT '')"); err = executeSqliteOrders(sqlOrders); // Set new version version = QStringLiteral("0.2"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("0.2")) { // Migration from version 0.2 to 0.3 SKGTRACEL(10) << "Migration from 0.2 to 0.3" << endl; err = executeSqliteOrder(QStringLiteral("UPDATE node set f_sortorder=id")); // Set new version version = QStringLiteral("0.3"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("0.3")) { // Migration from version 0.3 to 0.4 SKGTRACEL(10) << "Migration from 0.3 to 0.4" << endl; err = executeSqliteOrder(QStringLiteral("ALTER TABLE node ADD COLUMN t_autostart VARCHAR(1) DEFAULT 'N' CHECK (t_autostart IN ('Y', 'N'))")); IFOKDO(err, executeSqliteOrder("UPDATE node set t_autostart='Y' where t_name='" % i18nc("Verb, automatically load when the application is started", "autostart") % '\'')) // Set new version version = QStringLiteral("0.4"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("0.4")) { // Migration from version 0.4 to 0.5 SKGTRACEL(10) << "Migration from 0.4 to 0.5" << endl; err = executeSqliteOrder(QStringLiteral("ALTER TABLE doctransactionmsg ADD COLUMN t_popup VARCHAR(1) DEFAULT 'Y' CHECK (t_popup IN ('Y', 'N'))")); // Set new version version = QStringLiteral("0.5"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("0.5")) { // Migration from version 0.5 to 0.6 SKGTRACEL(10) << "Migration from 0.5 to 0.6" << endl; err = executeSqliteOrder(QStringLiteral("UPDATE node set t_autostart='N' where t_autostart NOT IN ('Y', 'N')")); // Set new version version = QStringLiteral("0.6"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("0.6")) { // Migration from version 0.6 to 0.7 SKGTRACEL(10) << "Migration from 0.6 to 0.7" << endl; err = executeSqliteOrder(QStringLiteral("ALTER TABLE parameters ADD COLUMN b_blob BLOB")); // Set new version version = QStringLiteral("0.7"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("0.7")) { // Migration from version 0.7 to 0.8 SKGTRACEL(10) << "Migration from 0.7 to 0.8" << endl; err = executeSqliteOrder(QStringLiteral("UPDATE parameters set t_name='SKG_LANGUAGE' where t_name='SKGLANGUAGE'")); // Set new version version = QStringLiteral("0.8"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("0.8")) { SKGTRACEL(10) << "Migration from 0.8 to 0.9" << endl; QStringList sql; sql << QStringLiteral("ALTER TABLE parameters ADD COLUMN i_tmp INTEGER NOT NULL DEFAULT 0") << QStringLiteral("UPDATE parameters set i_tmp=0"); err = executeSqliteOrders(sql); // Set new version version = QStringLiteral("0.9"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("0.9")) { SKGTRACEL(10) << "Migration from 0.9 to 1.0" << endl; err = SKGDocument::setParameter(QStringLiteral("SKG_UNIQUE_ID"), QLatin1String("")); // Set new version version = QStringLiteral("1.0"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("1.0")) { // Migration from version 1.0 to 1.1 SKGTRACEL(10) << "Migration from 1.0 to 1.1" << endl; err = executeSqliteOrder(QStringLiteral("ALTER TABLE node ADD COLUMN t_icon TEXT DEFAULT ''")); IFOK(err) { SKGStringListList result; err = executeSelectSqliteOrder(QStringLiteral("SELECT id,t_data from node"), result); int nb = result.count(); for (int i = 1; !err && i < nb; ++i) { const QStringList& line = result.at(i); QString icon = QStringLiteral("folder-bookmark"); QStringList data = SKGServices::splitCSVLine(line.at(1)); if (data.count() > 2) { icon = data.at(2); } data.removeAt(2); err = executeSqliteOrder("UPDATE node set t_icon='" % SKGServices::stringToSqlString(icon) % "', t_data='" % SKGServices::stringToSqlString(SKGServices::stringsToCsv(data)) % "' where id=" % line.at(0)); } } // Set new version version = QStringLiteral("1.1"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("1.1")) { // Migration from version 1.1 to 1.2 SKGTRACEL(10) << "Migration from 1.1 to 1.2" << endl; QStringList sql; sql << QStringLiteral("ALTER TABLE doctransaction ADD COLUMN t_refreshviews VARCHAR(1) DEFAULT 'Y' CHECK (t_refreshviews IN ('Y', 'N'))") << QStringLiteral("UPDATE doctransaction set t_refreshviews='Y'"); err = executeSqliteOrders(sql); // Set new version version = QStringLiteral("1.2"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("1.2")) { // Migration from version 1.2 to 1.3 SKGTRACEL(10) << "Migration from 1.2 to 1.3" << endl; err = SKGDocument::refreshViewsIndexesAndTriggers(); QStringList sql; sql << QStringLiteral("DELETE FROM node WHERE (r_node_id IS NULL OR r_node_id='') AND EXISTS (SELECT 1 FROM node n WHERE n.t_name=node.t_name AND r_node_id=0)") << QStringLiteral("UPDATE node SET t_name=t_name"); IFOKDO(err, executeSqliteOrders(sql)) // Set new version version = QStringLiteral("1.3"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("1.3")) { // Migration from version 1.3 to 1.4 SKGTRACEL(10) << "Migration from 1.3 to 1.4" << endl; QStringList sql; sql << "CREATE TABLE node2 (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," "t_name TEXT NOT NULL DEFAULT '' CHECK (t_name NOT LIKE '%" % OBJECTSEPARATOR % "%')," "t_fullname TEXT," "t_icon TEXT DEFAULT ''," "f_sortorder FLOAT," "t_autostart VARCHAR(1) DEFAULT 'N' CHECK (t_autostart IN ('Y', 'N'))," "t_data TEXT," "rd_node_id INT CONSTRAINT fk_id REFERENCES node(id) ON DELETE CASCADE)" << QStringLiteral("INSERT INTO node2 (id, t_name, t_fullname, t_icon, f_sortorder, t_autostart, t_data, rd_node_id) " "SELECT id, t_name, t_fullname, t_icon, f_sortorder, t_autostart, t_data, r_node_id FROM node") << QStringLiteral("DROP TABLE IF EXISTS node") << QStringLiteral("ALTER TABLE node2 RENAME TO node"); err = executeSqliteOrders(sql); // Set new version version = QStringLiteral("1.4"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("1.4")) { // Migration from version 1.4 to 1.5 SKGTRACEL(10) << "Migration from 1.4 to 1.5" << endl; err = SKGDocument::refreshViewsIndexesAndTriggers(); QStringList sql; sql << QStringLiteral("UPDATE parameters SET t_uuid_parent='advice' WHERE t_uuid_parent='advices'"); IFOKDO(err, executeSqliteOrders(sql)) // Set new version version = QStringLiteral("1.5"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } if (!err && version == QStringLiteral("1.5")) { // Migration from version 1.5 to 1.6 SKGTRACEL(10) << "Migration from 1.5 to 1.6" << endl; err = SKGDocument::refreshViewsIndexesAndTriggers(); QStringList sql; sql << QStringLiteral("DROP TABLE IF EXISTS doctransactionmsg2") << QStringLiteral("CREATE TABLE doctransactionmsg2 (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " "rd_doctransaction_id INTEGER NOT NULL," "t_message TEXT NOT NULL DEFAULT ''," "t_type VARCHAR(1) DEFAULT 'I' CHECK (t_type IN ('P', 'I', 'W', 'E', 'H')))") // Positive, Information, Warning, Error, Hidden << QStringLiteral("INSERT INTO doctransactionmsg2 (id, rd_doctransaction_id, t_message, t_type) SELECT id, rd_doctransaction_id, t_message, (CASE WHEN t_popup='Y' THEN 'I' ELSE 'H' END) FROM doctransactionmsg") << QStringLiteral("DROP TABLE IF EXISTS doctransactionmsg") << QStringLiteral("ALTER TABLE doctransactionmsg2 RENAME TO doctransactionmsg"); IFOKDO(err, executeSqliteOrders(sql)) // Set new version version = QStringLiteral("1.6"); IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) oMigrationDone = true; } } IFOKDO(err, stepForward(1, i18nc("Progression step", "Refresh views"))) // Set sqlite last version SKGStringListList listTmp; IFOKDO(err, executeSelectSqliteOrder(QStringLiteral("select sqlite_version()"), listTmp)) if (!err && listTmp.count() == 2) { err = setParameter(QStringLiteral("SKG_SQLITE_LAST_VERSION"), listTmp.at(1).at(0)); } // Refresh views IFOKDO(err, refreshViewsIndexesAndTriggers()) IFOKDO(err, stepForward(2, i18nc("Progression step", "Update materialized views"))) // Refresh materialized views if (!err && oMigrationDone) { err = computeMaterializedViews(); } IFOKDO(err, stepForward(3)) IFKO(err) err.addError(ERR_FAIL, i18nc("Error message: Could not perform database migration", "Database migration from version %1 to version %2 failed", initialversion, version)); } return err; } SKGError SKGDocument::createUndoRedoTemporaryTriggers() const { SKGError err; SKGTRACEINFUNCRC(10, err); // Create triggers QStringList tables; err = this->getTablesList(tables); int nbTables = tables.count(); for (int i = 0; !err && i < nbTables; ++i) { // Get table name const QString& table = tables.at(i); // Do we have to treat this table if (!SKGListNotUndoable.contains("T." % table) && !table.startsWith(QLatin1String("vm_"))) { // YES // Get attributes name QStringList attributes; err = getAttributesList(table, attributes); // Build sqlorder for update and insert QString sqlorderForUpdate2; QString sqlorderForInsert1; QString sqlorderForInsert2; int nbAttributes = attributes.count(); for (int j = 0; !err && j < nbAttributes; ++j) { // Get attribute const QString& att = attributes.at(j); // Do we have to treat this attribute if (!SKGListNotUndoable.contains("A." % table % '.' % att)) { // Build for update if (!sqlorderForUpdate2.isEmpty()) { sqlorderForUpdate2 += ','; } sqlorderForUpdate2 += att % "='||quote(old." % att % ")||'"; // Build for insert part 1 if (!sqlorderForInsert1.isEmpty()) { sqlorderForInsert1 += ','; } sqlorderForInsert1 += att; // Build for insert part 2 if (!sqlorderForInsert2.isEmpty()) { sqlorderForInsert2 += ','; } sqlorderForInsert2 += "'||quote(old." % att % ")||'"; } } // Create specific triggers for the current transaction QStringList sqlOrders; // DROP DELETE trigger sqlOrders << "DROP TRIGGER IF EXISTS UR_" % table % "_IN" // Create DELETE trigger << "CREATE TEMP TRIGGER UR_" % table % "_IN " "AFTER INSERT ON " % table % " BEGIN " "INSERT INTO doctransactionitem (rd_doctransaction_id, t_sqlorder,i_object_id,t_object_table,t_action) VALUES(0,'DELETE FROM " % table % " WHERE id='||new.id,new.id,'" % table % "','D');END" // DROP UPDATE trigger << "DROP TRIGGER IF EXISTS UR_" % table % "_UP" // Create UPDATE trigger << "CREATE TEMP TRIGGER UR_" % table % "_UP " "AFTER UPDATE ON " % table % " BEGIN " "INSERT INTO doctransactionitem (rd_doctransaction_id, t_sqlorder,i_object_id,t_object_table,t_action) VALUES(0,'UPDATE " % table % " SET " % sqlorderForUpdate2 % " WHERE id='||new.id,new.id,'" % table % "','U');END" // DROP INSERT trigger << "DROP TRIGGER IF EXISTS UR_" % table % "_DE" // Create INSERT trigger << "CREATE TEMP TRIGGER UR_" % table % "_DE " "AFTER DELETE ON " % table % " BEGIN " "INSERT INTO doctransactionitem (rd_doctransaction_id, t_sqlorder,i_object_id,t_object_table,t_action) VALUES(0,'INSERT INTO " % table % '(' % sqlorderForInsert1 % ") VALUES(" % sqlorderForInsert2 % ")',old.id,'" % table % "','I'); END"; err = executeSqliteOrders(sqlOrders); } } return err; } QStringList SKGDocument::getParameters(const QString& iParentUUID, const QString& iWhereClause) { SKGTRACEINFUNC(10); QStringList output; QString wc = "t_uuid_parent='" % SKGServices::stringToSqlString(iParentUUID) % '\''; if (!iWhereClause.isEmpty()) { wc += " AND (" % iWhereClause % ')'; } this->getDistinctValues(QStringLiteral("parameters"), QStringLiteral("t_name"), wc, output); return output; } QString SKGDocument::getParameter(const QString& iName, const QString& iParentUUID) const { SKGTRACEINFUNC(10); SKGTRACEL(10) << "Input parameter [iName]=[" << iName << ']' << endl; QString output; // Get parameter SKGObjectBase param; SKGError err = getObject(QStringLiteral("parameters"), "t_name='" % SKGServices::stringToSqlString(iName) % "' AND t_uuid_parent='" % SKGServices::stringToSqlString(iParentUUID) % '\'', param); IFOK(err) { output = param.getAttribute(QStringLiteral("t_value")); } return output; } QVariant SKGDocument::getParameterBlob(const QString& iName, const QString& iParentUUID) const { SKGTRACEINFUNC(10); SKGTRACEL(10) << "Input parameter [iName]=[" << iName << ']' << endl; QVariant output; if (getMainDatabase() != nullptr) { QString sqlQuery = QStringLiteral("SELECT b_blob FROM parameters WHERE t_name=? AND t_uuid_parent=?"); QSqlQuery query(*getMainDatabase()); query.prepare(sqlQuery); query.addBindValue(iName); query.addBindValue(iParentUUID); if (Q_LIKELY(!query.exec())) { QSqlError sqlError = query.lastError(); SKGTRACE << "WARNING: " << sqlQuery << endl; SKGTRACE << " returns :" << sqlError.text() << endl; } else { if (query.next()) { output = query.value(0); } } } return output; } SKGError SKGDocument::setParameter(const QString& iName, const QString& iValue, const QString& iFileName, const QString& iParentUUID, SKGPropertyObject* oObjectCreated) const { SKGError err; SKGTRACEINFUNCRC(10, err); SKGTRACEL(10) << "Input parameter [iName] =[" << iName << ']' << endl; SKGTRACEL(10) << "Input parameter [iValue] =[" << iValue << ']' << endl; SKGTRACEL(10) << "Input parameter [iFileName]=[" << iFileName << ']' << endl; QVariant blob; QString value = iValue; QFile file(iFileName); if (file.exists()) { QFileInfo fileInfo(iFileName); if (fileInfo.isDir()) { value = "file://" % iFileName; } else { // Open file if (Q_UNLIKELY(!file.open(QIODevice::ReadOnly))) { err = SKGError(ERR_INVALIDARG, i18nc("Error message: Could not open a file", "Open file '%1' failed", iFileName)); } else { QByteArray blob_bytes = file.readAll(); if (blob_bytes.isEmpty()) { err = SKGError(ERR_INVALIDARG, i18nc("Error message: Could not open a file", "Open file '%1' failed", iFileName)); } else { blob = blob_bytes; value = fileInfo.fileName(); } // close file file.close(); } } } IFOKDO(err, setParameter(iName, value, blob, iParentUUID, oObjectCreated)) return err; } SKGError SKGDocument::setParameter(const QString& iName, const QString& iValue, const QVariant& iBlob, const QString& iParentUUID, SKGPropertyObject* oObjectCreated) const { SKGError err; SKGTRACEINFUNCRC(10, err); SKGTRACEL(10) << "Input parameter [iName] =[" << iName << ']' << endl; SKGTRACEL(10) << "Input parameter [iValue] =[" << iValue << ']' << endl; if (getMainDatabase() == nullptr) { err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined")); } else { SKGPropertyObject param(const_cast(this)); IFOKDO(err, param.setName(iName)) IFOKDO(err, param.setValue(iValue)) IFOKDO(err, param.setParentId(iParentUUID)) IFOKDO(err, param.save(true, oObjectCreated != nullptr)) if (!err && !iBlob.isNull()) { err = param.load(); IFOK(err) { // Set blob QString sqlQuery = QStringLiteral("UPDATE parameters SET b_blob=? WHERE id=?"); QSqlQuery query(*getMainDatabase()); query.prepare(sqlQuery); query.addBindValue(iBlob); query.addBindValue(param.getID()); if (Q_LIKELY(!query.exec())) { QSqlError sqlError = query.lastError(); QString msg = sqlQuery % ':' % sqlError.text(); err = SKGError(SQLLITEERROR + sqlError.number(), msg); } } } if (!err && oObjectCreated != nullptr) { *oObjectCreated = param; } } return err; } SKGError SKGDocument::dump(int iMode) const { SKGError err; // dump parameters SKGTRACE << "=== START DUMP ===" << endl; if ((iMode & DUMPSQLITE) != 0) { SKGTRACE << "=== DUMPSQLITE ===" << endl; err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM sqlite_master order by type"))); SKGTRACE << "=== DUMPSQLITE (TEMPORARY) ===" << endl; err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM sqlite_temp_master order by type"))); } if ((iMode & DUMPPARAMETERS) != 0) { SKGTRACE << "=== DUMPPARAMETERS ===" << endl; err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM parameters order by id"))); } if ((iMode & DUMPNODES) != 0) { SKGTRACE << "=== DUMPNODES ===" << endl; err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM node order by id"))); } if ((iMode & DUMPTRANSACTIONS) != 0) { // dump transaction SKGTRACE << "=== DUMPTRANSACTIONS ===" << endl; err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM doctransaction order by id"))); // dump transaction SKGTRACE << "=== DUMPTRANSACTIONS (ITEMS) ===" << endl; err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM doctransactionitem order by rd_doctransaction_id, id"))); } SKGTRACE << "=== END DUMP ===" << endl; return err; } QSqlDatabase* SKGDocument::getMainDatabase() const { if (!d->m_currentDatabase.isOpen()) { return nullptr; } return const_cast(&d->m_currentDatabase); } QSqlDatabase SKGDocument::getThreadDatabase() const { if (qApp->thread() != QThread::currentThread()) { d->m_mutex.lock(); QString pwd = getPassword(); QString dbName = getMainDatabase()->databaseName(); QString conName = getMainDatabase()->connectionName(); QString id = conName % "_" % QString::number((quint64)QThread::currentThread(), 16); d->m_mutex.unlock(); auto tmpDatabase = QSqlDatabase::database(id); if (!tmpDatabase.isValid()) { tmpDatabase = QSqlDatabase::addDatabase(SQLDRIVERNAME, id); } if (tmpDatabase.databaseName() != dbName) { tmpDatabase.setConnectOptions(QStringLiteral("QSQLITE_OPEN_URI")); tmpDatabase.setDatabaseName(dbName); if (tmpDatabase.open()) { addSqliteAddon(&tmpDatabase); if (d->m_modeSQLCipher) { SKGServices::executeSqliteOrder(tmpDatabase, "PRAGMA KEY = '" % SKGServices::stringToSqlString(pwd.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : pwd) % "'"); } } } return tmpDatabase; } return d->m_currentDatabase; } SKGError SKGDocument::getConsolidatedView(const QString& iTable, const QString& iAsColumn, const QString& iAsRow, const QString& iAttribute, const QString& iOpAtt, const QString& iWhereClause, SKGStringListList& oTable, const QString& iMissingValue) const { SKGError err; SKGTRACEINFUNCRC(10, err); SKGTRACEL(10) << "Input parameter [iTable]=[" << iTable << ']' << endl; SKGTRACEL(10) << "Input parameter [iAsColumn]=[" << iAsColumn << ']' << endl; SKGTRACEL(10) << "Input parameter [iAsRow]=[" << iAsRow << ']' << endl; SKGTRACEL(10) << "Input parameter [iAttribute]=[" << iAttribute << ']' << endl; SKGTRACEL(10) << "Input parameter [iOpAtt]=[" << iOpAtt << ']' << endl; SKGTRACEL(10) << "Input parameter [iWhereClause]=[" << iWhereClause << ']' << endl; SKGTRACEL(10) << "Input parameter [iMissingValue]=[" << iMissingValue << ']' << endl; // Mode int mode = 0; if (!iAsColumn.isEmpty()) { mode += 1; } if (!iAsRow.isEmpty()) { mode += 2; } oTable.clear(); oTable.push_back(QStringList()); QStringList titles = oTable.at(0); if (mode == 3) { titles.push_back(iAsRow % '/' % iAsColumn); } else { if (mode == 1) { titles.push_back(iAsColumn); QStringList sums; sums.push_back(i18nc("Noun, the numerical sum of a list of values", "Sum")); oTable.push_back(sums); } else { if (mode == 2) { titles.push_back(iAsRow); titles.push_back(i18nc("Noun, the numerical sum of a list of values", "Sum")); } } } oTable.removeAt(0); oTable.insert(0, titles); // Create sqlorder QString asColumn = iAsColumn; if (asColumn.startsWith(QLatin1String("p_"))) { QString propertyName = asColumn.right(asColumn.length() - 2); asColumn = "(SELECT t_value FROM parameters WHERE t_uuid_parent=" % iTable % ".id||'-" % SKGServices::getRealTable(iTable) % "' AND t_name='" % propertyName % "')"; } QString asRow = iAsRow; if (asRow.startsWith(QLatin1String("p_"))) { QString propertyName = asRow.right(asRow.length() - 2); asRow = "(SELECT t_value FROM parameters WHERE t_uuid_parent=" % iTable % ".id||'-" % SKGServices::getRealTable(iTable) % "' AND t_name='" % propertyName % "')"; } QString att = asColumn; if (!att.isEmpty() && !asRow.isEmpty()) { att += ','; } att += asRow; QString sort = asRow; if (!sort.isEmpty() && !asColumn.isEmpty()) { sort += ','; } sort += asColumn; if (!att.isEmpty()) { QString sql = "SELECT " % att % ',' % iOpAtt % '(' % iAttribute % ") FROM " % iTable; if (!iWhereClause.isEmpty()) { sql += " WHERE " % iWhereClause; } if (!iOpAtt.isEmpty()) { sql += " GROUP BY " % att; } sql += " ORDER BY " % sort; QHash cols; QHash rows; SKGTRACEL(10) << "sqlorder=[" << sql << ']' << endl; SKGStringListList listTmp; err = executeSelectSqliteOrder(sql, listTmp); int nb = listTmp.count(); for (int i = 1; !err && i < nb; ++i) { // Title is ignored const QStringList& line = listTmp.at(i); int rowindex = -1; int colindex = -1; if (mode >= 2) { const QString& rowname = line.at(mode == 3 ? 1 : 0); if (!rows.contains(rowname)) { QStringList r; int nbx = oTable.at(0).count(); r.reserve(nbx); r.push_back(rowname); for (int j = 1; j < nbx; ++j) { r.push_back(iMissingValue); } oTable.push_back(r); rowindex = oTable.count() - 1; rows.insert(rowname, rowindex); } else { rowindex = rows[rowname]; } } else { rowindex = 1; } if (mode == 1 || mode == 3) { const QString& colname = line.at(0); if (!cols.contains(colname)) { // Search better position of this column colindex = -1; { QHashIterator cols_i(cols); while (cols_i.hasNext()) { cols_i.next(); if (colname > cols_i.key() && cols_i.value() > colindex) { colindex = cols_i.value(); } } } if (colindex == -1) { colindex = 1; } else { ++colindex; } int nbx = oTable.count(); for (int j = 0; j < nbx; ++j) { if (j == 0) { oTable[j].insert(colindex, colname); } else { oTable[j].insert(colindex, iMissingValue); } } { QHash tmp; QHashIterator cols_i(cols); while (cols_i.hasNext()) { cols_i.next(); tmp.insert(cols_i.key(), cols_i.value() + (cols_i.value() >= colindex ? 1 : 0)); } cols = tmp; } cols.insert(colname, colindex); } else { colindex = cols[colname]; } } else { colindex = 1; } const QString& sum = line.at(mode == 3 ? 2 : 1); oTable[rowindex][colindex] = sum; } IFSKGTRACEL(10) { QStringList dump2 = SKGServices::tableToDump(oTable, SKGServices::DUMP_TEXT); int nbl = dump2.count(); for (int i = 0; i < nbl; ++i) { SKGTRACE << dump2.at(i) << endl; } } // Correction bug 205466 vvv // If some months or years are missing, we must add them. if (asColumn.startsWith(QLatin1String("d_"))) { for (int c = 1; c < oTable[0].count() - 1; ++c) { // Dynamic size bool forecast = false; QString title = oTable.at(0).at(c); if (title.isEmpty()) { title = QStringLiteral("0000"); } if (title.endsWith(QLatin1String("999"))) { title = title.left(title.count() - 3); forecast = true; } QString nextTitle = oTable.at(0).at(c + 1); if (nextTitle.endsWith(QLatin1String("999"))) { nextTitle = nextTitle.left(nextTitle.count() - 3); forecast = true; } QString dateFormat = (asColumn == QStringLiteral("d_date") ? QStringLiteral("yyyy-MM-dd") : (asColumn == QStringLiteral("d_DATEMONTH") ? QStringLiteral("yyyy-MM") : (asColumn == QStringLiteral("d_DATEQUARTER") ? QStringLiteral("yyyy-QM") : (asColumn == QStringLiteral("d_DATESEMESTER") ? QStringLiteral("yyyy-SM") : (asColumn == QStringLiteral("d_DATEWEEK") ? QStringLiteral("yyyy-WM") : QStringLiteral("yyyy")))))); QDate nextExpected = QDate::fromString(title, dateFormat); QString nextExpectedString; if (asColumn == QStringLiteral("d_DATEWEEK")) { /* TODO(Stephane MANKOWSKI) QStringList items=SKGServices::splitCSVLine(oTable.at(0).at(c),'-'); nextExpected=QDate(SKGServices::stringToInt(items.at(0)), 1, 1); QString w=items.at(1); w.remove('W'); nextExpected=nextExpected.addDays(7*SKGServices::stringToInt(w)); QString newW=SKGServices::intToString(nextExpected.weekNumber()); if(newW.count()==1) newW='0'+newW; */ nextExpectedString = nextTitle; } else if (asColumn == QStringLiteral("d_DATEMONTH")) { nextExpected = nextExpected.addMonths(1); nextExpectedString = nextExpected.toString(dateFormat); } else if (asColumn == QStringLiteral("d_DATEQUARTER")) { nextExpected = nextExpected.addMonths(nextExpected.month() * 3 - nextExpected.month()); // convert quarter in month nextExpected = nextExpected.addMonths(3); nextExpectedString = nextExpected.toString(QStringLiteral("yyyy-Q")) % (nextExpected.month() <= 3 ? '1' : (nextExpected.month() <= 6 ? '2' : (nextExpected.month() <= 9 ? '3' : '4'))); } else if (asColumn == QStringLiteral("d_DATESEMESTER")) { nextExpected = nextExpected.addMonths(nextExpected.month() * 6 - nextExpected.month()); // convert semester in month nextExpected = nextExpected.addMonths(6); nextExpectedString = nextExpected.toString(QStringLiteral("yyyy-S")) % (nextExpected.month() <= 6 ? '1' : '2'); } else if (asColumn == QStringLiteral("d_DATEYEAR")) { nextExpected = nextExpected.addYears(1); nextExpectedString = nextExpected.toString(dateFormat); } else { nextExpected = nextExpected.addDays(1); nextExpectedString = nextExpected.toString(dateFormat); } if (title != QStringLiteral("0000") && nextTitle != nextExpectedString && nextTitle != title) { int colindex = c + 1; if (forecast) { nextExpectedString += QStringLiteral("999"); } int nbx = oTable.count(); oTable[0].insert(colindex, nextExpectedString); for (int j = 1; j < nbx; ++j) { oTable[j].insert(colindex, iMissingValue); } } } } // Correction bug 205466 ^^^ } return err; } SKGDocument::SKGModelTemplateList SKGDocument::getDisplaySchemas(const QString& iRealTable) const { SKGDocument::SKGModelTemplateList listSchema; // Build schemas if (iRealTable == QStringLiteral("doctransaction")) { SKGModelTemplate def; def.id = QStringLiteral("default"); def.name = i18nc("Noun, the default value of an item", "Default"); def.icon = QStringLiteral("edit-undo"); def.schema = QStringLiteral("t_name;t_value;d_lastmodifdate;t_savestep"); listSchema.push_back(def); SKGModelTemplate minimum; minimum.id = QStringLiteral("minimum"); minimum.name = i18nc("Noun, the minimum value of an item", "Minimum"); minimum.icon = QLatin1String(""); minimum.schema = QStringLiteral("t_name;t_value;d_lastmodifdate|N;t_savestep|N"); listSchema.push_back(minimum); } else if (iRealTable == QStringLiteral("parameters")) { SKGModelTemplate def; def.id = QStringLiteral("default"); def.name = i18nc("Noun, the default value of an item", "Default"); def.icon = QStringLiteral("edit-undo"); def.schema = QStringLiteral("t_name;t_value"); listSchema.push_back(def); } else if (iRealTable == QStringLiteral("node")) { SKGModelTemplate def; def.id = QStringLiteral("default"); def.name = i18nc("Noun, the default value of an item", "Default"); def.icon = QStringLiteral("edit-undo"); def.schema = QStringLiteral("t_name"); listSchema.push_back(def); } else { SKGModelTemplate def; def.id = QStringLiteral("default"); def.name = i18nc("Noun, the default value of an item", "Default"); def.icon = QStringLiteral("edit-undo"); def.schema = QLatin1String(""); SKGStringListList lines; executeSelectSqliteOrder("PRAGMA table_info(" % iRealTable % ");", lines); for (const auto& line : qAsConst(lines)) { if (!def.schema.isEmpty()) { def.schema += ';'; } def.schema += line[1]; } listSchema.push_back(def); } return listSchema; } QString SKGDocument::getDisplay(const QString& iString) const { QString output = iString.toLower(); if (output.endsWith(QLatin1String("t_name"))) { output = i18nc("Noun, the name of an item", "Name"); } else if (output.endsWith(QLatin1String("d_date"))) { output = i18nc("Noun, the date of an item", "Date"); } else if (output.endsWith(QLatin1String("t_savestep"))) { output = i18nc("Verb, save a document", "Save"); } else if (output.endsWith(QLatin1String("t_value"))) { output = i18nc("Noun, the value of an item", "Value"); } else if (output.endsWith(QLatin1String("d_lastmodifdate"))) { output = i18nc("Noun, date of last modification", "Last modification"); } else if (output.startsWith(QLatin1String("p_")) || output.contains(QStringLiteral("p_"))) { // This is a property int pos = iString.indexOf(QStringLiteral(".")); if (pos != -1) { output = iString.right(iString.count() - pos - 1); } output = output.right(output.length() - 2); } else { output = iString; } return output; } QString SKGDocument::getIconName(const QString& iString) const { QString output = iString.toLower(); if (output.startsWith(QLatin1String("p_")) || output.contains(QStringLiteral("p_"))) { return QStringLiteral("tag"); } return QLatin1String(""); } QIcon SKGDocument::getIcon(const QString& iString) const { return SKGServices::fromTheme(getIconName(iString)); } QString SKGDocument::getRealAttribute(const QString& iString) const { if (iString == iString.toLower()) { return iString; } return QLatin1String(""); } SKGServices::AttributeType SKGDocument::getAttributeType(const QString& iAttributeName) const { SKGServices::AttributeType output = SKGServices::TEXT; if (iAttributeName.startsWith(QLatin1String("d_"))) { output = SKGServices::DATE; } else if (iAttributeName.startsWith(QLatin1String("i_"))) { output = SKGServices::INTEGER; } else if (iAttributeName.startsWith(QLatin1String("rd_")) || iAttributeName.startsWith(QLatin1String("rc_")) || iAttributeName.startsWith(QLatin1String("r_")) || iAttributeName.startsWith(QLatin1String("id_"))) { output = SKGServices::LINK; } else if (iAttributeName.startsWith(QLatin1String("f_"))) { output = SKGServices::FLOAT; } else if (iAttributeName.startsWith(QLatin1String("b_"))) { output = SKGServices::BLOB; } else if (iAttributeName == QStringLiteral("id")) { output = SKGServices::ID; } else if (iAttributeName == QStringLiteral("t_savestep") || iAttributeName == QStringLiteral("t_refreshviews")) { output = SKGServices::BOOL; } return output; } SKGServices::SKGUnitInfo SKGDocument::getUnit(const QString& iPrefixInCache) const { SKGServices::SKGUnitInfo output; output.Name = getCachedValue(iPrefixInCache % "UnitCache"); if (output.Name.isEmpty()) { refreshCache(QStringLiteral("unit")); output.Name = getCachedValue(iPrefixInCache % "UnitCache"); } output.Symbol = getCachedValue(iPrefixInCache % "UnitSymbolCache"); QString val = getCachedValue(iPrefixInCache % "UnitValueCache"); if (!val.isEmpty()) { output.Value = SKGServices::stringToDouble(val); } else { output.Value = 1; } val = getCachedValue(iPrefixInCache % "UnitDecimalCache"); if (!val.isEmpty()) { output.NbDecimal = SKGServices::stringToInt(val); } else { output.NbDecimal = 2; } return output; } QString SKGDocument::formatMoney(double iValue, const SKGServices::SKGUnitInfo& iUnit, bool iHtml) const { QString val = SKGServices::toCurrencyString(iValue / iUnit.Value, iUnit.Symbol, iUnit.NbDecimal); if (iHtml) { // Return value if (iValue < 0) { // Get std colors KColorScheme scheme(QPalette::Normal); return QStringLiteral("" % SKGServices::stringToHtml(val) % ""; } return SKGServices::stringToHtml(val); } return val; } QString SKGDocument::formatPrimaryMoney(double iValue) const { return SKGServices::doubleToString(iValue); } QString SKGDocument::formatSecondaryMoney(double iValue)const { return SKGServices::doubleToString(iValue); } QString SKGDocument::formatPercentage(double iValue, bool iInvertColors) const { // Get std colors KColorScheme scheme(QPalette::Normal); QString negative = scheme.foreground(KColorScheme::NegativeText).color().name(); QString positive = scheme.foreground(KColorScheme::PositiveText).color().name(); // Return value QString p = SKGServices::toPercentageString(iValue); if (iValue > 0) { p = '+' % p; } if (p.count() > 10 || std::isnan(iValue) || std::isinf(iValue)) { p = QChar(8734); } return "= 0 && iInvertColors) ? negative : positive) % "\">" % SKGServices::stringToHtml(p) % ""; } QString SKGDocument::getFileExtension() const { return QStringLiteral("skgc"); } QString SKGDocument::getDocumentHeader() const { return QStringLiteral("SKG"); } void SKGDocument::addValueInCache(const QString& iKey, const QString& iValue) const { d->m_cache[iKey] = iValue; } void SKGDocument::addSqlResultInCache(const QString& iKey, const SKGStringListList& iValue) const { d->m_mutex.lock(); (*d->m_cacheSql)[iKey] = iValue; d->m_mutex.unlock(); } QString SKGDocument::getCachedValue(const QString& iKey) const { return d->m_cache.value(iKey); } SKGStringListList SKGDocument::getCachedSqlResult(const QString& iKey) const { return d->m_cacheSql->value(iKey); } void SKGDocument::refreshCache(const QString& iTable) const { Q_UNUSED(iTable); } void SKGDocument::setBackupParameters(const QString& iPrefix, const QString& iSuffix) const { d->m_backupPrefix = iPrefix; d->m_backupSuffix = iSuffix; } QString SKGDocument::getCurrentTemporaryFile() const { return d->m_temporaryFile; } QString SKGDocument::getTemporaryFile(const QString& iFileName, bool iForceReadOnly) { QString output; QFileInfo fi(iFileName); QFileInfo di(fi.dir().path()); if (iForceReadOnly || !QUrl::fromUserInput(iFileName).isLocalFile() || !di.permission(QFile::WriteUser)) { output = QDir::tempPath(); } else { output = fi.absolutePath(); } return output += "/." % fi.fileName() % ".wrk"; } QString SKGDocument::getBackupFile(const QString& iFileName) const { QString output; if (!d->m_backupPrefix.isEmpty() || !d->m_backupSuffix.isEmpty()) { QFileInfo fi(iFileName); if (d->m_backupSuffix.endsWith(QLatin1String(".skg"))) { output = d->m_backupPrefix % fi.baseName() % d->m_backupSuffix; } else { output = d->m_backupPrefix % fi.fileName() % d->m_backupSuffix; } output = output.replace(QStringLiteral(""), SKGServices::dateToSqlString(QDateTime::currentDateTime().date())); output = output.replace(QStringLiteral("