Timesheets-Server
Timesheets server
Načítám...
Vyhledávám...
Nebylo nic nalezeno
database_plugin_postgres.cpp
Zobrazit dokumentaci tohoto souboru.
1
7#include "msettings.h"
8#include "msqlquery.h"
9#include "json.h"
10#include "pdebug.h"
11#include <QUuid>
12#include <QSqlError>
13#include <QCryptographicHash>
14#include <QFile>
15#include <QRandomGenerator>
16
17using namespace Db::Plugins;
18
19namespace Db::Plugins {
20 QString null(const QString& x) {
21 return (x == "0") ? "" : x;
22 }
23}
24
25bool DatabasePluginPostgres::m_upgraded = false;
26
27
28DatabasePluginPostgres::~DatabasePluginPostgres() {
29 close();
30}
31
32
33DatabasePluginPostgres::DatabasePluginPostgres(QObject *parent) : Db::Plugins::DatabasePlugin(parent) {
34 Q_ASSERT(parent != NULL);
35 setObjectName("DatabasePluginPostgres");
36 m_temporaryTableTicketsCreated = false;
37}
38
39
40bool DatabasePluginPostgres::open() {
41 m_db = QSqlDatabase::addDatabase("QPSQL", QUuid::createUuid().toString().toUtf8());
42
43 m_db.setDatabaseName ( m_databasename );
44 m_db.setHostName ( m_hostname );
45 m_db.setPort ( m_port );
46 m_db.setUserName ( m_username );
47 m_db.setPassword ( m_password );
48
49 if (!m_db.open()) {
50 PDEBUG << "Cannot connect to database Postgres";
51 PDEBUG << m_db.lastError().text();
52 return false;
53 }
54
55 upgrade();
56
57 return true;
58}
59
60
61void DatabasePluginPostgres::upgrade() {
62 if (m_upgraded) { return; }
63 PDEBUG;
64 m_upgraded = true;
65 MSqlQuery q(m_db);
66 q.exec("select version from version;");
67 int version = (q.next()) ? q.value(0).toInt() : -1;
68 for (;;) {
69 version++;
70 QString patchname = QString(":/postgres/patch.%1.sql").arg(version, 3, 10, QChar('0'));
71 QFile file(patchname);
72 if (!file.open(QIODevice::ReadOnly)) {
73 return;
74 }
75 PDEBUG << "aplying db patch " << patchname;
76
77 while (!file.atEnd()) {
78 QByteArray command;
79 QByteArray line;
80 do {
81 line = file.readLine();
82 command += line;
83 } while (!line.contains(";") && !file.atEnd());
84 command = command.trimmed();
85 if (command.isEmpty()) { continue; }
86 MSqlQuery q(m_db);
87 if (!q.exec(QString::fromUtf8(command))) {
88 QSqlError e = q.lastError();
89 if (e.type() != QSqlError::NoError) {
90 PDEBUG << q.lastQuery();
91 PDEBUG << e.databaseText();
92 PDEBUG << e.driverText();
93 return;
94 }
95 }
96 }
97
98 q.exec(QString("update version set version = %1;").arg(version));
99 }
100
101 return;
102}
103
104
105bool DatabasePluginPostgres::close() {
106 m_db.close();
107 return true;
108}
109
110
111void DatabasePluginPostgres::begin() {
112 MSqlQuery q(m_db);
113 q.exec("begin;");
114}
115
116
117void DatabasePluginPostgres::commit() {
118 MSqlQuery q(m_db);
119 q.exec("commit;");
120}
121
122
123void DatabasePluginPostgres::changePassword(const QString& login, const QString& oldpassword, const QString& newpassword) {
124 PDEBUG;
125 QString md5new = QString::fromUtf8(QCryptographicHash::hash(newpassword.toUtf8(), QCryptographicHash::Md5).toHex());
126 QString md5old = QString::fromUtf8(QCryptographicHash::hash(oldpassword.toUtf8(), QCryptographicHash::Md5).toHex());
127 QList<Dbt::Users> list;
128 MSqlQuery q(m_db);
129 q.prepare(R"'(select true from users where "user" = :userid and admin)'");
130 q.bindValue(":userid", userId());
131 q.exec();
132 bool admin = q.next();
133
134 if (admin) {
135 // admin can change password for other users
136 PDEBUG << "admin";
137 q.prepare("update users set password = :newpassword where login = :login;");
138 } else {
139 // non-privileged user can change it's own password only, must know the old password
140 PDEBUG << "not admin";
141 q.prepare("update users set password = :newpassword where login = :login and password = :oldpassword and enabled;");
142 }
143 q.bindValue(":userid", userId());
144 q.bindValue(":login", login);
145 q.bindValue(":newpassword", md5new);
146 q.bindValue(":oldpassword", md5old);
147 q.exec();
148 return;
149}
150
151
152QList<Dbt::Users> DatabasePluginPostgres::authenticate(const QString& login, const QString& password) {
153 QString md5 = QString::fromUtf8(QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Md5).toHex());
154 QList<Dbt::Users> list;
155 MSqlQuery q(m_db);
156 q.prepare("select \"user\", login, name, lang, enabled, admin from users where login = :login and password = :password and enabled;");
157 q.bindValue(":login", login);
158 q.bindValue(":password", md5);
159 q.exec();
160 while (q.next()) {
161 int i=0;
162 Dbt::Users x;
163 x.user = q.value(i++).toInt();
164 x.login = q.value(i++).toString();
165 x.name = q.value(i++).toString();
166 x.lang = q.value(i++).toString();
167 x.enabled = q.value(i++).toBool();
168 x.admin = q.value(i++).toBool();
169 list << x;
170 }
171
172 return list;
173}
174
175
176QList<Dbt::Users> DatabasePluginPostgres::users(int id) {
177 QList<Dbt::Users> list;
178 MSqlQuery q(m_db);
179 q.prepare(R"'(
180 select "user", login, name, lang, enabled, admin
181 from users
182 where (:id1 <= 0 or :id2 = "user");
183 )'");
184 q.bindValue(":id1", id);
185 q.bindValue(":id2", id);
186 q.exec();
187 while (q.next()) {
188 int i=0;
189 Dbt::Users x;
190 x.user = q.value(i++).toInt();
191 x.login = q.value(i++).toString();
192 x.name = q.value(i++).toString();
193 x.lang = q.value(i++).toString();
194 x.enabled = q.value(i++).toBool();
195 x.admin = q.value(i++).toBool();
196 list << x;
197 }
198
199 return list;
200}
201
202
203void DatabasePluginPostgres::remove(const Dbt::Categories& id) {
204 MSqlQuery q(m_db);
205 q.prepare(R"'(delete from categories where category = :id;)'");
206 q.bindValue(":id", id.category);
207 q.exec();
208}
209
210
211QVariant DatabasePluginPostgres::currval(const QString& sequence) {
212 MSqlQuery q(m_db);
213 QVariant cv;
214 q.prepare(R"'(select currval(:sequence);)'");
215 q.bindValue(":sequence", sequence);
216 q.exec();
217 if (q.next()) {
218 cv = q.value(0);
219 }
220 return cv;
221}
222
223
224QVariant DatabasePluginPostgres::save(const Dbt::Categories& data) {
225 MSqlQuery q(m_db);
226
227 QVariant parent_category = (data.parent_category.toInt() > 0)
228 ? data.parent_category.toInt()
229 : QVariant();
230
231 QVariant category = (data.category.toInt() > 0)
232 ? data.category.toInt()
233 : QVariant(QVariant::Int);
234
235 if (parent_category.toInt() == category.toInt()) {
236 parent_category = QVariant();
237 }
238
239 // TODO: Kontrola, aby nešlo založit novou kategorii v nadřízené kategorii bez přístupu
240
241 q.begin();
242
243 q.prepare(R"'(select 1 from categories where category = :category;)'");
244 q.bindValue(":category", data.category.toInt());
245 q.exec();
246 bool exists = q.next();
247 if (exists && ! parent_category.isNull()) {
248 q.prepare(R"'(
249 update categories set
250 parent_category = ?,
251 description = ?,
252 price = ?
253 where category = ?
254 )'");
255 q.bindValue(0, parent_category);
256 q.bindValue(1, data.description);
257 q.bindValue(2, data.price);
258 q.bindValue(3, category.toInt());
259 q.exec();
260 }
261
262 if (exists && parent_category.isNull()) {
263 q.prepare(R"'(
264 update categories set
265 description = ?,
266 price = ?
267 where category = ?
268 )'");
269 q.bindValue(0, data.description);
270 q.bindValue(1, data.price);
271 q.bindValue(2, category.toInt());
272 q.exec();
273 }
274
275 if (!exists) {
276 q.prepare(R"string(
277 insert into categories (parent_category, description, price) values (?, ?, ?);
278 )string");
279 q.bindValue(0, parent_category);
280 q.bindValue(1, data.description);
281 q.bindValue(2, data.price);
282 q.exec();
283 category = currval("categories_category_seq");
284 // insert permission for creator
285 q.prepare(R"string(
286 insert into users_categories ("user", category) values (?, ?) on conflict do nothing;
287 )string");
288 q.bindValue(0, userId());
289 q.bindValue(1, category);
290 q.exec();
291 }
292
293 // empty list of users is ignored
294 // if list is set, then replace old list
295 if (!data.users.isEmpty()) {
296 q.prepare(R"'(delete from users_categories where category = ?;)'");
297 q.bindValue(0, category);
298 q.exec();
299 }
300
301 for (int i=0; i<data.users.size(); i++) {
302 q.prepare(R"string(
303 insert into users_categories ("user", category) values (?, ?) on conflict do nothing;
304 )string");
305 q.bindValue(0, data.users[i].toInt());
306 q.bindValue(1, category);
307 q.exec();
308 }
309
310 q.commit();
311
312 return category;
313}
314
315
316QList<Dbt::ClientSettings> DatabasePluginPostgres::clientSettings() {
317 QList<Dbt::ClientSettings> list;
318 MSqlQuery q(m_db);
319
320 q.prepare(R"'(
321 select multiple_timesheets, show_price, can_change_category, edit_categories,
322 show_multiple_timesheets, show_show_price, show_can_change_category, show_edit_categories
323 from client_settings;
324 )'");
325 q.exec();
326 while (q.next()) {
328 int i = 0;
329 x.multiple_timesheets = q.value(i++).toBool();
330 x.show_price = q.value(i++).toBool();
331 x.can_change_category = q.value(i++).toBool();
332 x.edit_categories = q.value(i++).toBool();
333 x.show_multiple_timesheets = q.value(i++).toBool();
334 x.show_show_price = q.value(i++).toBool();
335 x.show_can_change_category = q.value(i++).toBool();
336 x.show_edit_categories = q.value(i++).toBool();
337 list << x;
338 }
339 return list;
340}
341
342
343QList<Dbt::ServerInfo> DatabasePluginPostgres::serverInfo() {
344 QList<Dbt::ServerInfo> list;
345 MSqlQuery q(m_db);
346 q.prepare(R"'(
347 select name, description from server_info;
348 )'");
349 q.exec();
350 while (q.next()) {
352 int i = 0;
353 x.name = q.value(i++).toString();
354 x.description = q.value(i++).toString();
355 list << x;
356 }
357 return list;
358}
359
360
361QVariant DatabasePluginPostgres::save(const Dbt::ServerInfo& data) {
362 MSqlQuery q(m_db);
363
364 q.prepare(R"'(
365 update server_info set
366 name = :name,
367 description = :description
368 ;
369 )'");
370 q.bindValue(":name", data.name);
371 q.bindValue(":description", data.description);
372 q.exec();
373
374 return QVariant();
375}
376
377
378QVariantList pgArrayToVariantList(const QVariant& input) {
379 QStringList x = input.toString().replace("{", "").replace("}", "").split(",");
380 QVariantList list;
381 for (int i=0; i<x.size(); i++) {
382 list << x[i];
383 }
384 return list;
385}
386
387
388QList<Dbt::Categories> DatabasePluginPostgres::categories(const QString& id) {
389 QList<Dbt::Categories> list;
390 MSqlQuery q(m_db);
391
392 q.prepare(R"'(
393 select c.category, c.parent_category, c.description, c.price, ux.users, categories_tree_description(c.category) as description_tree
394 from categories c, users u
395 left join lateral (select array_agg("user") as users from users_categories where category = c.category) ux on (true)
396 where (:id1 <= 0 or :id2 = c.category)
397 and u."user" = :user and (u.admin or u."user" in (select "user" from users_categories uc where uc.category = c.category))
398 ;
399 )'");
400 q.bindValue(":user", userId());
401 q.bindValue(":id1", id.toInt());
402 q.bindValue(":id2", id.toInt());
403 q.exec();
404 while (q.next()) {
405 int i=0;
407 x.category = q.value(i++).toString();
408 x.parent_category = null(q.value(i++).toString());
409 x.description = q.value(i++).toString();
410 x.price = q.value(i++).toDouble();
411 x.users = pgArrayToVariantList(q.value(i++));
412 x.description_tree = q.value(i++).toString();
413 list << x;
414 }
415
416 return list;
417}
418
419
420QList<Dbt::Categories> DatabasePluginPostgres::categoriesToRoot(const QString& id) {
421 QList<Dbt::Categories> list;
422 MSqlQuery q(m_db);
423 int xid = id.toInt();
424
425 q.prepare(R"'(
426 select c.category, c.parent_category, c.description, c.price, ux.users, categories_tree_description(c.category) as description_tree
427 from categories c, users u
428 left join lateral (select array_agg("user") as users from users_categories where category = c.category) ux on (true)
429 where :id = c.category
430 and u."user" = :user and (u.admin or u."user" in (select "user" from users_categories uc where uc.category = c.category))
431 ;
432 )'");
433 for (;;) {
434 q.bindValue(":user", userId());
435 q.bindValue(":id", xid);
436 q.exec();
437 bool found = q.next();
438 PDEBUG << xid << found;
439 if (!found) { return list; }
440 int i=0;
442 x.category = q.value(i++).toString();
443 x.parent_category = null(q.value(i++).toString());
444 x.description = q.value(i++).toString();
445 x.price = q.value(i++).toDouble();
446 x.users = pgArrayToVariantList(q.value(i++));
447 x.description_tree = q.value(i++).toString();
448 list.prepend(x);
449 xid = x.parent_category.toInt();
450 }
451
452 return list;
453}
454
455
456QList<Dbt::Categories> DatabasePluginPostgres::subcategories(const QString& id) {
457 QList<Dbt::Categories> list;
458 MSqlQuery q(m_db);
459
460 q.prepare(R"'(
461 select c.category, c.parent_category, c.description, c.price, ux.users, categories_tree_description(c.category) as description_tree
462 from categories c, users u
463 left join lateral (select array_agg("user") as users from users_categories where category = c.category) ux on (true)
464 where ((:id1 <= 0 and c.parent_category is null) or :id2 = c.parent_category)
465 and u."user" = :user and (u.admin or u."user" in (select "user" from users_categories uc where uc.category = c.category))
466 ;
467 )'");
468 q.bindValue(":user", userId());
469 q.bindValue(":id1", id.toInt());
470 q.bindValue(":id2", id.toInt());
471 q.exec();
472 while (q.next()) {
473 int i=0;
475 x.category = q.value(i++).toString();
476 x.parent_category = null(q.value(i++).toString());
477 x.description = q.value(i++).toString();
478 x.price = q.value(i++).toDouble();
479 x.users = pgArrayToVariantList(q.value(i++));
480 x.description_tree = q.value(i++).toString();
481 list << x;
482 }
483
484 return list;
485}
486
487
488QList<Dbt::Categories> DatabasePluginPostgres::siblingcategories(const QString& id) {
489 QList<Dbt::Categories> list;
490 MSqlQuery q(m_db);
491
492 q.prepare(R"'(
493 with recursive tree as (
494 select category, parent_category
495 from categories where category = :id
496 union all
497 select c.category, c.parent_category
498 from tree t, categories c
499 where
500 t.category = c.parent_category
501 )
502 select c.category, c.parent_category, c.description, c.price, ux.users, categories_tree_description(c.category) as description_tree
503 from categories c, users u
504 left join lateral (select array_agg("user") as users from users_categories where category = c.category) ux on (true)
505 where u."user" = :user and (u.admin or u."user" in (select "user" from users_categories uc where uc.category = c.category))
506 and c.category not in (select category from tree)
507 ;
508 )'");
509 q.bindValue(":user", userId());
510 q.bindValue(":id", id.toInt());
511 q.exec();
512 while (q.next()) {
513 int i=0;
515 x.category = q.value(i++).toString();
516 x.parent_category = null(q.value(i++).toString());
517 x.description = q.value(i++).toString();
518 x.price = q.value(i++).toDouble();
519 x.users = pgArrayToVariantList(q.value(i++));
520 x.description_tree = q.value(i++).toString();
521 list << x;
522 }
523
524 return list;
525}
526
527
528void DatabasePluginPostgres::remove(const Dbt::Users& id) {
529 MSqlQuery q(m_db);
530
531 q.prepare(R"'(select 1 from users where "user" = :id and admin and enabled;)'");
532 q.bindValue(":id", userId());
533 q.exec();
534 if (!q.next()) { return; }
535
536 q.prepare(R"'(delete from users where "user" = :id;)'");
537 q.bindValue(":id", id.user);
538 q.exec();
539}
540
541
542QVariant DatabasePluginPostgres::save(const Dbt::Users& data) {
543 MSqlQuery q(m_db);
544
545 q.prepare(R"'(select 1 from users where "user" = :id and admin and enabled;)'");
546 q.bindValue(":id", userId());
547 q.exec();
548 if (!q.next()) { return QVariant(); }
549
550
551 q.prepare(R"'(select 1 from users where "user" = :id;)'");
552 q.bindValue(":id", data.user);
553 q.exec();
554 if (q.next()) {
555 q.prepare(R"'(
556 update users set
557 login = :login,
558 name = :name,
559 lang = :lang,
560 enabled = :enabled,
561 admin = :admin
562 where "user" = :id1
563 )'");
564 q.bindValue(":id1", data.user);
565 q.bindValue(":login", data.login);
566 q.bindValue(":name", data.name);
567 q.bindValue(":lang", data.lang);
568 q.bindValue(":enabled", data.enabled);
569 q.bindValue(":admin", data.admin);
570 q.exec();
571 return QVariant(data.user);
572 } else {
573
574 q.prepare(R"'(
575 insert into users (login, name, lang, enabled, admin)
576 select :login, :name, :lang, :enabled, :admin
577 where not exists (select 1 from users where "user" = :id1);
578 )'");
579 q.bindValue(":id1", data.user);
580 q.bindValue(":login", data.login);
581 q.bindValue(":name", data.name);
582 q.bindValue(":lang", data.lang);
583 q.bindValue(":enabled", data.enabled);
584 q.bindValue(":admin", data.admin);
585 q.exec();
586
587 return currval("users_user_seq");
588 }
589
590 return QVariant();
591
592}
593
594
595QList<Dbt::StatusOrder> DatabasePluginPostgres::statusOrder(const QString& id) {
596 QList<Dbt::StatusOrder> list;
597 MSqlQuery q(m_db);
598
599 q.prepare(R"'(
600 select id, category, previous_status, next_status from status_order
601 where (:id1 <= 0 or :id2 = id);
602 )'");
603 q.bindValue(":id1", id.toInt());
604 q.bindValue(":id2", id.toInt());
605 q.exec();
606 while (q.next()) {
607 int i = 0;
609 x.id = q.value(i++).toInt();
610 x.category = q.value(i++);
611 x.previous_status = q.value(i++);
612 x.next_status = q.value(i++);
613 list << x;
614 }
615
616 return list;
617
618}
619
620
621void DatabasePluginPostgres::remove(const Dbt::StatusOrder& id) {
622 MSqlQuery q(m_db);
623 q.prepare(R"'(delete from status_order where id = :id;)'");
624 q.bindValue(":id", id.id);
625 q.exec();
626}
627
628
629QVariant DatabasePluginPostgres::save(const Dbt::StatusOrder& data) {
630 MSqlQuery q(m_db);
631
632 q.prepare(R"'(select 1 from status_order where id = :id;)'");
633 q.bindValue(":id", data.id);
634 q.exec();
635 if (q.next()) {
636 q.prepare(R"'(
637 update status_order set
638 category = :category,
639 previous_status = :previous_status,
640 next_status = :next_status
641 where id = :id
642 )'");
643 q.bindValue(":category", data.category);
644 q.bindValue(":previous_status", data.previous_status);
645 q.bindValue(":next_status", data.next_status);
646 q.bindValue(":id", data.id);
647 q.exec();
648 return QVariant(data.id);
649
650 } else {
651
652 q.prepare(R"'(
653 insert into status_order (category, previous_status, next_status)
654 select :category, :previous_status, :next_status
655 )'");
656 q.bindValue(":category", data.category);
657 q.bindValue(":previous_status", data.previous_status);
658 q.bindValue(":next_status", data.next_status);
659 q.bindValue(":id", data.id);
660 q.exec();
661 return currval("status_order_id_seq");
662 }
663
664 return QVariant();
665
666}
667
668
669void DatabasePluginPostgres::createTemporaryTableTickets(int ticket, bool all) {
670 if (m_temporaryTableTicketsCreated) { return; }
671 m_temporaryTableTicketsCreated = true;
672 MSqlQuery q(m_db);
673 q.exec(R"'(
674 create temporary table temporary_tickets (
675 ticket int,
676 category int,
677 date timestamp with time zone,
678 price numeric,
679 description text,
680 "user" int
681 );
682 )'");
683
684 if (all) {
685 PDEBUG << "Vybiram VSE" << ticket;
686 q.prepare(R"'(
687 insert into temporary_tickets (ticket, category, date, price, description, "user")
688 select t.ticket, t.category, t.date, t.price, t.description, t."user"
689 from tickets t, users u
690 where (t.category in (select category from users_categories where "user" = u."user") or u.admin = true)
691 and u."user" = :user
692 and (:ticket1 <= 0 or :ticket2 = t.ticket)
693 ;
694 )'");
695 } else {
696 PDEBUG << "Vybiram pouze otevrene" << ticket;
697 q.prepare(R"'(
698 with
699 ending_status as (
700 select status from statuses where closed
701 ),
702 ticket_last_status as (
703 select t.ticket, tl.status
704 from tickets t
705 left join lateral (select tn.ticket, tn.status from ticket_status tn where tn.ticket = t.ticket order by ticket, date desc limit 1) tl using (ticket)
706 ),
707 closed_tickets as (
708 select distinct ts.ticket from ticket_last_status ts, ending_status es where ts.status = es.status
709 ),
710 active_tickets as (
711 select t1.ticket from tickets t1 where t1.ticket not in (select ticket from closed_tickets)
712 )
713
714 insert into temporary_tickets (ticket, category, date, price, description, "user")
715 select t.ticket, t.category, t.date, t.price, t.description, t."user"
716 from tickets t, users_categories uc
717 where t.ticket in (select ticket from active_tickets)
718 and uc."user" = :user
719 and t.category = uc.category
720 and (:ticket1 <= 0 or :ticket2 = t.ticket)
721 ;
722 )'");
723 }
724 q.bindValue(":user", userId());
725 q.bindValue(":ticket1", ticket);
726 q.bindValue(":ticket2", ticket);
727 q.exec();
728
729 q.exec("select count(1) from temporary_tickets;");
730 q.next();
731 PDEBUG << "Vybranych vet" << q.value(0).toInt();
732
733}
734
735
736QList<Dbt::Tickets> DatabasePluginPostgres::tickets(bool all) {
737 return tickets(-1, all);
738}
739
740
741QList<Dbt::Tickets> DatabasePluginPostgres::tickets(int ticket, bool all) {
742 createTemporaryTableTickets(ticket, all);
743 QList<Dbt::Tickets> list;
744 MSqlQuery q(m_db);
745
746 q.prepare(R"'(
747 select ticket, category, date, price, description, "user"
748 from temporary_tickets ;
749 )'");
750 q.exec();
751 while (q.next()) {
752 Dbt::Tickets x;
753 int i=0;
754 x.ticket = q.value(i++);
755 x.category = q.value(i++);
756 x.date = q.value(i++).toDateTime();
757 x.price = q.value(i++).toDouble();
758 x.description = q.value(i++).toString();
759 x.user = q.value(i++).toInt();
760 list << x;
761 }
762 return list;
763}
764
765
766QList<Dbt::Tickets> DatabasePluginPostgres::tickets(const Dbt::Categories& category, bool all) {
767 createTemporaryTableTickets(-1, all);
768 QList<Dbt::Tickets> list;
769 MSqlQuery q(m_db);
770
771 q.prepare(R"'(
772 select ticket, category, date, price, description, "user"
773 from temporary_tickets
774 where category = :category;
775 )'");
776 q.bindValue(":category", category.category);
777 q.exec();
778 while (q.next()) {
779 Dbt::Tickets x;
780 int i=0;
781 x.ticket = q.value(i++);
782 x.category = q.value(i++);
783 x.date = q.value(i++).toDateTime();
784 x.price = q.value(i++).toDouble();
785 x.description = q.value(i++).toString();
786 x.user = q.value(i++).toInt();
787 list << x;
788 }
789 return list;
790}
791
792
793
794QList<Dbt::TicketsVw> DatabasePluginPostgres::ticketsVw(bool all) {
795 return ticketsVw(-1, all);
796}
797
798
799QList<Dbt::TicketsVw> DatabasePluginPostgres::ticketsVw(int ticket, bool all) {
800 QList<Dbt::Tickets> list1 = tickets(ticket,all);
801 QList<Dbt::TicketsVw> list;
802 MSqlQuery q(m_db);
803 for (int i=0; i<list1.size(); i++) {
804 Dbt::TicketsVw x = list1[i];
805 x.timesheets = ticketTimesheets(list1[i].ticket.toInt(), all);
806 x.statuses = ticketStatus(list1[i].ticket.toInt(), all);
807 x.values = ticketValues(list1[i].ticket.toInt(), all);
808 x.files = ticketFiles(list1[i].ticket.toInt(), all);
809 list << x;
810 }
811
812 return list;
813}
814
815
816QList<Dbt::TicketsVw> DatabasePluginPostgres::ticketsVw(const Dbt::Categories& category, bool all) {
817 QList<Dbt::Tickets> list1 = tickets(category, all);
818 QList<Dbt::TicketsVw> list;
819 MSqlQuery q(m_db);
820 for (int i=0; i<list1.size(); i++) {
821 Dbt::TicketsVw x = list1[i];
822 x.timesheets = ticketTimesheets(list1[i].ticket.toInt(), true);
823 x.statuses = ticketStatus(list1[i].ticket.toInt(), true);
824 x.values = ticketValues(list1[i].ticket.toInt(), true);
825 x.files = ticketFiles(list1[i].ticket.toInt(), true);
826 list << x;
827 }
828
829 return list;
830}
831
832
833void DatabasePluginPostgres::remove(const Dbt::Tickets& id) {
834 MSqlQuery q(m_db);
835 q.prepare(R"'(delete from tickets where ticket = :id;)'");
836 q.bindValue(":id", id.ticket);
837 q.exec();
838}
839
840
841QVariant DatabasePluginPostgres::save(const Dbt::Tickets& data) {
842 MSqlQuery q(m_db);
843
844 bool found = false;
845 if (!data.created) {
846 q.prepare(R"'(select 1 from tickets where ticket = :ticket;)'");
847 q.bindValue(":ticket", data.ticket);
848 q.exec();
849 found = q.next();
850 }
851
852 if (!data.created && found) {
853 q.prepare(R"'(
854 update tickets set
855 category = :category,
856 date = :date,
857 price = :price,
858 description = :description,
859 "user" = :user
860 where ticket = :ticket
861 )'");
862 q.bindValue(":category", data.category);
863 q.bindValue(":date", data.date);
864 q.bindValue(":price", data.price);
865 q.bindValue(":description", data.description);
866 q.bindValue(":user", data.user);
867 q.bindValue(":ticket", data.ticket);
868 q.exec();
869 return QVariant(data.ticket);
870 }
871
872 if (data.created || !found) {
873 q.prepare(R"'(
874 insert into tickets (category, date, price, description, "user")
875 values (:category, :date, :price, :description, :user);
876 )'");
877 q.bindValue(":category", data.category);
878 q.bindValue(":date", data.date);
879 q.bindValue(":price", data.price);
880 q.bindValue(":description", data.description);
881 q.bindValue(":user", data.user);
882 q.exec();
883 return currval("tickets_ticket_seq");
884 }
885
886 Q_UNREACHABLE();
887 qFatal("Should not happen");
888 return QVariant();
889
890}
891
892
893template<typename T>
894QList<T> remapTicket(const QList<T>& input, int ticket) {
895 QList<T> list;
896 QListIterator<T> iterator(input);
897 while (iterator.hasNext()) {
898 T x = iterator.next();
899 x.ticket = ticket;
900 if (ticket <= 0) { x.id = 0; }
901 list << x;
902 }
903 return list;
904}
905
906
907QVariant DatabasePluginPostgres::save(const Dbt::TicketsVw& data) {
908 MSqlQuery q(m_db);
909 q.begin();
910 int ticket = save(dynamic_cast<const Dbt::Tickets&>(data)).toInt();
911 removeTicketsDetails(ticket);
912 save(remapTicket(data.timesheets, ticket));
913 save(remapTicket(data.statuses, ticket));
914 save(remapTicket(data.values, ticket));
915 save(remapTicket(data.files, ticket));
916 q.commit();
917
918 return QVariant();
919}
920
921
922void DatabasePluginPostgres::removeTicketsDetails(int ticket) {
923 MSqlQuery q(m_db);
924 q.exec(QString(R"'(delete from ticket_timesheets where ticket = %1;)'").arg(ticket));
925 q.exec(QString(R"'(delete from ticket_status where ticket = %1;)'").arg(ticket));
926 q.exec(QString(R"'(delete from ticket_values where ticket = %1;)'").arg(ticket));
927 q.exec(QString(R"'(delete from ticket_files where ticket = %1;)'").arg(ticket));
928}
929
930
931QList<Dbt::TicketStatus> DatabasePluginPostgres::ticketStatus(int ticket, bool all) {
932 createTemporaryTableTickets(ticket, all);
933 QList<Dbt::TicketStatus> list;
934 MSqlQuery q(m_db);
935
936 q.prepare(R"'(
937 select ts.id, ts.ticket, ts."user", ts.date, ts.description, ts.status, s.color, s.description, s.closed, s.can_be_run, s.ignored
938 from temporary_tickets t, ticket_status ts, statuses s
939 where t.ticket = ts.ticket
940 and ts.status = s.status
941 and t.ticket = :ticket
942 )'");
943 q.bindValue(":ticket", ticket);
944 q.exec();
945 while (q.next()) {
947 int i=0;
948 x.id = q.value(i++).toInt();
949 x.ticket = q.value(i++).toInt();
950 x.user = q.value(i++).toInt();
951 x.date = q.value(i++).toDateTime();
952 x.description = q.value(i++).toString();
953 x.status = q.value(i++).toString();
954 x.status_color = q.value(i++).toString();
955 x.status_description = q.value(i++).toString();
956 x.status_closed = q.value(i++).toBool();
957 x.status_can_be_run = q.value(i++).toBool();
958 x.status_ignored = q.value(i++).toBool();
959 list << x;
960 }
961 return list;
962}
963
964
965QList<Dbt::TicketStatus> DatabasePluginPostgres::ticketStatus(bool all) {
966 return ticketStatus(-1, all);
967}
968
969
970QList<Dbt::TicketStatus> DatabasePluginPostgres::ticketStatus(int id) {
971 QList<Dbt::TicketStatus> list;
972 MSqlQuery q(m_db);
973
974 q.prepare(R"'(
975 select ts.id, ts.ticket, ts."user", ts.date, ts.description, ts.status, s.color, s.description, s.close, s.can_be_run, s.ignored
976 from ticket_status ts, users u, tickets t, users_categories uc, statuses s
977 where ts.id = :id
978 and t.ticket = ts.ticket
979 and t.category = uc.category
980 and u."user" = ts."user"
981 and ts.status = s.status
982 )'");
983 q.bindValue(":id", id);
984 q.bindValue(":user", userId());
985 q.exec();
986 while (q.next()) {
988 int i=0;
989 x.id = q.value(i++).toInt();
990 x.ticket = q.value(i++).toInt();
991 x.user = q.value(i++).toInt();
992 x.date = q.value(i++).toDateTime();
993 x.description = q.value(i++).toString();
994 x.status = q.value(i++).toString();
995 x.status_color = q.value(i++).toString();
996 x.status_description = q.value(i++).toString();
997 x.status_closed = q.value(i++).toBool();
998 x.status_can_be_run = q.value(i++).toBool();
999 x.status_ignored = q.value(i++).toBool();
1000 list << x;
1001 }
1002 return list;
1003}
1004
1005
1006
1007void DatabasePluginPostgres::remove(const Dbt::TicketStatus& id) {
1008 MSqlQuery q(m_db);
1009 q.prepare(R"'(delete from ticket_status where id = :id;)'");
1010 q.bindValue(":id", id.id);
1011 q.exec();
1012}
1013
1014
1015QVariant DatabasePluginPostgres::save(const Dbt::TicketStatus& data) {
1016 MSqlQuery q(m_db);
1017
1018 bool found = false;
1019 if (!data.created) {
1020 q.prepare(R"'(select 1 from ticket_status where id = :id;)'");
1021 q.bindValue(":id", data.id);
1022 q.exec();
1023 found = q.next();
1024 }
1025
1026 if (!data.created && found) {
1027 q.prepare(R"'(
1028 update ticket_status set
1029 ticket = :ticket,
1030 "user" = :user,
1031 date = :date,
1032 description = :description,
1033 status = :status
1034 where id = :id
1035 )'");
1036 q.bindValue(":id", data.id);
1037 q.bindValue(":user", data.user);
1038 q.bindValue(":ticket", data.ticket);
1039 q.bindValue(":date", data.date);
1040 q.bindValue(":description", data.description);
1041 q.bindValue(":status", data.status);
1042 q.exec();
1043 return QVariant(data.id);
1044 }
1045
1046 if (data.created || !found) {
1047 q.prepare(R"'(
1048 insert into ticket_status (ticket, "user", date, description, status)
1049 values (:ticket, :user, :date, :description, :status)
1050 )'");
1051 q.bindValue(":user", data.user);
1052 q.bindValue(":ticket", data.ticket);
1053 q.bindValue(":date", data.date);
1054 q.bindValue(":description", data.description);
1055 q.bindValue(":status", data.status);
1056 q.exec();
1057
1058 return currval("ticket_status_id_seq");
1059 }
1060
1061 Q_UNREACHABLE();
1062 qFatal("Should not happen");
1063 return QVariant();
1064}
1065
1066
1067QVariant DatabasePluginPostgres::save(const Dbt::AppendStatuses& data) {
1068 PDEBUG << data.toMap();
1069
1070 QString statusesX;
1071 QStringList statusesL;
1072 for (int i=0; i<data.recent_status.size(); i++) {
1073 statusesL << "'" + data.recent_status[i].toString() + "'";
1074 }
1075 statusesL.sort();
1076 statusesX = statusesL.join(",");
1077
1078 QString categoriesX;
1079 QStringList categoriesL;
1080 for (int i=0; i<data.categories.size(); i++) {
1081 categoriesL << "'" + data.categories[i].toString() + "'";
1082 }
1083 categoriesL.sort();
1084 categoriesX = categoriesL.join(",");
1085
1086 MSqlQuery q(m_db);
1087 q.prepare(QString(R"'(
1088 with
1089
1090 -- select statuses
1091 x_statuses as (
1092 select * from statuses
1093 where status in (%1)
1094 and not ignored
1095 ),
1096
1097 -- select new status, if possible
1098 x_newstatuses as (
1099 select distinct s.*
1100 from statuses s, status_order o
1101 where status = :status
1102 and s.status = o.next_status
1103 and o.previous_status in (select status from x_statuses)
1104 ),
1105
1106 -- select current user
1107 x_users as (
1108 select *
1109 from users
1110 where "user" = :userid
1111 ),
1112
1113 -- select valid categories
1114 x_categories as (
1115 select c.*
1116 from categories c, x_users u
1117 where c.category in (%2)
1118 and (u.admin = true
1119 or c.category in (select category from users_categories where "user" = u."user")
1120 )
1121 ),
1122
1123 -- select tickets with statuses, user, categories
1124 x_tickets as (
1125 select t.*, ls.*
1126 from tickets t
1127 join x_categories using (category)
1128 left join lateral (select ts.status
1129 from ticket_status ts,
1130 statuses s
1131 where t.ticket = ts.ticket
1132 and s.status = ts.status
1133 and not s.ignored
1134 order by date desc
1135 limit 1) ls on (true)
1136 where ls.status in (select status from x_statuses)
1137 )
1138
1139 insert into ticket_status (ticket, "user", description, status)
1140 select t.ticket, u."user", :description, n.status
1141 from x_tickets t, x_users u, x_newstatuses n
1142
1143 )'").arg(statusesX).arg(categoriesX));
1144 q.bindValue(":user", data.status);
1145 q.bindValue(":status", data.status);
1146 q.bindValue(":description", data.description);
1147 q.bindValue(":userid", userId());
1148 PDEBUG << q.lastBoundQuery();
1149 q.exec();
1150
1151 return QVariant();
1152}
1153
1154
1155QList<Dbt::TicketTimesheets> DatabasePluginPostgres::ticketTimesheets(int ticket, bool all) {
1156 createTemporaryTableTickets(ticket, all);
1157 QList<Dbt::TicketTimesheets> list;
1158 MSqlQuery q(m_db);
1159 q.prepare(R"'(
1160 select tt.id, tt.ticket, tt."user", tt.date_from, tt.date_to
1161 from temporary_tickets t, ticket_timesheets tt
1162 where t.ticket = tt.ticket
1163 and t.ticket = :ticket
1164 order by tt.date_from -- must be sorted!
1165 ;
1166 )'");
1167 q.bindValue(":ticket", ticket);
1168 q.exec();
1169 while (q.next()) {
1170 int i=0;
1172 x.id = q.value(i++).toInt();
1173 x.ticket = q.value(i++).toInt();
1174 x.user = q.value(i++).toInt();
1175 x.date_from = q.value(i++).toDateTime();
1176 x.date_to = q.value(i++).toDateTime();
1177 list << x;
1178 }
1179 return list;
1180}
1181
1182
1183QList<Dbt::TicketTimesheets> DatabasePluginPostgres::ticketTimesheets(int id) {
1184 QList<Dbt::TicketTimesheets> list;
1185 MSqlQuery q(m_db);
1186 q.prepare(R"'(
1187 select tt.id, tt.ticket, tt."user", tt.date_from, tt.date_to
1188 from ticket_timesheets tt, tickets t, users_categories uc
1189 where t.ticket = tt.ticket
1190 and t.category = uc.category
1191 and uc."user" = :user
1192 and :id = tt.id
1193 order by tt.date_from
1194 ;
1195 )'");
1196 q.bindValue(":user", userId());
1197 q.bindValue(":id", id);
1198 q.exec();
1199 while (q.next()) {
1200 int i=0;
1202 x.id = q.value(i++).toInt();
1203 x.ticket = q.value(i++).toInt();
1204 x.user = q.value(i++).toInt();
1205 x.date_from = q.value(i++).toDateTime();
1206 x.date_to = q.value(i++).toDateTime();
1207 list << x;
1208 }
1209 return list;
1210}
1211
1212
1213QList<Dbt::TicketTimesheets> DatabasePluginPostgres::ticketTimesheets(bool all) {
1214 return ticketTimesheets(-1, all);
1215}
1216
1217
1218QList<Dbt::TicketTimesheets> DatabasePluginPostgres::runningTimesheets(int ticket) {
1219 QList<Dbt::TicketTimesheets> list;
1220 MSqlQuery q(m_db);
1221 q.prepare(R"'(
1222 select tt.id, tt.ticket, tt."user", tt.date_from, tt.date_to
1223 from ticket_timesheets tt, tickets t, users_categories uc
1224 where t.ticket = tt.ticket
1225 and t.category = uc.category
1226 and uc."user" = :user
1227 and tt.date_to is null
1228 and (:ticket1 = tt.ticket or :ticket2 <= 0)
1229 ;
1230 )'");
1231 q.bindValue(":user", userId());
1232 q.bindValue(":ticket1", ticket);
1233 q.bindValue(":ticket2", ticket);
1234 q.exec();
1235 while (q.next()) {
1236 int i=0;
1238 x.id = q.value(i++).toInt();
1239 x.ticket = q.value(i++).toInt();
1240 x.user = q.value(i++).toInt();
1241 x.date_from = q.value(i++).toDateTime();
1242 x.date_to = q.value(i++).toDateTime();
1243 list << x;
1244 }
1245 return list;
1246}
1247
1248
1249QList<Dbt::TicketTimesheets> DatabasePluginPostgres::startTimesheet(int ticket) {
1250 QList<Dbt::TicketTimesheets> list;
1251 MSqlQuery q(m_db);
1252
1253 // Zkontroluj, jestli je ticket dostupný pro uživatele a je zastavený
1254 q.prepare(R"'(
1255 select 1
1256 from ticket_timesheets tt, tickets t, users_categories uc
1257 where t.ticket = tt.ticket
1258 and t.category = uc.category
1259 and uc."user" = :user
1260 and :ticket = tt.ticket
1261 and tt.date_to is null
1262 ;
1263 )'");
1264 q.bindValue(":user", userId());
1265 q.bindValue(":ticket", ticket);
1266 q.exec();
1267 if (q.next()) {
1268 return list;
1269 }
1270
1271 // Zkontroluj, jestli je ticket dostupný pro uživatele a existující
1272 q.prepare(R"'(
1273 select 1
1274 from tickets t, users_categories uc
1275 where t.category = uc.category
1276 and uc."user" = :user
1277 and :ticket = t.ticket
1278 ;
1279 )'");
1280 q.bindValue(":user", userId());
1281 q.bindValue(":ticket", ticket);
1282 q.exec();
1283 if (!q.next()) {
1284 return list;
1285 }
1286
1287 // Vložit timesheet
1288 q.prepare(R"'(
1289 insert into ticket_timesheets (ticket, "user", date_from)
1290 values (:ticket, :user, now())
1291 )'");
1292 q.bindValue(":user", userId());
1293 q.bindValue(":ticket", ticket);
1294 q.exec();
1295 QVariant id = currval("ticket_timesheets_id_seq").toInt();
1296 if (id.isNull() || !id.isValid()) {
1297 return list;
1298 }
1299
1300 list = ticketTimesheets(id.toInt());
1301 return list;
1302}
1303
1304
1305QList<Dbt::TicketTimesheets> DatabasePluginPostgres::stopTimesheet(int ticket) {
1306 QList<Dbt::TicketTimesheets> list;
1307 MSqlQuery q(m_db);
1308
1309 q.begin();
1310 // Zkontroluj, jestli je ticket dostupný pro uživatele a je běžící
1311 q.prepare(R"'(
1312 select tt.id
1313 from ticket_timesheets tt, tickets t, users_categories uc
1314 where t.ticket = tt.ticket
1315 and t.category = uc.category
1316 and uc."user" = :user
1317 and :ticket = tt.ticket
1318 and tt.date_to is null
1319 ;
1320 )'");
1321 q.bindValue(":user", userId());
1322 q.bindValue(":ticket", ticket);
1323 q.setForwardOnly(false);
1324 q.exec();
1325 if (q.size() != 1) {
1326 q.commit();
1327 return list;
1328 }
1329 q.next();
1330 int id = q.value(0).toInt();
1331
1332 // Ukončit timesheet
1333 q.prepare(R"'(
1334 update ticket_timesheets set date_to = now() where id = :id;
1335 )'");
1336 q.bindValue(":id", id);
1337 q.exec();
1338
1339 list = ticketTimesheets(ticket, true);
1340
1341 int remove_secs = 10;
1342 int round2_mins = 5;
1343 int round5_mins = 5;
1344 int join_mins = 5;
1345 int remove_singles_mins = 3;
1346
1347 // step 1 - remove timesheets shorter than ...
1348 for (int i=list.size()-1; i>=0; i--) {
1349 Dbt::TicketTimesheets& x = list[i];
1350 if (x.date_from.secsTo(x.date_to) > remove_secs) { continue; }
1351 list.removeAt(i);
1352 }
1353
1354 // step2 - round timeshiits to ...
1355 for (int i=list.size()-1; i>=0; i--) {
1356 Dbt::TicketTimesheets& x = list[i];
1357 int secs;
1358 secs = x.date_from.secsTo(x.date_to);
1359 secs = secs / (round2_mins * 60) + ( (secs % (round2_mins*60)) ? 1 : 0);
1360 secs = secs * (round2_mins * 60);
1361 x.date_to = x.date_from.addSecs(secs);
1362 }
1363
1364 // step3 - join overlapinng records
1365 for (int i=list.size()-1; i>=1; i--) {
1366 Dbt::TicketTimesheets& x0 = list[i-1];
1367 Dbt::TicketTimesheets& x1 = list[i];
1368
1369 if (x0.date_to < x1.date_from) { continue; }
1370 x0.date_to = x1.date_to;
1371 list.removeAt(i);
1372 }
1373
1374 // step4 - join near records
1375 for (int i=list.size()-1; i>=1; i--) {
1376 Dbt::TicketTimesheets& x0 = list[i-1];
1377 Dbt::TicketTimesheets& x1 = list[i];
1378 if (x0.date_to.addSecs(join_mins*60) < x1.date_from) { continue; }
1379 x0.date_to = x1.date_to;
1380 list.removeAt(i);
1381 }
1382
1383 // step5 - round timetracks to ...
1384 for (int i=list.size()-1; i>=0; i--) {
1385 Dbt::TicketTimesheets& x = list[i];
1386 int secs;
1387 secs = x.date_from.secsTo(x.date_to);
1388 secs = secs + (round5_mins * 60) -1;
1389 secs = secs / (round5_mins * 60);
1390 secs = secs * (round5_mins * 60);
1391 x.date_to = x.date_from.addSecs(secs);
1392 }
1393
1394 // step6 - remove short single records
1395 for (int i=list.size()-1; i>=0; i--) {
1396 Dbt::TicketTimesheets& x = list[i];
1397 int secs = x.date_from.secsTo(x.date_to);
1398 if (secs <= 0) { continue; }
1399 if (secs >= remove_singles_mins * 60) { continue; }
1400 list.removeAt(i);
1401 }
1402
1403 q.prepare(R"'(delete from ticket_timesheets where ticket = :ticket;)'");
1404 q.bindValue(":ticket", ticket);
1405 q.exec();
1406
1407 q.prepare(R"'(insert into ticket_timesheets (ticket, "user", date_from, date_to)
1408 values (:ticket, :user, :date_from, :date_to);
1409 )'");
1410 for (int i=0; i<list.size(); i++) {
1411 Dbt::TicketTimesheets& x = list[i];
1412 q.bindValue(":ticket", ticket);
1413 q.bindValue(":user", userId());
1414 q.bindValue(":date_from", x.date_from);
1415 q.bindValue(":date_to", x.date_to);
1416 q.exec();
1417 }
1418
1419 q.commit();
1420 return list.mid(list.size()-1);
1421}
1422
1423
1424QList<Dbt::TicketTimesheets> DatabasePluginPostgres::toggleTimesheet(int ticket) {
1425 QList<Dbt::TicketTimesheets> list;
1426 MSqlQuery q(m_db);
1427
1428 // Zkontroluj, jestli je ticket dostupný pro uživatele a existující
1429 q.prepare(R"'(
1430 select 1
1431 from tickets t, users_categories uc
1432 where t.category = uc.category
1433 and uc."user" = :user
1434 and :ticket = t.ticket
1435 ;
1436 )'");
1437 q.bindValue(":user", userId());
1438 q.bindValue(":ticket", ticket);
1439 q.exec();
1440 if (!q.next()) {
1441 return list;
1442 }
1443
1444 // Zkontroluj, jestli je ticket dostupný pro uživatele a je běžící
1445 q.prepare(R"'(
1446 select tt.id
1447 from ticket_timesheets tt, tickets t, users_categories uc
1448 where t.ticket = tt.ticket
1449 and t.category = uc.category
1450 and uc."user" = :user
1451 and :ticket = tt.ticket
1452 and tt.date_to is null
1453 ;
1454 )'");
1455 q.bindValue(":user", userId());
1456 q.bindValue(":ticket", ticket);
1457 q.setForwardOnly(false);
1458 q.exec();
1459 bool found = (q.size() == 1);
1460 int id = (found) ? q.next(), q.value(0).toInt() : 0;
1461
1462 if (!found) {
1463 // Vložit timesheet
1464 q.prepare(R"'(
1465 insert into ticket_timesheets (ticket, "user", date_from)
1466 values (:ticket, :user, now())
1467 )'");
1468 q.bindValue(":user", userId());
1469 q.bindValue(":ticket", ticket);
1470 q.exec();
1471 QVariant newid = currval("ticket_timesheets_id_seq");
1472 if (newid.isNull() || !newid.isValid()) {
1473 return list;
1474 }
1475 id = newid.toInt();
1476 }
1477
1478 if (found) {
1479 // Ukončit timesheet
1480 q.prepare(R"'(
1481 update ticket_timesheets set date_to = now() where id = :id;
1482 )'");
1483 q.bindValue(":id", id);
1484 q.exec();
1485 }
1486
1487 list = ticketTimesheets(id);
1488 return list;
1489}
1490
1491
1492QVariant DatabasePluginPostgres::save(const Dbt::TicketTimesheets& data) {
1493 MSqlQuery q(m_db);
1494
1495 bool found = false;
1496 if (!data.created) {
1497 q.prepare(R"'(select 1 from ticket_timesheets where id = :id;)'");
1498 q.bindValue(":id", data.id);
1499 q.exec();
1500 found = q.next();
1501 }
1502
1503 if (!data.created && found) {
1504 q.prepare(R"'(
1505 update ticket_timesheets set
1506 ticket = :ticket,
1507 "user" = :user,
1508 date_from = :date_from,
1509 date_to = :date_to
1510 where id = :id
1511 )'");
1512 q.bindValue(":id", data.id);
1513 q.bindValue(":user", data.user);
1514 q.bindValue(":ticket", data.ticket);
1515 q.bindValue(":date_from", data.date_from);
1516 q.bindValue(":date_to", data.date_to);
1517 q.exec();
1518 return QVariant(data.id);
1519 }
1520
1521 if (data.created || !found) {
1522 q.prepare(R"'(
1523 insert into ticket_timesheets (ticket, "user", date_from, date_to)
1524 values (:ticket, :user, :date_from, :date_to)
1525 )'");
1526 q.bindValue(":user", data.user);
1527 q.bindValue(":ticket", data.ticket);
1528 q.bindValue(":date_from", data.date_from);
1529 q.bindValue(":date_to", data.date_to);
1530 q.exec();
1531
1532 return currval("ticket_timesheets_id_seq");
1533 }
1534
1535 Q_UNREACHABLE();
1536 return QVariant();
1537}
1538
1539
1540void DatabasePluginPostgres::remove(const Dbt::TicketTimesheets& id) {
1541 MSqlQuery q(m_db);
1542 q.prepare(R"'(delete from ticket_timesheets where id = :id;)'");
1543 q.bindValue(":id", id.id);
1544 q.exec();
1545}
1546
1547
1548QList<Dbt::TicketValues> DatabasePluginPostgres::ticketValues(int ticket, bool all) {
1549 createTemporaryTableTickets(ticket, all);
1550 QList<Dbt::TicketValues> list;
1551 MSqlQuery q(m_db);
1552 q.prepare(R"'(
1553 select tv.id, tt.ticket, tv.name, tv.value, tv."user", tv.date
1554 from temporary_tickets tt, ticket_values tv
1555 where tt.ticket = tv.ticket
1556 ;
1557 )'");
1558 q.exec();
1559 while (q.next()) {
1560 int i=0;
1562 x.id = q.value(i++).toInt();
1563 x.ticket = q.value(i++).toInt();
1564 x.name = q.value(i++).toString();
1565 x.value = JSON::data(q.value(i++).toByteArray());
1566 x.user = q.value(i++).toInt();
1567 x.date = q.value(i++).toDateTime();
1568 list << x;
1569 }
1570 return list;
1571}
1572
1573
1574QList<Dbt::TicketValues> DatabasePluginPostgres::ticketValues(int id) {
1575 QList<Dbt::TicketValues> list;
1576 MSqlQuery q(m_db);
1577 q.prepare(R"'(
1578 select tv.id, tv.ticket, tv.date, tv.name, tv.value, tv."user"
1579 from tickets tt, ticket_values tv, users_categories uc
1580 where tt.ticket = tv.ticket
1581 and tt.category = uc.category
1582 and uc.user = :user
1583 and :id = tv.id
1584 ;
1585 )'");
1586 q.bindValue(":id", id);
1587 q.bindValue(":user", userId());
1588 q.exec();
1589 while (q.next()) {
1590 int i=0;
1592 x.id = q.value(i++).toInt();
1593 x.ticket = q.value(i++).toInt();
1594 x.date = q.value(i++).toDateTime();
1595 x.name = q.value(i++).toString();
1596 x.value = JSON::data(q.value(i++).toByteArray());
1597 x.user = q.value(i++).toInt();
1598 list << x;
1599 }
1600 return list;
1601}
1602
1603
1604QList<Dbt::TicketValues> DatabasePluginPostgres::ticketValues(bool all) {
1605 return ticketValues(-1, all);
1606}
1607
1608
1609QVariant DatabasePluginPostgres::save(const Dbt::TicketValues& data) {
1610 MSqlQuery q(m_db);
1611
1612 bool found = false;
1613 if (!data.created) {
1614 q.prepare(R"'(select 1 from ticket_values where id = :id;)'");
1615 q.bindValue(":id", data.id);
1616 q.exec();
1617 found = q.next();
1618 }
1619
1620 if (!data.created && found) {
1621 q.prepare(QString(R"'(
1622 update ticket_values set
1623 ticket = :ticket,
1624 "user" = :user,
1625 date = :date,
1626 name = :name,
1627 value = '%1'
1628 where id = :id
1629 )'").arg(QString::fromUtf8(JSON::json(data.value))));
1630 q.bindValue(":id", data.id);
1631 q.bindValue(":ticket", data.ticket);
1632 q.bindValue(":user", data.user);
1633 q.bindValue(":date", data.date);
1634 q.bindValue(":name", data.name);
1635 // q.bindValue(":value", JSON::json(data.value));
1636 q.exec();
1637 return QVariant(data.id);
1638 }
1639
1640 if (data.created || !found) {
1641 q.prepare(QString(R"'(
1642 insert into ticket_values (ticket, "user", date, name, value)
1643 select :ticket, :user, :date, :name, '%1'
1644 where not exists (select 1 from ticket_values where id = :id);
1645 )'").arg(QString::fromUtf8(JSON::json(data.value))));
1646 q.bindValue(":id", data.id);
1647 q.bindValue(":ticket", data.ticket);
1648 q.bindValue(":user", data.user);
1649 q.bindValue(":date", data.date);
1650 q.bindValue(":name", data.name);
1651 // q.bindValue(":value", JSON::json(data.value));
1652 q.exec();
1653
1654 return currval("ticket_values_id_seq");
1655 }
1656
1657 Q_UNREACHABLE();
1658 qFatal("Should not happen");
1659 return QVariant();
1660}
1661
1662
1663void DatabasePluginPostgres::remove(const Dbt::TicketValues& id) {
1664 MSqlQuery q(m_db);
1665 q.prepare(R"'(delete from ticket_values where id = :id;)'");
1666 q.bindValue(":id", id.id);
1667 q.exec();
1668}
1669
1670
1671QList<Dbt::Statuses> DatabasePluginPostgres::statuses(const QString& id) {
1672 QList<Dbt::Statuses> list;
1673 MSqlQuery q(m_db);
1674 if (id.isEmpty() || id == "") {
1675 q.prepare(R"'(
1676 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored,
1677 n.status, n.description, n.abbreviation, n.color, n.closed, n.can_be_run, n.ignored
1678 from statuses s
1679 left join status_order o on (s.status = o.previous_status)
1680 left join statuses n on (o.next_status = n.status)
1681 order by s.status, n.status
1682 )'");
1683 } else {
1684 q.prepare(R"'(
1685 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored,
1686 n.status, n.description, n.abbreviation, n.color, n.closed, n.can_be_run, n.ignored
1687 from statuses s
1688 left join status_order o on (s.status = o.previous_status)
1689 left join statuses n on (o.next_status = n.status)
1690 where (:id = s.status)
1691 order by s.status, n.status
1692 )'");
1693 }
1694 q.bindValue(":id", id);
1695 q.exec();
1696 Dbt::Statuses x;
1697 while (q.next()) {
1698 int i=0;
1699 if (!x.status.isEmpty() && x.status != q.value(0).toString()) {
1700 list << x;
1701 x.clear();
1702 }
1703 x.status = q.value(i++).toString();
1704 x.description = q.value(i++).toString();
1705 x.abbreviation = q.value(i++).toString();
1706 x.color = q.value(i++).toString();
1707 x.closed = q.value(i++).toBool();
1708 x.can_be_run = q.value(i++).toBool();
1709 x.ignored = q.value(i++).toBool();
1710 x.can_have_next = true;
1711 if (!q.value(i).isNull()) {
1712 Dbt::Statuses n;
1713 n.status = q.value(i++).toString();
1714 n.description = q.value(i++).toString();
1715 n.abbreviation = q.value(i++).toString();
1716 n.color = q.value(i++).toString();
1717 n.closed = q.value(i++).toBool();
1718 n.can_be_run = q.value(i++).toBool();
1719 n.ignored = q.value(i++).toBool();
1720 x.next << n;
1721 }
1722 }
1723 if (!x.status.isEmpty()) {
1724 list << x;
1725 }
1726 return list;
1727}
1728
1729
1730QList<Dbt::Statuses> DatabasePluginPostgres::statuses(const QString& category, const QString& previousStatus) {
1731 if ((category.isEmpty() || category == "") &&
1732 (previousStatus.isEmpty() || previousStatus == "")) { return statuses(QString()); }
1733 int categoryi = category.toInt();
1734 MSqlQuery q(m_db);
1735 QString ps = (previousStatus.isEmpty()) ? "0" : previousStatus;
1736 auto results = [&q]() {
1737 QList<Dbt::Statuses> list;
1738 q.exec();
1739 while (q.next()) {
1740 Dbt::Statuses x;
1741 int i=0;
1742 x.status = q.value(i++).toString();
1743 x.description = q.value(i++).toString();
1744 x.abbreviation = q.value(i++).toString();
1745 x.color = q.value(i++).toString();
1746 x.closed = q.value(i++).toBool();
1747 x.can_be_run = q.value(i++).toBool();
1748 x.ignored = q.value(i++).toBool();
1749 list << x;
1750 }
1751 return list;
1752 };
1753
1754 bool findNullCategory = (category.isEmpty() || category == "");
1755 if (!findNullCategory) {
1756 q.prepare(R"'(select 1 from status_order where category = :category;)'");
1757 q.bindValue(":category", categoryi);
1758 q.exec();
1759 findNullCategory = !q.next();
1760 }
1761
1762 if (findNullCategory && (previousStatus.isEmpty() || previousStatus == "")) {
1763 q.prepare(R"'(
1764 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored
1765 from statuses s, status_order o
1766 where s.status = o.next_status
1767 and (o.previous_status is null or o.previous_status = '')
1768 and (o.category is null);
1769 )'");
1770 return results();
1771 }
1772
1773 if (findNullCategory) {
1774 q.prepare(R"'(
1775 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored
1776 from statuses s, status_order o
1777 where s.status = o.next_status
1778 and o.previous_status = :previous_status
1779 and (o.category is null);
1780 )'");
1781 q.bindValue(":previous_status", ps);
1782 return results();
1783 }
1784
1785 if (!findNullCategory && (previousStatus.isEmpty() || previousStatus == "")) {
1786 q.prepare(R"'(
1787 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored
1788 from statuses s, status_order o
1789 where s.status = o.next_status
1790 and (o.previous_status is null or o.previous_status = '')
1791 and o.category = :category;
1792 )'");
1793 q.bindValue(":category", categoryi);
1794 return results();
1795 }
1796
1797
1798 if (!findNullCategory) {
1799 q.prepare(R"'(
1800 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored
1801 from statuses s, status_order o
1802 where s.status = o.next_status
1803 and o.previous_status = :previous_status
1804 and o.category = :category;
1805 )'");
1806 q.bindValue(":previous_status", ps);
1807 q.bindValue(":category", categoryi);
1808 return results();
1809 }
1810
1811 return QList<Dbt::Statuses>();
1812
1813}
1814
1815
1816void DatabasePluginPostgres::remove(const Dbt::Statuses& id) {
1817 MSqlQuery q(m_db);
1818 q.prepare(R"'(delete from statuses where status = :id;)'");
1819 q.bindValue(":id", id.status);
1820 q.exec();
1821}
1822
1823
1824QVariant DatabasePluginPostgres::save(const Dbt::Statuses& data) {
1825 MSqlQuery q(m_db);
1826
1827 begin();
1828 q.prepare(R"'(
1829 update statuses set
1830 description = :description,
1831 abbreviation = :abbreviation,
1832 color = :color,
1833 closed = :closed,
1834 can_be_run = :can_be_run,
1835 ignored = :ignored
1836 where status = :status
1837 )'");
1838 q.bindValue(":description", data.description);
1839 q.bindValue(":abbreviation", data.abbreviation);
1840 q.bindValue(":color", data.color);
1841 q.bindValue(":closed", data.closed);
1842 q.bindValue(":can_be_run", data.can_be_run);
1843 q.bindValue(":ignored", data.ignored);
1844 q.bindValue(":status", data.status);
1845 q.exec();
1846
1847 q.prepare(R"'(
1848 insert into statuses (status, description, abbreviation, color, closed, can_be_run, ignored)
1849 select :status1, :description, :abbreviation, :color, :closed, :can_be_run, :ignored
1850 where not exists (select 1 from statuses where status = :status2);
1851 )'");
1852 q.bindValue(":status1", data.status);
1853 q.bindValue(":description", data.description);
1854 q.bindValue(":abbreviation", data.abbreviation);
1855 q.bindValue(":color", data.color);
1856 q.bindValue(":closed", data.closed);
1857 q.bindValue(":can_be_run", data.can_be_run);
1858 q.bindValue(":ignored", data.ignored);
1859 q.bindValue(":status2", data.status);
1860 q.exec();
1861
1862 q.prepare(R"'(
1863 delete from status_order
1864 where previous_status = :status
1865 )'");
1866 q.bindValue(":status", data.status);
1867 q.exec();
1868
1869 for (int i=0; i<data.next.size(); i++) {
1870 q.prepare(R"'(
1871 insert into status_order (previous_status, next_status)
1872 values (:previous_status, :next_status);
1873 )'");
1874 q.bindValue(":previous_status", data.status);
1875 q.bindValue(":next_status", data.next[i].status);
1876 q.exec();
1877 }
1878
1879 commit();
1880
1881 return QVariant(data.status);
1882}
1883
1884
1885QList<Dbt::StatusTemplates> DatabasePluginPostgres::statusTemplates(int id) {
1886 Q_UNUSED(id);
1887 MSqlQuery q(m_db);
1888 QList<Dbt::StatusTemplates> list;
1889 return list;
1890}
1891
1892
1893QList<Dbt::TicketFiles> DatabasePluginPostgres::ticketFiles(int ticket, bool all) {
1894 createTemporaryTableTickets(ticket, all);
1895 QList<Dbt::TicketFiles> list;
1896 MSqlQuery q(m_db);
1897
1898 q.prepare(R"'(
1899 select f.id, f."user", f.date, f.ticket, f.name, f.type, f.content
1900 from temporary_tickets t, ticket_files f
1901 where t.ticket = f.ticket
1902 ;
1903 )'");
1904 q.exec();
1905 while (q.next()) {
1906 int i=0;
1908 x.id = q.value(i++).toInt();
1909 x.user = q.value(i++).toInt();
1910 x.date = q.value(i++).toDateTime();
1911 x.ticket = q.value(i++).toInt();
1912 x.name = q.value(i++).toString();
1913 x.type = q.value(i++).toString();
1914 x.content = q.value(i++).toByteArray();
1915 list << x;
1916 }
1917
1918 return list;
1919}
1920
1921
1922QList<Dbt::TicketFiles> DatabasePluginPostgres::ticketFiles(int id) {
1923 QList<Dbt::TicketFiles> list;
1924 MSqlQuery q(m_db);
1925
1926 q.prepare(R"'(
1927 select f.id, f."user", f.date, f.ticket, f.name, f.type, f.content
1928 from ticket_files f, users u, tickets t, users_categories uc
1929 where f.id = :id
1930 and t.ticket = f.ticket
1931 and t.category = uc.category
1932 and u."user" = f."user"
1933 ;
1934 )'");
1935 q.bindValue(":id", id);
1936 q.bindValue(":user", userId());
1937 q.exec();
1938 while (q.next()) {
1939 int i=0;
1941 x.id = q.value(i++).toInt();
1942 x.user = q.value(i++).toInt();
1943 x.date = q.value(i++).toDateTime();
1944 x.ticket = q.value(i++).toInt();
1945 x.name = q.value(i++).toString();
1946 x.type = q.value(i++).toString();
1947 x.content = q.value(i++).toByteArray();
1948 list << x;
1949 }
1950
1951 return list;
1952}
1953
1954
1955QList<Dbt::TicketFiles> DatabasePluginPostgres::ticketFiles(bool all) {
1956 return ticketFiles(-1, all);
1957}
1958
1959
1960QVariant DatabasePluginPostgres::save(const Dbt::TicketFiles& data) {
1961 MSqlQuery q(m_db);
1962
1963 bool found = false;
1964 if (!data.created) {
1965 q.prepare(R"'(select 1 from ticket_files where id = :id;)'");
1966 q.bindValue(":id", data.id);
1967 q.exec();
1968 found = q.next();
1969 }
1970
1971 if (!data.created && found) {
1972 q.prepare(R"'(
1973 update ticket_files set
1974 ticket = :ticket,
1975 date = :date,
1976 "user" = :user,
1977 name = :name,
1978 type = :type,
1979 content = :content
1980 where id = :id
1981 )'");
1982 q.bindValue(":id", data.id);
1983 q.bindValue(":ticket", data.ticket);
1984 q.bindValue(":user", data.user);
1985 q.bindValue(":date", data.date);
1986 q.bindValue(":name", data.name);
1987 q.bindValue(":type", data.type);
1988 q.bindValue(":content", data.content);
1989 q.exec();
1990 return QVariant(data.id);
1991 }
1992
1993 if (data.created || !found) {
1994 q.prepare(R"'(
1995 insert into ticket_files (ticket, "user", date, name, type, content)
1996 values (:ticket, :user, :date, :name, :type, :content);
1997 )'");
1998 q.bindValue(":id", data.id);
1999 q.bindValue(":ticket", data.ticket);
2000 q.bindValue(":user", data.user);
2001 q.bindValue(":date", data.date);
2002 q.bindValue(":name", data.name);
2003 q.bindValue(":type", data.type);
2004 q.bindValue(":content", data.content);
2005 q.exec();
2006 return currval("ticket_files_id_seq");
2007 }
2008
2009 Q_UNREACHABLE();
2010 return QVariant();
2011}
2012
2013
2014void DatabasePluginPostgres::remove(const Dbt::TicketFiles& id) {
2015 MSqlQuery q(m_db);
2016 q.prepare(R"'(delete from ticket_files where id = :id;)'");
2017 q.bindValue(":id", id.id);
2018 q.exec();
2019}
2020
2021
2022QList<Dbt::UsersCategories> DatabasePluginPostgres::usersCategories(int id, int user, const QString& category) {
2023 QList<Dbt::UsersCategories> list;
2024 MSqlQuery q(m_db);
2025 auto retvals = [&]() {
2026 q.exec();
2027 while (q.next()) {
2028 int i=0;
2030 x.id = q.value(i++).toInt();
2031 x.user = q.value(i++).toInt();
2032 x.category = q.value(i++).toString();
2033 list << x;
2034 }
2035 return list;
2036 };
2037
2038 if (id > 0) {
2039 q.prepare(R"'(select id, "user", category from users_categories where id = :id)'");
2040 q.bindValue(":id", id);
2041 return retvals();
2042 }
2043
2044 if (id <= 0 && category.isEmpty() && user > 0) {
2045 q.prepare(R"'(select id, "user", category from users_categories where "user" = :user)'");
2046 q.bindValue(":user", user);
2047 return retvals();
2048 }
2049
2050 if (id <= 0 && category.isEmpty() && user <= 0) {
2051 q.prepare(R"'(select id, "user", category from users_categories)'");
2052 return retvals();
2053 }
2054
2055 if (id <= 0 && !category.isEmpty() && user > 0) {
2056 q.prepare(R"'(select id, "user", category from users_categories where "user" = :user and category = :category)'");
2057 q.bindValue(":user", user);
2058 q.bindValue(":category", category);
2059 return retvals();
2060 }
2061
2062 if (id <= 0 && !category.isEmpty() && user <= 0) {
2063 q.prepare(R"'(select id, "user", category from users_categories where category = :category)'");
2064 q.bindValue(":category", category);
2065 return retvals();
2066 }
2067
2068 return list;
2069}
2070
2071
2072
2073QList<Dbt::CategoriesOverview> DatabasePluginPostgres::categoriesOverview(const QStringList& statuses) {
2074 QString statusesX;
2075 QStringList statusesL;
2076 for (int i=0; i<statuses.size(); i++) {
2077 statusesL << "'" + statuses[i] + "'";
2078 }
2079 statusesL.sort();
2080 statusesX = statusesL.join(",");
2081
2082 QList<Dbt::CategoriesOverview> list;
2083 MSqlQuery q(m_db);
2084 q.prepare(QString(R"'(
2085 with recursive tree as (
2086 select category, parent_category, description, 0::int as depth, format('%04s', category) as ordering
2087 from categories where parent_category is null
2088 union
2089 select c.category, c.parent_category, c.description, t.depth + 1 as depth, t.ordering || format('%04s', c.category) as ordering
2090 from tree t, categories c
2091 where
2092 t.category = c.parent_category
2093 ),
2094 x_users as (
2095 select *
2096 from users
2097 where "user" = :userid
2098 ),
2099 x_statuses as (
2100 select *
2101 from statuses
2102 where status in (%1)
2103 and not ignored
2104 ),
2105 x_tickets as (
2106 select t.*, ls.*, tts.*
2107 from tickets t
2108 left join lateral (select ts.status
2109 from ticket_status ts,
2110 statuses s
2111 where t.ticket = ts.ticket
2112 and s.status = ts.status
2113 and not s.ignored
2114 order by date desc
2115 limit 1) ls on (true)
2116 left join lateral (select to_hours(sum(tt.date_to - coalesce(tt.date_from, now()))) as time
2117 from ticket_timesheets tt,
2118 x_users u
2119 where tt.ticket = t.ticket
2120 -- and (tt."user" = u."user" or u.admin = true)
2121 group by tt.ticket) tts on (true)
2122 where ls.status in (select status from x_statuses)
2123 )
2124
2125
2126 select c.depth, c.category, c.description, x.price, x.time, x.tickets_count, c.ordering
2127 into temporary table xxx
2128 from tree c
2129 join x_users u on true
2130 left join users_categories uc on (uc.category = c.category and uc."user" = u."user")
2131 left join (select category, count(1) as tickets_count, sum(time) as time, round(sum(price*time)) as price
2132 from x_tickets
2133 group by category
2134 ) x on (c.category = x.category)
2135 where (uc.category is not null or u.admin = true)
2136 order by c.ordering
2137 ;
2138 )'").arg(statusesX));
2139 q.bindValue(":userid", userId());
2140 q.exec();
2141 q.prepare(R"'(
2142 select 'DETAIL' as type, x.depth, x.category, x.description, x.price, x.time, x.tickets_count, x.ordering
2143 from xxx x
2144 union all
2145 select 'SUM', null, null, 'Total', sum(price), sum(time), sum(tickets_count), null
2146 from xxx
2147 ;
2148 )'");
2149 q.exec();
2150 while (q.next()) {
2151 int i=0;
2153 x.type = q.value(i++).toString();
2154 x.depth = q.value(i++).toInt();
2155 x.category = q.value(i++).toString();
2156 x.description = q.value(i++).toString();
2157 x.price = q.value(i++).toDouble();
2158 x.time = q.value(i++).toDouble();
2159 x.tickets_count = q.value(i++).toInt();
2160 x.ordering = q.value(i++).toString();
2161 list << x;
2162 }
2163 return list;
2164}
2165
2166
2167QList<Dbt::Overview> DatabasePluginPostgres::overview(const QString& category, const QStringList& statuses) {
2168 QString statusesX;
2169 QStringList statusesL;
2170 for (int i=0; i<statuses.size(); i++) {
2171 statusesL << "'" + statuses[i] + "'";
2172 }
2173 statusesL.sort();
2174 statusesX = "array[" +statusesL.join(",") + "]";
2175
2176 QList<Dbt::Overview> list;
2177 MSqlQuery q(m_db);
2178 q.prepare(R"'(
2179 select key, category, statuses
2180 from overview_params
2181 where category = :category
2182 and statuses = )'" + statusesX );
2183 q.bindValue(":category", category);
2184 q.exec();
2185 if (q.next()) {
2186 list = overview(q.value(0).toString());
2187 return list;
2188 }
2189
2190 quint64 rnd = QRandomGenerator::global()->generate64();
2191 QString rnds = QString::number(rnd, 36).toUpper();
2192 q.prepare("insert into overview_params (key, category, statuses) values (:key, :category, " + statusesX + ");");
2193 q.bindValue(":key", rnds);
2194 q.bindValue(":category", category);
2195 q.exec();
2196 list = overview(rnds);
2197 return list;
2198
2199}
2200
2201
2202QList<Dbt::Overview> DatabasePluginPostgres::overview(const QString& overviewId) {
2203 QList<Dbt::Overview> list;
2204 MSqlQuery q(m_db);
2205 Dbt::Overview overview;
2206 overview.id = overviewId.toUpper();
2207
2208 q.prepare(R"'(
2209 select c.category, c.parent_category, c.description, c.price
2210 from categories c, overview_params p
2211 where c.category = p.category
2212 and p.key = :key
2213 )'");
2214 q.bindValue(":key", overviewId.toUpper());
2215 q.exec();
2216 if (!q.next()) { return list; }
2217 overview.category.category = q.value(0).toString();
2218 overview.category.parent_category = null(q.value(1).toString());
2219 overview.category.description = q.value(2).toString();
2220 overview.category.price = q.value(3).toDouble();
2221
2222 q.exec(R"'(create temporary table overview_categories_tmp(category int);)'");
2223 q.exec(R"'(create temporary table overview_statuses_tmp(status text);)'");
2224
2225 q.prepare(R"'(
2226 with recursive tree(category, parent_category, depth) as (
2227
2228 -- nerekurzivni
2229 select category, parent_category, 0 as depth
2230 from categories
2231 where category in (select category from overview_params where key = :key)
2232
2233 -- rekurzivni
2234 union all
2235 select c.category, c.parent_category, t.depth+1 as depth
2236 from tree t
2237 left join categories c on (c.parent_category = t.category)
2238 where depth < 10
2239 and c.category is not null
2240
2241 )
2242 insert into overview_categories_tmp (category)
2243 select category
2244 from tree;
2245 )'");
2246
2247 q.bindValue(":key", overviewId.toUpper());
2248 q.exec();
2249
2250 q.prepare(R"'(insert into overview_statuses_tmp select unnest(statuses) from overview_params where key = :key;)'");
2251 q.bindValue(":key", overviewId.toUpper());
2252 q.exec();
2253
2254 q.exec(R"'(
2255 with
2256 ticket_last_status as not materialized (
2257 select t.ticket, tl.status, st.description
2258 from tickets t
2259 left join lateral (select tn.ticket, tn.status
2260 from ticket_status tn, statuses s
2261 where tn.ticket = t.ticket
2262 and tn.status = s.status
2263 and not s.ignored
2264 order by ticket, date desc
2265 limit 1
2266 ) tl using (ticket)
2267 left join statuses st on (st.status = tl.status)
2268 ),
2269 ticket_timesheets_sum as not materialized (
2270 select ticket, "user", sum(date_to - date_from) as duration
2271 from ticket_timesheets
2272 group by ticket, "user"
2273 )
2274
2275 -- detaily
2276 select t.ticket, t.description, t."user", u.name, t.price as hour_price, to_hours(ts.duration), round(to_hours(ts.duration) * t.price), ls.description
2277 from tickets t
2278 left join ticket_last_status ls using (ticket)
2279 left join ticket_timesheets_sum ts using (ticket, "user")
2280 left join users u using ("user")
2281 where ls.status in (select status from overview_statuses_tmp)
2282 and category in (select category from overview_categories_tmp)
2283
2284 union all
2285 -- Součet za jednotlivé tickety
2286 -- 0 1 2 3 4 5 6 7
2287 select g.ticket, g.description, -1, null, null, g.duration, g.price, st.description
2288 from (
2289 select t.ticket, t.description, -1, null, null, to_hours(sum(ts.duration)) as duration, sum(to_hours(ts.duration) * t.price) as price
2290 from tickets t
2291 left join ticket_timesheets_sum ts using (ticket, "user")
2292 left join ticket_last_status ls using (ticket)
2293 where ls.status in (select status from overview_statuses_tmp)
2294 and category in (select category from overview_categories_tmp)
2295 group by t.ticket
2296 ) g
2297 left join ticket_last_status st using(ticket)
2298
2299 union all
2300 -- Součet za jednotlivé statusy
2301 select null, null, -2, null, null, to_hours(sum(ts.duration)) as duration, round(sum(to_hours(ts.duration) * t.price)) as price, ls.description
2302 from ticket_last_status ls
2303 left join tickets t using (ticket)
2304 left join ticket_timesheets_sum ts using (ticket)
2305 where ls.status in (select status from overview_statuses_tmp)
2306 and category in (select category from overview_categories_tmp)
2307 group by ls.description
2308
2309 union all
2310 -- Součet za jednotlivé lidi
2311 -- 0 1 2 3 4 5 6 7
2312 select null, null, -3, u.name, null, to_hours(sum(ts.duration)) as duration, round(sum(to_hours(ts.duration) * t.price)) as price, null
2313 from ticket_last_status ls
2314 left join tickets t using (ticket)
2315 left join users u using ("user")
2316 left join ticket_timesheets_sum ts using (ticket)
2317 where ls.status in (select status from overview_statuses_tmp)
2318 and category in (select category from overview_categories_tmp)
2319 group by t."user", u.name
2320
2321 )'");
2322 while (q.next()) {
2323 if (q.value(2).toInt() == -1) {
2325 s.ticket = q.value(0).toInt();
2326 s.description = q.value(1).toString();
2327 s.duration = q.value(5).toDouble();
2328 s.price = q.value(6).toDouble();
2329 s.status = q.value(7).toString();
2330 overview.ticketsSum << s;
2331 continue;
2332 }
2333 if (q.value(2).toInt() == -2) {
2335 s.duration = q.value(5).toDouble();
2336 s.price = q.value(6).toDouble();
2337 s.status = q.value(7).toString();
2338 overview.statusSum << s;
2339 continue;
2340 }
2341 if (q.value(2).toInt() == -3) {
2343 s.user_name = q.value(3).toString();
2344 s.duration = q.value(5).toDouble();
2345 s.price = q.value(6).toDouble();
2346 overview.userSum << s;
2347 continue;
2348 }
2349
2350 int i = 0;
2352 t.ticket = q.value(i++).toInt();
2353 t.description = q.value(i++).toString();
2354 t.user = q.value(i++).toInt();
2355 t.user_name = q.value(i++).toString();
2356 t.hour_price = q.value(i++).toDouble();
2357 t.duration = q.value(i++).toDouble();
2358 t.price = q.value(i++).toDouble();
2359 t.status = q.value(i++).toString();
2360 overview.tickets << t;
2361 }
2362
2363 q.exec(R"'(
2364 with
2365 ticket_last_status as not materialized (
2366 select t.ticket, tl.status
2367 from tickets t
2368 left join lateral (select tn.ticket, tn.status
2369 from ticket_status tn, statuses s
2370 where tn.ticket = t.ticket
2371 and tn.status = s.status
2372 and not s.ignored
2373 order by ticket, date desc
2374 limit 1
2375 ) tl using (ticket)
2376 ),
2377 ticket_timesheets_sum as not materialized (
2378 select ticket, "user", date_to::date as date, sum(date_to - date_from) as duration
2379 from ticket_timesheets
2380 group by ticket, "user", date_to::date
2381 )
2382
2383 select t.ticket, t.description, t."user", u.name, t.price as hour_price, ts.date, to_hours(ts.duration), round(to_hours(ts.duration) * t.price)
2384 from tickets t
2385 left join users u using ("user")
2386 left join ticket_last_status ls using (ticket)
2387 left join ticket_timesheets_sum ts using (ticket, "user")
2388 left join categories c using (category)
2389 where ls.status in (select status from overview_statuses_tmp)
2390 and category in (select category from overview_categories_tmp)
2391
2392 union all
2393 -- 0 1 2 3 4 5 6 7
2394 select null, null, -1, null, null, null, to_hours(sum(duration)), sum(round(to_hours(ts.duration) * t.price))
2395 from tickets t
2396 left join ticket_last_status ls using (ticket)
2397 left join ticket_timesheets_sum ts using (ticket, "user")
2398 where ls.status in (select status from overview_statuses_tmp)
2399 and category in (select category from overview_categories_tmp)
2400 order by date
2401 )'");
2402 while (q.next()) {
2403 if (q.value(2).toInt() == -1) {
2404 overview.sum.duration = q.value(6).toDouble();
2405 overview.sum.price = q.value(7).toDouble();
2406 continue;
2407 }
2408 int i = 0;
2410 t.ticket = q.value(i++).toInt();
2411 t.description = q.value(i++).toString();
2412 t.user = q.value(i++).toInt();
2413 t.user_name = q.value(i++).toString();
2414 t.hour_price = q.value(i++).toDouble();
2415 t.date = q.value(i++).toDateTime();
2416 t.duration = q.value(i++).toDouble();
2417 t.price = q.value(i++).toDouble();
2418 overview.days << t;
2419 }
2420
2421 list << overview;
2422 return list;
2423}
2424
2425
2426QList<Dbt::OverviewList> DatabasePluginPostgres::overviewList() {
2427 MSqlQuery q(m_db);
2428 QList<Dbt::OverviewList> list;
2429
2430 QList<Dbt::Statuses> statusesList = statuses(QString());
2431 QHash<QString, Dbt::Statuses> statusesHash;
2432 for (int i=0; i<statusesList.size(); i++) {
2433 const Dbt::Statuses& item = statusesList[i];
2434 statusesHash[item.status] = item;
2435 }
2436
2437 q.exec(R"'(
2438 select o.key, o.statuses, c.category, c.parent_category, c.description, c.price
2439 from overview_params o
2440 left join categories c using (category)
2441 )'");
2442 while (q.next()) {
2443 int i=0;
2445 x.key = q.value(i++).toString();
2446
2447 QStringList statuses = q.value(i++).toString().replace("{","").replace("}","").split(",");
2448 for (int i=0; i<statuses.size(); i++) {
2449 x.statuses << statusesHash[statuses[i]];
2450 }
2451
2453 c.category = q.value(i++).toString();
2454 c.parent_category = q.value(i++).toString();
2455 c.description = q.value(i++).toString();
2456 c.price = q.value(i++).toDouble();
2457 x.category = c;
2458
2459 list << x;
2460 }
2461
2462 return list;
2463}
2464
2465void DatabasePluginPostgres::remove(const Dbt::OverviewList& x) {
2466 MSqlQuery q(m_db);
2467 q.prepare(R"'(delete from overview_params where key = :key;)'");
2468 q.bindValue(":key", x.key);
2469 q.exec();
2470}
2471
2472
2473QList<Dbt::Departments> DatabasePluginPostgres::departments(int department) {
2474 MSqlQuery q(m_db);
2475 QList<Dbt::Departments> list;
2476 q.prepare(R"'(select department, abbr, description from departments where department = :key1 or 0 = :key2;)'");
2477 q.bindValue(":key1", department);
2478 q.bindValue(":key2", department);
2479 q.exec();
2480 while (q.next()) {
2482 int i=0;
2483 x.department = q.value(i++).toInt();
2484 x.abbr = q.value(i++).toString();
2485 x.description = q.value(i++).toString();
2486 list << x;
2487 }
2488 return list;
2489}
2490
2491
2492QList<Dbt::Doors> DatabasePluginPostgres::doors(int door) {
2493 MSqlQuery q(m_db);
2494 QList<Dbt::Doors> list;
2495 q.prepare(R"'(select door, description from departments where department = :key1 or 0 = :key2;)'");
2496 q.bindValue(":key1", door);
2497 q.bindValue(":key2", door);
2498 q.exec();
2499 while (q.next()) {
2500 Dbt::Doors x;
2501 int i=0;
2502 x.door = q.value(i++).toInt();
2503 x.description = q.value(i++).toString();
2504 list << x;
2505 }
2506 return list;
2507}
2508
2509QList<Dbt::Employees> DatabasePluginPostgres::employess(int employee) {
2510 MSqlQuery q(m_db);
2511 QList<Dbt::Employees> list;
2512 q.prepare(R"'(select employee, firstname, surname, active where employee = :key1 or 0 = :key2;)'");
2513 q.bindValue(":key1", employee);
2514 q.bindValue(":key2", employee);
2515 q.exec();
2516 while (q.next()) {
2518 int i=0;
2519 x.employee = q.value(i++).toInt();
2520 x.firstname = q.value(i++).toString();
2521 x.surname = q.value(i++).toString();
2522 x.active = q.value(i++).toBool();
2523 list << x;
2524 }
2525 return list;
2526}
2527
2528QList<Dbt::EventTypes> DatabasePluginPostgres::eventTypes(const QString& eventType) {
2529 MSqlQuery q(m_db);
2530 QList<Dbt::EventTypes> list;
2531 q.prepare(R"'(select event_type, description,
2532 end_state, passage, arrival, vacation, sick_leave, compensatory_leave, business_trip, break_time, unpaid_leave, sick_care
2533 where event_type = :key1 or "" = :key2;
2534 )'");
2535 q.bindValue(":key1", eventType);
2536 q.bindValue(":key2", eventType);
2537 q.exec();
2538 while (q.next()) {
2540 int i=0;
2541 x.event_type = q.value(i++).toString();
2542 x.description = q.value(i++).toString();
2543 x.end_state = q.value(i++).toBool();
2544 x.passage = q.value(i++).toBool();
2545 x.arrival = q.value(i++).toBool();
2546 x.vacation = q.value(i++).toBool();
2547 x.sick_leave = q.value(i++).toBool();
2548 x.compensatory_leave = q.value(i++).toBool();
2549 x.business_trip = q.value(i++).toBool();
2550 x.break_time = q.value(i++).toBool();
2551 x.unpaid_leave = q.value(i++).toBool();
2552 x.sick_care = q.value(i++).toBool();
2553 list << x;
2554 }
2555 return list;
2556}
2557
2558QList<Dbt::Events> DatabasePluginPostgres::events(int event) {
2559 MSqlQuery q(m_db);
2560 QList<Dbt::Events> list;
2561 q.prepare(R"'(select event, date, event_type, employee, valid, user_edited from events where event = :key1 or 0 = :key2;)'");
2562 q.bindValue(":key1", event);
2563 q.bindValue(":key2", event);
2564 q.exec();
2565 while (q.next()) {
2566 Dbt::Events x;
2567 int i=0;
2568 x.event = q.value(i++).toInt();
2569 x.date = q.value(i++).toDateTime();
2570 x.event_type = q.value(i++).toString();
2571 x.employee = q.value(i++).toInt();
2572 x.valid = q.value(i++).toBool();
2573 x.user_edited = q.value(i++).toInt();
2574 list << x;
2575 }
2576 return list;
2577}
2578
2579QList<Dbt::DepartmentHasManager> DatabasePluginPostgres::departmentHasManager(const Dbt::DepartmentHasManager& p) {
2580 MSqlQuery q(m_db);
2581 QList<Dbt::DepartmentHasManager> list;
2582 q.prepare(R"'(select department, "user" from department_has_member
2583 where (department = ::key11 or 0 = :key12)
2584 and ("user" = :key21 or 0 == :key22);
2585 )'");
2586 q.bindValue(":key11", p.department);
2587 q.bindValue(":key12", p.department);
2588 q.bindValue(":key21", p.user);
2589 q.bindValue(":key22", p.user);
2590 q.exec();
2591 while (q.next()) {
2593 int i=0;
2594 x.department = q.value(i++).toInt();
2595 x.user = q.value(i++).toInt();
2596 list << x;
2597 }
2598 return list;
2599}
2600
2601QList<Dbt::DepartmentHasMember> DatabasePluginPostgres::departmentHasMember(const Dbt::DepartmentHasMember& p) {
2602 MSqlQuery q(m_db);
2603 QList<Dbt::DepartmentHasMember> list;
2604 q.prepare(R"'(select department, employee from department_has_member
2605 where (department = ::key11 or 0 = :key12)
2606 and (employee = :key21 or 0 == :key22);
2607 )'");
2608 q.bindValue(":key11", p.department);
2609 q.bindValue(":key12", p.department);
2610 q.bindValue(":key21", p.employee);
2611 q.bindValue(":key22", p.employee);
2612 q.exec();
2613 while (q.next()) {
2615 int i=0;
2616 x.department = q.value(i++).toInt();
2617 x.employee = q.value(i++).toInt();
2618 list << x;
2619 }
2620 return list;
2621}
2622
2623QList<Dbt::EmployeeCanOpenDoor> DatabasePluginPostgres::employeeCanOpenDoor(const Dbt::EmployeeCanOpenDoor& p) {
2624 MSqlQuery q(m_db);
2625 QList<Dbt::EmployeeCanOpenDoor> list;
2626 q.prepare(R"'(select door, employee from employee_can_open_door
2627 where (door = ::key11 or 0 = :key12)
2628 and (employee = :key21 or 0 == :key22);
2629 )'");
2630 q.bindValue(":key11", p.door);
2631 q.bindValue(":key12", p.door);
2632 q.bindValue(":key21", p.employee);
2633 q.bindValue(":key22", p.employee);
2634 q.exec();
2635 while (q.next()) {
2637 int i=0;
2638 x.door = q.value(i++).toInt();
2639 x.employee = q.value(i++).toInt();
2640 list << x;
2641 }
2642 return list;
2643}
2644
2645QList<Dbt::EmployeeHasRfid> DatabasePluginPostgres::employeeHasRfid(const Dbt::EmployeeHasRfid& p) {
2646 MSqlQuery q(m_db);
2647 QList<Dbt::EmployeeHasRfid> list;
2648 q.prepare(R"'(select employee, rfid from employee_has_rfid
2649 where (employee = ::key11 or 0 = :key12)
2650 and (rfid = :key21 or 0 == :key22);
2651 )'");
2652 q.bindValue(":key11", p.employee);
2653 q.bindValue(":key12", p.employee);
2654 q.bindValue(":key21", p.rfid);
2655 q.bindValue(":key22", p.rfid);
2656 q.exec();
2657 while (q.next()) {
2659 int i=0;
2660 x.employee = q.value(i++).toInt();
2661 x.rfid = q.value(i++).toInt();
2662 list << x;
2663 }
2664 return list;
2665}
2666
2667void DatabasePluginPostgres::remove(const Dbt::Departments& data) {
2668 MSqlQuery q(m_db);
2669 q.prepare(R"'(delete from departments where department = :key;)'");
2670 q.bindValue(":key", data.department);
2671}
2672
2673void DatabasePluginPostgres::remove(const Dbt::Doors& data) {
2674 MSqlQuery q(m_db);
2675 q.prepare(R"'(delete from doors where door = :key;)'");
2676 q.bindValue(":key", data.door);
2677}
2678
2679void DatabasePluginPostgres::remove(const Dbt::Employees& data) {
2680 MSqlQuery q(m_db);
2681 q.prepare(R"'(delete from employees where employee = :key;)'");
2682 q.bindValue(":key", data.employee);
2683}
2684
2685void DatabasePluginPostgres::remove(const Dbt::EventTypes& data) {
2686 MSqlQuery q(m_db);
2687 q.prepare(R"'(delete from event_types where event_type = :key;)'");
2688 q.bindValue(":key", data.event_type);
2689}
2690
2691void DatabasePluginPostgres::remove(const Dbt::Events& data) {
2692 MSqlQuery q(m_db);
2693 q.prepare(R"'(delete from event where event = :key;)'");
2694 q.bindValue(":key", data.event);
2695}
2696
2697void DatabasePluginPostgres::remove(const Dbt::DepartmentHasManager& data) {
2698 MSqlQuery q(m_db);
2699 q.prepare(R"'(delete from departments_has_manager where department = :key1 and "user" = :key2;)'");
2700 q.bindValue(":key1", data.department);
2701 q.bindValue(":key2", data.user);
2702}
2703
2704void DatabasePluginPostgres::remove(const Dbt::DepartmentHasMember& data) {
2705 MSqlQuery q(m_db);
2706 q.prepare(R"'(delete from departments_has_member where department = :key and employee = :key2;)'");
2707 q.bindValue(":key1", data.department);
2708 q.bindValue(":key2", data.employee);
2709}
2710
2711void DatabasePluginPostgres::remove(const Dbt::EmployeeCanOpenDoor& data) {
2712 MSqlQuery q(m_db);
2713 q.prepare(R"'(delete from employee_can_open_door where employee = :key1 and door = :key2;)'");
2714 q.bindValue(":key1", data.employee);
2715 q.bindValue(":key2", data.door);
2716}
2717
2718void DatabasePluginPostgres::remove(const Dbt::EmployeeHasRfid& data) {
2719 MSqlQuery q(m_db);
2720 q.prepare(R"'(delete from employee_has_rfid where employee = :key1 and rfid = :key2;)'");
2721 q.bindValue(":key1", data.employee);
2722 q.bindValue(":key2", data.rfid);
2723}
2724
2725
Vlastní rozšíření QSqlQuery o primitivní transakce a logování
Definition msqlquery.h:18
static QVariant data(const QByteArray &json)
Converts json to data.
Definition json.cpp:69
static QByteArray json(const QVariant &data)
Converts data to json.
Definition json.cpp:21
QVariantList categories
categories which are appended with new statuse
Definition dbt.h:362
QString description
new status description
Definition dbt.h:365
QString status
new status
Definition dbt.h:364
QVariantList recent_status
recent statuses
Definition dbt.h:363