1 /***************************************************************************
2 * SPDX-FileCopyrightText: 2021 S. MANKOWSKI stephane@mankowski.fr
3 * SPDX-FileCopyrightText: 2021 G. DE BURE support@mankowski.fr
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 ***************************************************************************/
6 /** @file
7 * A skrooge plugin to search and process operations
8 *
9 * @author Stephane MANKOWSKI
10 */
11 #include "skgsearchplugin.h"
12
13 #include <kaboutdata.h>
14 #include <kactioncollection.h>
15 #include <kpluginfactory.h>
16 #include <kstandardaction.h>
17
18 #include <qdom.h>
19
20 #include "skgalarmboardwidget.h"
21 #include "skgcategoryobject.h"
22 #include "skgdocumentbank.h"
23 #include "skgerror.h"
24 #include "skghtmlboardwidget.h"
25 #include "skgmainpanel.h"
26 #include "skgruleobject.h"
27 #include "skgsearch_settings.h"
28 #include "skgsearchpluginwidget.h"
29 #include "skgtraces.h"
30 #include "skgtransactionmng.h"
31
32 /**
33 * This plugin factory.
34 */
K_PLUGIN_FACTORY(SKGSearchPluginFactory,registerPlugin<SKGSearchPlugin> ();)35 K_PLUGIN_FACTORY(SKGSearchPluginFactory, registerPlugin<SKGSearchPlugin>();)
36
37 SKGSearchPlugin::SKGSearchPlugin(QWidget* iWidget, QObject* iParent, const QVariantList& /*iArg*/) :
38 SKGInterfacePlugin(iParent), m_currentBankDocument(nullptr)
39 {
40 Q_UNUSED(iWidget)
41 SKGTRACEINFUNC(10)
42 m_timer.setSingleShot(true);
43 connect(&m_timer, &QTimer::timeout, this, &SKGSearchPlugin::raiseAlarms, Qt::QueuedConnection);
44 }
45
~SKGSearchPlugin()46 SKGSearchPlugin::~SKGSearchPlugin()
47 {
48 SKGTRACEINFUNC(10)
49 m_currentBankDocument = nullptr;
50 }
51
getNbDashboardWidgets()52 int SKGSearchPlugin::getNbDashboardWidgets()
53 {
54 return 1;
55 }
56
getDashboardWidgetTitle(int iIndex)57 QString SKGSearchPlugin::getDashboardWidgetTitle(int iIndex)
58 {
59 Q_UNUSED(iIndex)
60 return i18nc("Noun, alarms", "Alarms");
61 }
62
getDashboardWidget(int iIndex)63 SKGBoardWidget* SKGSearchPlugin::getDashboardWidget(int iIndex)
64 {
65 Q_UNUSED(iIndex)
66 // Get QML mode for dashboard
67 KConfigSkeleton* skl = SKGMainPanel::getMainPanel()->getPluginByName(QStringLiteral("Dashboard plugin"))->getPreferenceSkeleton();
68 KConfigSkeletonItem* sklItem = skl->findItem(QStringLiteral("qmlmode"));
69 bool qml = sklItem->property().toBool();
70 if (qml) {
71 return new SKGHtmlBoardWidget(SKGMainPanel::getMainPanel(), m_currentBankDocument,
72 getDashboardWidgetTitle(iIndex),
73 QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("skrooge/html/default/alarm.qml")),
74 QStringList() << QStringLiteral("operation") << QStringLiteral("rule"));
75 }
76 return new SKGAlarmBoardWidget(SKGMainPanel::getMainPanel(), m_currentBankDocument);
77 }
78
setupActions(SKGDocument * iDocument)79 bool SKGSearchPlugin::setupActions(SKGDocument* iDocument)
80 {
81 SKGTRACEINFUNC(10)
82
83 m_currentBankDocument = qobject_cast<SKGDocumentBank*>(iDocument);
84 if (m_currentBankDocument == nullptr) {
85 return false;
86 }
87
88 setComponentName(QStringLiteral("skrooge_search"), title());
89 setXMLFile(QStringLiteral("skrooge_search.rc"));
90
91 // Create yours actions here
92 // Execute on all operation
93 auto actExecuteAll = new QAction(SKGServices::fromTheme(QStringLiteral("system-run")), i18nc("Verb, action to execute", "Execute on all operations"), this);
94 connect(actExecuteAll, &QAction::triggered, this, [ = ] { execute(SKGRuleObject::ALL); });
95 registerGlobalAction(QStringLiteral("execute_all"), actExecuteAll, QStringList() << QStringLiteral("rule"), 1, -1, 501);
96
97 // Execute on not checked
98 {
99 QStringList overlaycsv;
100 overlaycsv.push_back(QStringLiteral("document-import"));
101 auto actExecuteImported = new QAction(SKGServices::fromTheme(QStringLiteral("system-run"), overlaycsv), i18nc("Verb, action to execute", "Execute on not checked operations"), this);
102 connect(actExecuteImported, &QAction::triggered, this, [ = ] { execute(SKGRuleObject::NOTCHECKED); });
103 registerGlobalAction(QStringLiteral("execute_notchecked"), actExecuteImported, QStringList() << QStringLiteral("rule"), 1, -1, 502);
104 }
105
106 // Execute on imported operation
107 {
108 QStringList overlaycsv;
109 overlaycsv.push_back(QStringLiteral("document-import"));
110 auto actExecuteImported = new QAction(SKGServices::fromTheme(QStringLiteral("system-run"), overlaycsv), i18nc("Verb, action to execute", "Execute on imported operations"), this);
111 connect(actExecuteImported, &QAction::triggered, this, [ = ] { execute(SKGRuleObject::IMPORTED); });
112 registerGlobalAction(QStringLiteral("execute_imported"), actExecuteImported, QStringList() << QStringLiteral("rule"), 1, -1, 502);
113 }
114
115 // Execute on not validated
116 {
117 QStringList overlaycsv;
118 overlaycsv.push_back(QStringLiteral("dialog-ok"));
119 auto actExecuteNotValidated = new QAction(SKGServices::fromTheme(QStringLiteral("system-run"), overlaycsv), i18nc("Verb, action to execute", "Execute on not validated operations"), this);
120 connect(actExecuteNotValidated, &QAction::triggered, this, [ = ] { execute(SKGRuleObject::IMPORTEDNOTVALIDATE); });
121 registerGlobalAction(QStringLiteral("execute_not_validated"), actExecuteNotValidated, QStringList() << QStringLiteral("rule"), 1, -1, 503);
122 }
123
124 // Search
125 QAction* actSearch = actionCollection()->addAction(KStandardAction::Find, QStringLiteral("edit_find"), this, SLOT(find()));
126 registerGlobalAction(QStringLiteral("edit_find"), actSearch); // Global
127 auto actSearch2 = new QAction(actSearch->icon(), actSearch->text(), this);
128 connect(actSearch2, &QAction::triggered, this, &SKGSearchPlugin::find);
129 registerGlobalAction(QStringLiteral("edit_find_ctx"), actSearch2, QStringList() << QStringLiteral("account") << QStringLiteral("category") << QStringLiteral("refund") << QStringLiteral("payee") << QStringLiteral("operation") << QStringLiteral("suboperation"), 1, -1, 130); // For contextual menus
130
131 return true;
132 }
133
refresh()134 void SKGSearchPlugin::refresh()
135 {
136 SKGTRACEINFUNC(10)
137 // Start alarm
138 if ((m_currentBankDocument != nullptr) && m_currentBankDocument->getMainDatabase() != nullptr) {
139 QString doc_id = m_currentBankDocument->getUniqueIdentifier();
140 if (m_docUniqueIdentifier != doc_id) {
141 m_docUniqueIdentifier = doc_id;
142
143 raiseAlarms();
144 }
145 }
146 }
147
raiseAlarms()148 void SKGSearchPlugin::raiseAlarms()
149 {
150 if (m_currentBankDocument != nullptr) {
151 SKGObjectBase::SKGListSKGObjectBase rules;
152 SKGError err = m_currentBankDocument->getObjects(QStringLiteral("v_rule"), QStringLiteral("t_action_type='A' ORDER BY i_ORDER"), rules);
153 int nb = rules.count();
154 if (!err && (nb != 0)) {
155 for (int i = 0; !err && i < nb; ++i) {
156 SKGRuleObject rule(rules.at(i));
157 err = rule.execute();
158 }
159 }
160
161 // Display error
162 SKGMainPanel::displayErrorMessage(err);
163
164 m_timer.start(skgsearch_settings::alarm_frequency() * 60 * 1000);
165 }
166 }
167
execute(SKGRuleObject::ProcessMode iMode)168 void SKGSearchPlugin::execute(SKGRuleObject::ProcessMode iMode)
169 {
170 SKGError err;
171 SKGTRACEINFUNCRC(1, err)
172
173 // Get rules
174 SKGObjectBase::SKGListSKGObjectBase rules = SKGMainPanel::getMainPanel()->getSelectedObjects();
175
176 int nb = rules.count();
177 if (m_currentBankDocument != nullptr) {
178 SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Process execution"), err, nb)
179 for (int i = 0; !err && i < nb; ++i) {
180 SKGRuleObject rule(rules.at(i));
181 err = rule.execute(iMode);
182 IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
183 }
184 }
185
186 // status bar
187 IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Process executed")))
188 else {
189 err.addError(ERR_FAIL, i18nc("Error message", "Process execution failed"));
190 }
191
192 // Display error
193 SKGMainPanel::displayErrorMessage(err);
194 }
195
find()196 void SKGSearchPlugin::find()
197 {
198 SKGError err;
199 SKGTRACEINFUNCRC(10, err)
200 if (SKGMainPanel::getMainPanel() != nullptr) {
201 SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
202
203 QString xmlsearchcondition;
204 int nb = selection.count();
205 if (nb > 0) {
206 QString table = selection.at(0).getRealTable();
207 if (table == QStringLiteral("account")) {
208 QDomDocument doc(QStringLiteral("SKGML"));
209 QDomElement element = doc.createElement(QStringLiteral("element"));
210 doc.appendChild(element);
211 for (int i = 0; i < nb; ++i) {
212 QDomElement elementLine = doc.createElement(QStringLiteral("element"));
213 element.appendChild(elementLine);
214
215 QDomElement elementElement = doc.createElement(QStringLiteral("element"));
216 elementLine.appendChild(elementElement);
217
218 elementElement.setAttribute(QStringLiteral("attribute"), QStringLiteral("t_ACCOUNT"));
219 elementElement.setAttribute(QStringLiteral("operator"), QStringLiteral("#ATT#='#V1S#'"));
220 elementElement.setAttribute(QStringLiteral("value"), selection.at(i).getAttribute(QStringLiteral("t_name")));
221 }
222 xmlsearchcondition = doc.toString();
223 } else if (table == QStringLiteral("category")) {
224 QDomDocument doc(QStringLiteral("SKGML"));
225 QDomElement element = doc.createElement(QStringLiteral("element"));
226 doc.appendChild(element);
227 for (int i = 0; i < nb; ++i) {
228 QDomElement elementLine = doc.createElement(QStringLiteral("element"));
229 element.appendChild(elementLine);
230
231 QDomElement elementElement = doc.createElement(QStringLiteral("element"));
232 elementLine.appendChild(elementElement);
233
234 elementElement.setAttribute(QStringLiteral("attribute"), QStringLiteral("t_REALCATEGORY"));
235 elementElement.setAttribute(QStringLiteral("operator"), QStringLiteral("#ATT#='#V1S#'"));
236 SKGCategoryObject cat(selection.at(i));
237 elementElement.setAttribute(QStringLiteral("value"), cat.getFullName());
238 }
239 xmlsearchcondition = doc.toString();
240 } else if (table == QStringLiteral("refund")) {
241 QDomDocument doc(QStringLiteral("SKGML"));
242 QDomElement element = doc.createElement(QStringLiteral("element"));
243 doc.appendChild(element);
244 for (int i = 0; i < nb; ++i) {
245 QDomElement elementLine = doc.createElement(QStringLiteral("element"));
246 element.appendChild(elementLine);
247
248 QDomElement elementElement = doc.createElement(QStringLiteral("element"));
249 elementLine.appendChild(elementElement);
250
251 elementElement.setAttribute(QStringLiteral("attribute"), QStringLiteral("t_REALREFUND"));
252 elementElement.setAttribute(QStringLiteral("operator"), QStringLiteral("#ATT#='#V1S#'"));
253 elementElement.setAttribute(QStringLiteral("value"), selection.at(i).getAttribute(QStringLiteral("t_name")));
254 }
255 xmlsearchcondition = doc.toString();
256 } else if (table == QStringLiteral("payee")) {
257 QDomDocument doc(QStringLiteral("SKGML"));
258 QDomElement element = doc.createElement(QStringLiteral("element"));
259 doc.appendChild(element);
260 for (int i = 0; i < nb; ++i) {
261 QDomElement elementLine = doc.createElement(QStringLiteral("element"));
262 element.appendChild(elementLine);
263
264 QDomElement elementElement = doc.createElement(QStringLiteral("element"));
265 elementLine.appendChild(elementElement);
266
267 elementElement.setAttribute(QStringLiteral("attribute"), QStringLiteral("t_PAYEE"));
268 elementElement.setAttribute(QStringLiteral("operator"), QStringLiteral("#ATT#='#V1S#'"));
269 elementElement.setAttribute(QStringLiteral("value"), selection.at(i).getAttribute(QStringLiteral("t_name")));
270 }
271 xmlsearchcondition = doc.toString();
272 } else if (table == QStringLiteral("operation") || table == QStringLiteral("suboperation")) {
273 QStringList attributeForQuery;
274 attributeForQuery << QStringLiteral("d_date") << QStringLiteral("t_number") << QStringLiteral("t_mode") << QStringLiteral("t_PAYEE") << QStringLiteral("t_comment") << QStringLiteral("t_REALCATEGORY") << QStringLiteral("t_status") << QStringLiteral("t_bookmarked") << QStringLiteral("t_imported") << QStringLiteral("t_ACCOUNT") << QStringLiteral("f_REALCURRENTAMOUNT") << QStringLiteral("t_REALREFUND");
275
276 QDomDocument doc(QStringLiteral("SKGML"));
277 QDomElement element = doc.createElement(QStringLiteral("element"));
278 doc.appendChild(element);
279 for (int i = 0; i < nb; ++i) {
280 QDomElement elementLine = doc.createElement(QStringLiteral("element"));
281 element.appendChild(elementLine);
282
283 for (int j = 0; j < attributeForQuery.count(); ++j) {
284 const QString& att = attributeForQuery.at(j);
285 QString attRead = (att == QStringLiteral("t_REALCATEGORY") ? QStringLiteral("t_CATEGORY") : (att == QStringLiteral("f_REALCURRENTAMOUNT") ? QStringLiteral("f_CURRENTAMOUNT") : (att == QStringLiteral("t_REALREFUND") ? QStringLiteral("t_REFUND") : att)));
286
287 SKGObjectBase op(selection.at(i).getDocument(), QStringLiteral("v_operation_display_all"), selection.at(i).getID());
288 op.load();
289
290 QString val = op.getAttribute(attRead);
291 if (!val.isEmpty()) {
292 QDomElement elementElement = doc.createElement(QStringLiteral("element"));
293 elementLine.appendChild(elementElement);
294
295 elementElement.setAttribute(QStringLiteral("attribute"), att);
296 elementElement.setAttribute(QStringLiteral("operator"), att.startsWith(QLatin1String("f_")) || att.startsWith(QLatin1String("i_")) ? QStringLiteral("#ATT#=#V1#") : QStringLiteral("#ATT#='#V1S#'"));
297 elementElement.setAttribute(QStringLiteral("value"), val);
298 }
299 }
300 }
301 xmlsearchcondition = doc.toString();
302 }
303 }
304
305 // Call search plugin
306 SKGMainPanel::getMainPanel()->openPage("skg://skrooge_search_plugin/?currentPage=0&xmlsearchcondition=" % SKGServices::encodeForUrl(xmlsearchcondition));
307 }
308 }
309
getWidget()310 SKGTabPage* SKGSearchPlugin::getWidget()
311 {
312 SKGTRACEINFUNC(10)
313 return new SKGSearchPluginWidget(SKGMainPanel::getMainPanel(), m_currentBankDocument);
314 }
315
getPreferenceWidget()316 QWidget* SKGSearchPlugin::getPreferenceWidget()
317 {
318 SKGTRACEINFUNC(10)
319 auto w = new QWidget();
320 ui.setupUi(w);
321 return w;
322 }
323
getPreferenceSkeleton()324 KConfigSkeleton* SKGSearchPlugin::getPreferenceSkeleton()
325 {
326 return skgsearch_settings::self();
327 }
328
title() const329 QString SKGSearchPlugin::title() const
330 {
331 return i18nc("Noun", "Search and process");
332 }
333
icon() const334 QString SKGSearchPlugin::icon() const
335 {
336 return QStringLiteral("edit-find");
337 }
338
toolTip() const339 QString SKGSearchPlugin::toolTip() const
340 {
341 return i18nc("Noun", "Search and process management");
342 }
343
getOrder() const344 int SKGSearchPlugin::getOrder() const
345 {
346 return 35;
347 }
348
tips() const349 QStringList SKGSearchPlugin::tips() const
350 {
351 QStringList output;
352 output.push_back(i18nc("Description of a tips", "<p>... skrooge can <a href=\"skg://skrooge_search_plugin\">search</a> and automatically process some operations.</p>"));
353 output.push_back(i18nc("Description of a tips", "<p>... you can create alarms based on <a href=\"skg://skrooge_search_plugin\">searches</a>.</p>"));
354 return output;
355 }
356
isInPagesChooser() const357 bool SKGSearchPlugin::isInPagesChooser() const
358 {
359 return true;
360 }
361
advice(const QStringList & iIgnoredAdvice)362 SKGAdviceList SKGSearchPlugin::advice(const QStringList& iIgnoredAdvice)
363 {
364 SKGTRACEINFUNC(10)
365 SKGAdviceList output;
366
367 // Alarms
368 if (!iIgnoredAdvice.contains(QStringLiteral("skgsearchplugin_alarm"))) {
369 SKGObjectBase::SKGListSKGObjectBase rules;
370 SKGError err = m_currentBankDocument->getObjects(QStringLiteral("v_rule"), QStringLiteral("t_action_type='A' ORDER BY i_ORDER"), rules);
371 int nb = rules.count();
372 if (nb != 0) {
373 SKGServices::SKGUnitInfo primary = m_currentBankDocument->getPrimaryUnit();
374 SKGAdvice::SKGAdviceActionList autoCorrections;
375 for (int i = 0; !err && i < nb; ++i) {
376 SKGRuleObject rule(rules.at(i));
377 SKGRuleObject::SKGAlarmInfo alarm = rule.getAlarmInfo();
378 if (alarm.Raised) {
379 double percent = 100 * alarm.Amount / alarm.Limit;
380 if (percent >= 70) {
381 SKGAdvice ad;
382 ad.setUUID("skgsearchplugin_alarm|" % SKGServices::intToString(rule.getID()));
383 ad.setPriority(percent >= 90 ? 9 : 6);
384
385 QString msg = alarm.Message;
386 // Build the message
387 if (alarm.Message.contains(QLatin1String("%3"))) {
388 msg = alarm.Message.arg(m_currentBankDocument->formatMoney(alarm.Amount, primary, false), m_currentBankDocument->formatMoney(alarm.Limit, primary, false), m_currentBankDocument->formatMoney(alarm.Amount - alarm.Limit, primary, false));
389 } else if (alarm.Message.contains(QLatin1String("%2"))) {
390 msg = alarm.Message.arg(m_currentBankDocument->formatMoney(alarm.Amount, primary, false), m_currentBankDocument->formatMoney(alarm.Limit, primary, false));
391 } else if (alarm.Message.contains(QLatin1String("%1"))) {
392 msg = alarm.Message.arg(m_currentBankDocument->formatMoney(alarm.Amount, primary, false));
393 }
394
395 ad.setShortMessage(msg);
396 ad.setLongMessage(i18nc("Advice on making the best (long)", "Take care to your alarms.<br> %1.", msg));
397 autoCorrections.resize(0);
398 {
399 SKGAdvice::SKGAdviceAction a;
400 a.Title = i18nc("Advice on making the best (action)", "Open operations corresponding to this alarm");
401 a.IconName = QStringLiteral("quickopen");
402 a.IsRecommended = false;
403 autoCorrections.push_back(a);
404 }
405 ad.setAutoCorrections(autoCorrections);
406 output.push_back(ad);
407 }
408 }
409 }
410 }
411 }
412
413 return output;
414 }
415
executeAdviceCorrection(const QString & iAdviceIdentifier,int iSolution)416 SKGError SKGSearchPlugin::executeAdviceCorrection(const QString& iAdviceIdentifier, int iSolution)
417 {
418 if ((m_currentBankDocument != nullptr) && iAdviceIdentifier.startsWith(QLatin1String("skgsearchplugin_alarm|"))) {
419 // Get parameters
420 QString id = iAdviceIdentifier.right(iAdviceIdentifier.length() - 22);
421 SKGSearchPluginWidget::open(SKGRuleObject(m_currentBankDocument, SKGServices::stringToInt(id)));
422 return SKGError();
423 }
424
425 return SKGInterfacePlugin::executeAdviceCorrection(iAdviceIdentifier, iSolution);
426 }
427
428 #include <skgsearchplugin.moc>
429