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#include <QDateTime>
17
18using namespace Db::Plugins;
19
20namespace Db::Plugins {
21 QString null(const QString& x) {
22 return (x == "0") ? "" : x;
23 }
24}
25
26bool DatabasePluginPostgres::m_upgraded = false;
27
28
29DatabasePluginPostgres::~DatabasePluginPostgres() {
30 close();
31}
32
33
34DatabasePluginPostgres::DatabasePluginPostgres(QObject *parent) : Db::Plugins::DatabasePlugin(parent) {
35 Q_ASSERT(parent != NULL);
36 setObjectName("DatabasePluginPostgres");
37 m_temporaryTableTicketsCreated = false;
38}
39
40
41bool DatabasePluginPostgres::open() {
42 m_db = QSqlDatabase::addDatabase("QPSQL", QUuid::createUuid().toString().toUtf8());
43
44 m_db.setDatabaseName ( m_databasename );
45 m_db.setHostName ( m_hostname );
46 m_db.setPort ( m_port );
47 m_db.setUserName ( m_username );
48 m_db.setPassword ( m_password );
49
50 if (!m_db.open()) {
51 PDEBUG << "Cannot connect to database Postgres";
52 PDEBUG << m_db.lastError().text();
53 return false;
54 }
55
56 upgrade();
57
58 return true;
59}
60
61
62void DatabasePluginPostgres::upgrade() {
63 if (m_upgraded) { return; }
64 PDEBUG;
65 m_upgraded = true;
66 MSqlQuery q(m_db);
67 for (int version=0; version < 1000; version++) {
68 MSqlQuery qx(m_db);
69 qx.prepare("select * from version where version = :version");
70 qx.bindValue(":version", version);
71 qx.exec();
72 if (qx.next()) {
73 continue;
74 }
75
76 QString patchname = QString(":/postgres/patch.%1.sql").arg(version, 3, 10, QChar('0'));
77 QFile file(patchname);
78 if (!file.open(QIODevice::ReadOnly)) {
79 continue;
80 }
81
82 PDEBUG << "aplying db patch " << patchname;
83
84 while (!file.atEnd()) {
85 QByteArray command;
86 QByteArray line;
87 do {
88 line = file.readLine();
89 command += line;
90 } while (!line.contains(";") && !file.atEnd());
91 command = command.trimmed();
92 if (command.isEmpty()) { continue; }
93 MSqlQuery q(m_db);
94 if (!q.exec(QString::fromUtf8(command))) {
95 QSqlError e = q.lastError();
96 if (e.type() != QSqlError::NoError) {
97 PDEBUG << q.lastQuery();
98 PDEBUG << e.databaseText();
99 PDEBUG << e.driverText();
100 return;
101 }
102 }
103 }
104
105 }
106
107 return;
108}
109
110
111bool DatabasePluginPostgres::close() {
112 m_db.close();
113 return true;
114}
115
116
117void DatabasePluginPostgres::begin() {
118 MSqlQuery q(m_db);
119 q.exec("begin;");
120}
121
122
123void DatabasePluginPostgres::commit() {
124 MSqlQuery q(m_db);
125 q.exec("commit;");
126}
127
128
129void DatabasePluginPostgres::changePassword(const QString& login, const QString& oldpassword, const QString& newpassword) {
130 PDEBUG;
131 QString md5new = QString::fromUtf8(QCryptographicHash::hash(newpassword.toUtf8(), QCryptographicHash::Md5).toHex());
132 QString md5old = QString::fromUtf8(QCryptographicHash::hash(oldpassword.toUtf8(), QCryptographicHash::Md5).toHex());
133 QList<Dbt::Users> list;
134 MSqlQuery q(m_db);
135 q.prepare(R"'(select true from users where "user" = :userid and admin)'");
136 q.bindValue(":userid", userId());
137 q.exec();
138 bool admin = q.next();
139
140 if (admin) {
141 // admin can change password for other users
142 PDEBUG << "admin";
143 q.prepare("update users set password = :newpassword where login = :login;");
144 } else {
145 // non-privileged user can change it's own password only, must know the old password
146 PDEBUG << "not admin";
147 q.prepare("update users set password = :newpassword where login = :login and password = :oldpassword and enabled;");
148 }
149 q.bindValue(":userid", userId());
150 q.bindValue(":login", login);
151 q.bindValue(":newpassword", md5new);
152 q.bindValue(":oldpassword", md5old);
153 q.exec();
154 return;
155}
156
157
158QList<Dbt::Users> DatabasePluginPostgres::authenticate(const QString& login, const QString& password) {
159 QString md5 = QString::fromUtf8(QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Md5).toHex());
160 QList<Dbt::Users> list;
161 MSqlQuery q(m_db);
162 q.prepare("select \"user\", login, name, lang, enabled, admin from users where login = :login and password = :password and enabled;");
163 q.bindValue(":login", login);
164 q.bindValue(":password", md5);
165 q.exec();
166 while (q.next()) {
167 int i=0;
168 Dbt::Users x;
169 x.user = q.value(i++).toInt();
170 x.login = q.value(i++).toString();
171 x.name = q.value(i++).toString();
172 x.lang = q.value(i++).toString();
173 x.enabled = q.value(i++).toBool();
174 x.admin = q.value(i++).toBool();
175 list << x;
176 }
177
178 return list;
179}
180
181
182QList<Dbt::Users> DatabasePluginPostgres::users(int id) {
183 QList<Dbt::Users> list;
184 MSqlQuery q(m_db);
185 q.prepare(R"'(
186 select "user", login, name, lang, enabled, admin
187 from users
188 where (:id1 <= 0 or :id2 = "user");
189 )'");
190 q.bindValue(":id1", id);
191 q.bindValue(":id2", id);
192 q.exec();
193 while (q.next()) {
194 int i=0;
195 Dbt::Users x;
196 x.user = q.value(i++).toInt();
197 x.login = q.value(i++).toString();
198 x.name = q.value(i++).toString();
199 x.lang = q.value(i++).toString();
200 x.enabled = q.value(i++).toBool();
201 x.admin = q.value(i++).toBool();
202 list << x;
203 }
204
205 return list;
206}
207
208
209void DatabasePluginPostgres::remove(const Dbt::Categories& id) {
210 MSqlQuery q(m_db);
211 q.prepare(R"'(delete from categories where category = :id;)'");
212 q.bindValue(":id", id.category);
213 q.exec();
214}
215
216
217QVariant DatabasePluginPostgres::currval(const QString& sequence) {
218 MSqlQuery q(m_db);
219 QVariant cv;
220 q.prepare(R"'(select currval(:sequence);)'");
221 q.bindValue(":sequence", sequence);
222 q.exec();
223 if (q.next()) {
224 cv = q.value(0);
225 }
226 return cv;
227}
228
229
230QVariant DatabasePluginPostgres::save(const Dbt::Categories& data) {
231 MSqlQuery q(m_db);
232
233 QVariant parent_category = (data.parent_category.toInt() > 0)
234 ? data.parent_category.toInt()
235 : QVariant();
236
237 QVariant category = (data.category.toInt() > 0)
238 ? data.category.toInt()
239 : QVariant(QVariant::Int);
240
241 if (parent_category.toInt() == category.toInt()) {
242 parent_category = QVariant();
243 }
244
245 // TODO: Kontrola, aby nešlo založit novou kategorii v nadřízené kategorii bez přístupu
246
247 q.begin();
248
249 q.prepare(R"'(select 1 from categories where category = :category;)'");
250 q.bindValue(":category", data.category.toInt());
251 q.exec();
252 bool exists = q.next();
253 if (exists && ! parent_category.isNull()) {
254 q.prepare(R"'(
255 update categories set
256 parent_category = ?,
257 description = ?,
258 price = ?
259 where category = ?
260 )'");
261 q.bindValue(0, parent_category);
262 q.bindValue(1, data.description);
263 q.bindValue(2, data.price);
264 q.bindValue(3, category.toInt());
265 q.exec();
266 }
267
268 if (exists && parent_category.isNull()) {
269 q.prepare(R"'(
270 update categories set
271 description = ?,
272 price = ?
273 where category = ?
274 )'");
275 q.bindValue(0, data.description);
276 q.bindValue(1, data.price);
277 q.bindValue(2, category.toInt());
278 q.exec();
279 }
280
281 if (!exists) {
282 q.prepare(R"string(
283 insert into categories (parent_category, description, price) values (?, ?, ?);
284 )string");
285 q.bindValue(0, parent_category);
286 q.bindValue(1, data.description);
287 q.bindValue(2, data.price);
288 q.exec();
289 category = currval("categories_category_seq");
290 // insert permission for creator
291 q.prepare(R"string(
292 insert into users_categories ("user", category) values (?, ?) on conflict do nothing;
293 )string");
294 q.bindValue(0, userId());
295 q.bindValue(1, category);
296 q.exec();
297 }
298
299 // empty list of users is ignored
300 // if list is set, then replace old list
301 if (!data.users.isEmpty()) {
302 q.prepare(R"'(delete from users_categories where category = ?;)'");
303 q.bindValue(0, category);
304 q.exec();
305 }
306
307 for (int i=0; i<data.users.size(); i++) {
308 q.prepare(R"string(
309 insert into users_categories ("user", category) values (?, ?) on conflict do nothing;
310 )string");
311 q.bindValue(0, data.users[i].toInt());
312 q.bindValue(1, category);
313 q.exec();
314 }
315
316 q.commit();
317
318 return category;
319}
320
321
322QList<Dbt::ClientSettings> DatabasePluginPostgres::clientSettings() {
323 QList<Dbt::ClientSettings> list;
324 MSqlQuery q(m_db);
325
326 q.prepare(R"'(
327 select multiple_timesheets, show_price, can_change_category, edit_categories,
328 show_multiple_timesheets, show_show_price, show_can_change_category, show_edit_categories
329 from client_settings;
330 )'");
331 q.exec();
332 while (q.next()) {
333 Dbt::ClientSettings x;
334 int i = 0;
335 x.multiple_timesheets = q.value(i++).toBool();
336 x.show_price = q.value(i++).toBool();
337 x.can_change_category = q.value(i++).toBool();
338 x.edit_categories = q.value(i++).toBool();
339 x.show_multiple_timesheets = q.value(i++).toBool();
340 x.show_show_price = q.value(i++).toBool();
341 x.show_can_change_category = q.value(i++).toBool();
342 x.show_edit_categories = q.value(i++).toBool();
343 list << x;
344 }
345 return list;
346}
347
348
349QList<Dbt::ServerInfo> DatabasePluginPostgres::serverInfo() {
350 QList<Dbt::ServerInfo> list;
351 MSqlQuery q(m_db);
352 q.prepare(R"'(
353 select name, description from server_info;
354 )'");
355 q.exec();
356 while (q.next()) {
357 Dbt::ServerInfo x;
358 int i = 0;
359 x.name = q.value(i++).toString();
360 x.description = q.value(i++).toString();
361 list << x;
362 }
363 return list;
364}
365
366
367QVariant DatabasePluginPostgres::save(const Dbt::ServerInfo& data) {
368 MSqlQuery q(m_db);
369
370 q.prepare(R"'(
371 update server_info set
372 name = :name,
373 description = :description
374 ;
375 )'");
376 q.bindValue(":name", data.name);
377 q.bindValue(":description", data.description);
378 q.exec();
379
380 return QVariant();
381}
382
383QVariant DatabasePluginPostgres::save(const Dbt::EmployeeCanOpenDoor& data) {
384 MSqlQuery q(m_db);
385 q.prepare(R"'(
386 insert into attendance.employee_can_open_door (employee, door)
387 values (:employee, :door)
388 )'");
389 q.bindValue(":employee", data.employee);
390 q.bindValue(":door", data.door);
391 q.exec();
392 return QVariant();
393}
394
395QVariant DatabasePluginPostgres::save(const Dbt::DepartmentHasManager& data) {
396 MSqlQuery q(m_db);
397 q.prepare(R"'(
398 insert into attendance.department_has_manager (department, "user")
399 values (:department, :user)
400 )'");
401 q.bindValue(":department", data.department);
402 q.bindValue(":user", data.user);
403 q.exec();
404 return QVariant();
405}
406
407QVariant DatabasePluginPostgres::save(const Dbt::DepartmentHasMember& data) {
408 MSqlQuery q(m_db);
409 q.prepare(R"'(
410 insert into attendance.department_has_member (department, employee)
411 values (:department, :employee)
412 )'");
413 q.bindValue(":department", data.department);
414 q.bindValue(":employee", data.employee);
415 q.exec();
416 return QVariant();
417}
418
419QVariant DatabasePluginPostgres::save(const Dbt::EmployeeHasRfid& data) {
420 MSqlQuery q(m_db);
421 q.prepare(R"'(
422 insert into attendance.employee_has_rfid (rfid, employee)
423 values (:rfid, :employee)
424 on conflict (rfid) do update set employee = excluded.employee
425 )'");
426 q.bindValue(":rfid", data.rfid);
427 q.bindValue(":employee", data.employee);
428 q.exec();
429 return QVariant();
430}
431
432
433QVariantList pgArrayToVariantList(const QVariant& input) {
434 QStringList x = input.toString().replace("{", "").replace("}", "").split(",");
435 QVariantList list;
436 for (int i=0; i<x.size(); i++) {
437 list << x[i];
438 }
439 return list;
440}
441
442
443QList<Dbt::Categories> DatabasePluginPostgres::categories(const QString& id) {
444 QList<Dbt::Categories> list;
445 MSqlQuery q(m_db);
446
447 q.prepare(R"'(
448 select c.category, c.parent_category, c.description, c.price, ux.users, categories_tree_description(c.category) as description_tree
449 from categories c, users u
450 left join lateral (select array_agg("user") as users from users_categories where category = c.category) ux on (true)
451 where (:id1 <= 0 or :id2 = c.category)
452 and u."user" = :user and (u.admin or u."user" in (select "user" from users_categories uc where uc.category = c.category))
453 ;
454 )'");
455 q.bindValue(":user", userId());
456 q.bindValue(":id1", id.toInt());
457 q.bindValue(":id2", id.toInt());
458 q.exec();
459 while (q.next()) {
460 int i=0;
461 Dbt::Categories x;
462 x.category = q.value(i++).toString();
463 x.parent_category = null(q.value(i++).toString());
464 x.description = q.value(i++).toString();
465 x.price = q.value(i++).toDouble();
466 x.users = pgArrayToVariantList(q.value(i++));
467 x.description_tree = q.value(i++).toString();
468 list << x;
469 }
470
471 return list;
472}
473
474
475QList<Dbt::Categories> DatabasePluginPostgres::categoriesToRoot(const QString& id) {
476 QList<Dbt::Categories> list;
477 MSqlQuery q(m_db);
478 int xid = id.toInt();
479
480 q.prepare(R"'(
481 select c.category, c.parent_category, c.description, c.price, ux.users, categories_tree_description(c.category) as description_tree
482 from categories c, users u
483 left join lateral (select array_agg("user") as users from users_categories where category = c.category) ux on (true)
484 where :id = c.category
485 and u."user" = :user and (u.admin or u."user" in (select "user" from users_categories uc where uc.category = c.category))
486 ;
487 )'");
488 for (;;) {
489 q.bindValue(":user", userId());
490 q.bindValue(":id", xid);
491 q.exec();
492 bool found = q.next();
493 PDEBUG << xid << found;
494 if (!found) { return list; }
495 int i=0;
496 Dbt::Categories x;
497 x.category = q.value(i++).toString();
498 x.parent_category = null(q.value(i++).toString());
499 x.description = q.value(i++).toString();
500 x.price = q.value(i++).toDouble();
501 x.users = pgArrayToVariantList(q.value(i++));
502 x.description_tree = q.value(i++).toString();
503 list.prepend(x);
504 xid = x.parent_category.toInt();
505 }
506
507 return list;
508}
509
510
511QList<Dbt::Categories> DatabasePluginPostgres::subcategories(const QString& id) {
512 QList<Dbt::Categories> list;
513 MSqlQuery q(m_db);
514
515 q.prepare(R"'(
516 select c.category, c.parent_category, c.description, c.price, ux.users, categories_tree_description(c.category) as description_tree
517 from categories c, users u
518 left join lateral (select array_agg("user") as users from users_categories where category = c.category) ux on (true)
519 where ((:id1 <= 0 and c.parent_category is null) or :id2 = c.parent_category)
520 and u."user" = :user and (u.admin or u."user" in (select "user" from users_categories uc where uc.category = c.category))
521 ;
522 )'");
523 q.bindValue(":user", userId());
524 q.bindValue(":id1", id.toInt());
525 q.bindValue(":id2", id.toInt());
526 q.exec();
527 while (q.next()) {
528 int i=0;
529 Dbt::Categories x;
530 x.category = q.value(i++).toString();
531 x.parent_category = null(q.value(i++).toString());
532 x.description = q.value(i++).toString();
533 x.price = q.value(i++).toDouble();
534 x.users = pgArrayToVariantList(q.value(i++));
535 x.description_tree = q.value(i++).toString();
536 list << x;
537 }
538
539 return list;
540}
541
542
543QList<Dbt::Categories> DatabasePluginPostgres::siblingcategories(const QString& id) {
544 QList<Dbt::Categories> list;
545 MSqlQuery q(m_db);
546
547 q.prepare(R"'(
548 with recursive tree as (
549 select category, parent_category
550 from categories where category = :id
551 union all
552 select c.category, c.parent_category
553 from tree t, categories c
554 where
555 t.category = c.parent_category
556 )
557 select c.category, c.parent_category, c.description, c.price, ux.users, categories_tree_description(c.category) as description_tree
558 from categories c, users u
559 left join lateral (select array_agg("user") as users from users_categories where category = c.category) ux on (true)
560 where u."user" = :user and (u.admin or u."user" in (select "user" from users_categories uc where uc.category = c.category))
561 and c.category not in (select category from tree)
562 ;
563 )'");
564 q.bindValue(":user", userId());
565 q.bindValue(":id", id.toInt());
566 q.exec();
567 while (q.next()) {
568 int i=0;
569 Dbt::Categories x;
570 x.category = q.value(i++).toString();
571 x.parent_category = null(q.value(i++).toString());
572 x.description = q.value(i++).toString();
573 x.price = q.value(i++).toDouble();
574 x.users = pgArrayToVariantList(q.value(i++));
575 x.description_tree = q.value(i++).toString();
576 list << x;
577 }
578
579 return list;
580}
581
582
583void DatabasePluginPostgres::remove(const Dbt::Users& id) {
584 MSqlQuery q(m_db);
585
586 q.prepare(R"'(select 1 from users where "user" = :id and admin and enabled;)'");
587 q.bindValue(":id", userId());
588 q.exec();
589 if (!q.next()) { return; }
590
591 q.prepare(R"'(delete from users where "user" = :id;)'");
592 q.bindValue(":id", id.user);
593 q.exec();
594}
595
596
597QVariant DatabasePluginPostgres::save(const Dbt::Users& data) {
598 MSqlQuery q(m_db);
599
600 q.prepare(R"'(select 1 from users where "user" = :id and admin and enabled;)'");
601 q.bindValue(":id", userId());
602 q.exec();
603 if (!q.next()) { return QVariant(); }
604
605
606 q.prepare(R"'(select 1 from users where "user" = :id;)'");
607 q.bindValue(":id", data.user);
608 q.exec();
609 if (q.next()) {
610 q.prepare(R"'(
611 update users set
612 login = :login,
613 name = :name,
614 lang = :lang,
615 enabled = :enabled,
616 admin = :admin
617 where "user" = :id1
618 )'");
619 q.bindValue(":id1", data.user);
620 q.bindValue(":login", data.login);
621 q.bindValue(":name", data.name);
622 q.bindValue(":lang", data.lang);
623 q.bindValue(":enabled", data.enabled);
624 q.bindValue(":admin", data.admin);
625 q.exec();
626 return QVariant(data.user);
627 } else {
628
629 q.prepare(R"'(
630 insert into users (login, name, lang, enabled, admin)
631 select :login, :name, :lang, :enabled, :admin
632 where not exists (select 1 from users where "user" = :id1);
633 )'");
634 q.bindValue(":id1", data.user);
635 q.bindValue(":login", data.login);
636 q.bindValue(":name", data.name);
637 q.bindValue(":lang", data.lang);
638 q.bindValue(":enabled", data.enabled);
639 q.bindValue(":admin", data.admin);
640 q.exec();
641
642 return currval("users_user_seq");
643 }
644
645 return QVariant();
646
647}
648
649
650QList<Dbt::StatusOrder> DatabasePluginPostgres::statusOrder(const QString& id) {
651 QList<Dbt::StatusOrder> list;
652 MSqlQuery q(m_db);
653
654 q.prepare(R"'(
655 select id, category, previous_status, next_status from status_order
656 where (:id1 <= 0 or :id2 = id);
657 )'");
658 q.bindValue(":id1", id.toInt());
659 q.bindValue(":id2", id.toInt());
660 q.exec();
661 while (q.next()) {
662 int i = 0;
663 Dbt::StatusOrder x;
664 x.id = q.value(i++).toInt();
665 x.category = q.value(i++);
666 x.previous_status = q.value(i++);
667 x.next_status = q.value(i++);
668 list << x;
669 }
670
671 return list;
672
673}
674
675
676void DatabasePluginPostgres::remove(const Dbt::StatusOrder& id) {
677 MSqlQuery q(m_db);
678 q.prepare(R"'(delete from status_order where id = :id;)'");
679 q.bindValue(":id", id.id);
680 q.exec();
681}
682
683
684QVariant DatabasePluginPostgres::save(const Dbt::StatusOrder& data) {
685 MSqlQuery q(m_db);
686
687 q.prepare(R"'(select 1 from status_order where id = :id;)'");
688 q.bindValue(":id", data.id);
689 q.exec();
690 if (q.next()) {
691 q.prepare(R"'(
692 update status_order set
693 category = :category,
694 previous_status = :previous_status,
695 next_status = :next_status
696 where id = :id
697 )'");
698 q.bindValue(":category", data.category);
699 q.bindValue(":previous_status", data.previous_status);
700 q.bindValue(":next_status", data.next_status);
701 q.bindValue(":id", data.id);
702 q.exec();
703 return QVariant(data.id);
704
705 } else {
706
707 q.prepare(R"'(
708 insert into status_order (category, previous_status, next_status)
709 select :category, :previous_status, :next_status
710 )'");
711 q.bindValue(":category", data.category);
712 q.bindValue(":previous_status", data.previous_status);
713 q.bindValue(":next_status", data.next_status);
714 q.bindValue(":id", data.id);
715 q.exec();
716 return currval("status_order_id_seq");
717 }
718
719 return QVariant();
720
721}
722
723
724void DatabasePluginPostgres::createTemporaryTableTickets(int ticket, bool all) {
725 if (m_temporaryTableTicketsCreated) { return; }
726 m_temporaryTableTicketsCreated = true;
727 MSqlQuery q(m_db);
728 q.exec(R"'(
729 create temporary table temporary_tickets (
730 ticket int,
731 category int,
732 date timestamp with time zone,
733 price numeric,
734 description text,
735 "user" int
736 );
737 )'");
738
739 if (all) {
740 PDEBUG << "Vybiram VSE" << ticket;
741 q.prepare(R"'(
742 insert into temporary_tickets (ticket, category, date, price, description, "user")
743 select t.ticket, t.category, t.date, t.price, t.description, t."user"
744 from tickets t, users u
745 where (t.category in (select category from users_categories where "user" = u."user") or u.admin = true)
746 and u."user" = :user
747 and (:ticket1 <= 0 or :ticket2 = t.ticket)
748 ;
749 )'");
750 } else {
751 PDEBUG << "Vybiram pouze otevrene" << ticket;
752 q.prepare(R"'(
753 with
754 ending_status as (
755 select status from statuses where closed
756 ),
757 ticket_last_status as (
758 select t.ticket, tl.status
759 from tickets t
760 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)
761 ),
762 closed_tickets as (
763 select distinct ts.ticket from ticket_last_status ts, ending_status es where ts.status = es.status
764 ),
765 active_tickets as (
766 select t1.ticket from tickets t1 where t1.ticket not in (select ticket from closed_tickets)
767 )
768
769 insert into temporary_tickets (ticket, category, date, price, description, "user")
770 select t.ticket, t.category, t.date, t.price, t.description, t."user"
771 from tickets t, users_categories uc
772 where t.ticket in (select ticket from active_tickets)
773 and uc."user" = :user
774 and t.category = uc.category
775 and (:ticket1 <= 0 or :ticket2 = t.ticket)
776 ;
777 )'");
778 }
779 q.bindValue(":user", userId());
780 q.bindValue(":ticket1", ticket);
781 q.bindValue(":ticket2", ticket);
782 q.exec();
783
784 q.exec("select count(1) from temporary_tickets;");
785 q.next();
786 PDEBUG << "Vybranych vet" << q.value(0).toInt();
787
788}
789
790
791QList<Dbt::Tickets> DatabasePluginPostgres::tickets(bool all) {
792 return tickets(-1, all);
793}
794
795
796QList<Dbt::Tickets> DatabasePluginPostgres::tickets(int ticket, bool all) {
797 createTemporaryTableTickets(ticket, all);
798 QList<Dbt::Tickets> list;
799 MSqlQuery q(m_db);
800
801 q.prepare(R"'(
802 select ticket, category, date, price, description, "user"
803 from temporary_tickets ;
804 )'");
805 q.exec();
806 while (q.next()) {
807 Dbt::Tickets x;
808 int i=0;
809 x.ticket = q.value(i++);
810 x.category = q.value(i++);
811 x.date = q.value(i++).toDateTime();
812 x.price = q.value(i++).toDouble();
813 x.description = q.value(i++).toString();
814 x.user = q.value(i++).toInt();
815 list << x;
816 }
817 return list;
818}
819
820
821QList<Dbt::Tickets> DatabasePluginPostgres::tickets(const Dbt::Categories& category, bool all) {
822 createTemporaryTableTickets(-1, all);
823 QList<Dbt::Tickets> list;
824 MSqlQuery q(m_db);
825
826 q.prepare(R"'(
827 select ticket, category, date, price, description, "user"
828 from temporary_tickets
829 where category = :category;
830 )'");
831 q.bindValue(":category", category.category);
832 q.exec();
833 while (q.next()) {
834 Dbt::Tickets x;
835 int i=0;
836 x.ticket = q.value(i++);
837 x.category = q.value(i++);
838 x.date = q.value(i++).toDateTime();
839 x.price = q.value(i++).toDouble();
840 x.description = q.value(i++).toString();
841 x.user = q.value(i++).toInt();
842 list << x;
843 }
844 return list;
845}
846
847
848
849QList<Dbt::TicketsVw> DatabasePluginPostgres::ticketsVw(bool all) {
850 return ticketsVw(-1, all);
851}
852
853
854QList<Dbt::TicketsVw> DatabasePluginPostgres::ticketsVw(int ticket, bool all) {
855 QList<Dbt::Tickets> list1 = tickets(ticket,all);
856 QList<Dbt::TicketsVw> list;
857 MSqlQuery q(m_db);
858 for (int i=0; i<list1.size(); i++) {
859 Dbt::TicketsVw x = list1[i];
860 x.timesheets = ticketTimesheets(list1[i].ticket.toInt(), all);
861 x.statuses = ticketStatus(list1[i].ticket.toInt(), all);
862 x.values = ticketValues(list1[i].ticket.toInt(), all);
863 x.files = ticketFiles(list1[i].ticket.toInt(), all);
864 list << x;
865 }
866
867 return list;
868}
869
870
871QList<Dbt::TicketsVw> DatabasePluginPostgres::ticketsVw(const Dbt::Categories& category, bool all) {
872 QList<Dbt::Tickets> list1 = tickets(category, all);
873 QList<Dbt::TicketsVw> list;
874 MSqlQuery q(m_db);
875 for (int i=0; i<list1.size(); i++) {
876 Dbt::TicketsVw x = list1[i];
877 x.timesheets = ticketTimesheets(list1[i].ticket.toInt(), true);
878 x.statuses = ticketStatus(list1[i].ticket.toInt(), true);
879 x.values = ticketValues(list1[i].ticket.toInt(), true);
880 x.files = ticketFiles(list1[i].ticket.toInt(), true);
881 list << x;
882 }
883
884 return list;
885}
886
887
888void DatabasePluginPostgres::remove(const Dbt::Tickets& id) {
889 MSqlQuery q(m_db);
890 q.prepare(R"'(delete from tickets where ticket = :id;)'");
891 q.bindValue(":id", id.ticket);
892 q.exec();
893}
894
895
896QVariant DatabasePluginPostgres::save(const Dbt::Tickets& data) {
897 MSqlQuery q(m_db);
898
899 bool found = false;
900 if (!data.created) {
901 q.prepare(R"'(select 1 from tickets where ticket = :ticket;)'");
902 q.bindValue(":ticket", data.ticket);
903 q.exec();
904 found = q.next();
905 }
906
907 if (!data.created && found) {
908 q.prepare(R"'(
909 update tickets set
910 category = :category,
911 date = :date,
912 price = :price,
913 description = :description,
914 "user" = :user
915 where ticket = :ticket
916 )'");
917 q.bindValue(":category", data.category);
918 q.bindValue(":date", data.date);
919 q.bindValue(":price", data.price);
920 q.bindValue(":description", data.description);
921 q.bindValue(":user", data.user);
922 q.bindValue(":ticket", data.ticket);
923 q.exec();
924 return QVariant(data.ticket);
925 }
926
927 if (data.created || !found) {
928 q.prepare(R"'(
929 insert into tickets (category, date, price, description, "user")
930 values (:category, :date, :price, :description, :user);
931 )'");
932 q.bindValue(":category", data.category);
933 q.bindValue(":date", data.date);
934 q.bindValue(":price", data.price);
935 q.bindValue(":description", data.description);
936 q.bindValue(":user", data.user);
937 q.exec();
938 return currval("tickets_ticket_seq");
939 }
940
941 Q_UNREACHABLE();
942 qFatal("Should not happen");
943 return QVariant();
944
945}
946
947
948template<typename T>
949QList<T> remapTicket(const QList<T>& input, int ticket) {
950 QList<T> list;
951 QListIterator<T> iterator(input);
952 while (iterator.hasNext()) {
953 T x = iterator.next();
954 x.ticket = ticket;
955 if (ticket <= 0) { x.id = 0; }
956 list << x;
957 }
958 return list;
959}
960
961
962QVariant DatabasePluginPostgres::save(const Dbt::TicketsVw& data) {
963 MSqlQuery q(m_db);
964 q.begin();
965 int ticket = save(dynamic_cast<const Dbt::Tickets&>(data)).toInt();
966 removeTicketsDetails(ticket);
967 save(remapTicket(data.timesheets, ticket));
968 save(remapTicket(data.statuses, ticket));
969 save(remapTicket(data.values, ticket));
970 save(remapTicket(data.files, ticket));
971 q.commit();
972
973 return QVariant();
974}
975
976
977void DatabasePluginPostgres::removeTicketsDetails(int ticket) {
978 MSqlQuery q(m_db);
979 q.exec(QString(R"'(delete from ticket_timesheets where ticket = %1;)'").arg(ticket));
980 q.exec(QString(R"'(delete from ticket_status where ticket = %1;)'").arg(ticket));
981 q.exec(QString(R"'(delete from ticket_values where ticket = %1;)'").arg(ticket));
982 q.exec(QString(R"'(delete from ticket_files where ticket = %1;)'").arg(ticket));
983}
984
985
986QList<Dbt::TicketStatus> DatabasePluginPostgres::ticketStatus(int ticket, bool all) {
987 createTemporaryTableTickets(ticket, all);
988 QList<Dbt::TicketStatus> list;
989 MSqlQuery q(m_db);
990
991 q.prepare(R"'(
992 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
993 from temporary_tickets t, ticket_status ts, statuses s
994 where t.ticket = ts.ticket
995 and ts.status = s.status
996 and t.ticket = :ticket
997 )'");
998 q.bindValue(":ticket", ticket);
999 q.exec();
1000 while (q.next()) {
1001 Dbt::TicketStatus x;
1002 int i=0;
1003 x.id = q.value(i++).toInt();
1004 x.ticket = q.value(i++).toInt();
1005 x.user = q.value(i++).toInt();
1006 x.date = q.value(i++).toDateTime();
1007 x.description = q.value(i++).toString();
1008 x.status = q.value(i++).toString();
1009 x.status_color = q.value(i++).toString();
1010 x.status_description = q.value(i++).toString();
1011 x.status_closed = q.value(i++).toBool();
1012 x.status_can_be_run = q.value(i++).toBool();
1013 x.status_ignored = q.value(i++).toBool();
1014 list << x;
1015 }
1016 return list;
1017}
1018
1019
1020QList<Dbt::TicketStatus> DatabasePluginPostgres::ticketStatus(bool all) {
1021 return ticketStatus(-1, all);
1022}
1023
1024
1025QList<Dbt::TicketStatus> DatabasePluginPostgres::ticketStatus(int id) {
1026 QList<Dbt::TicketStatus> list;
1027 MSqlQuery q(m_db);
1028
1029 q.prepare(R"'(
1030 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
1031 from ticket_status ts, users u, tickets t, users_categories uc, statuses s
1032 where ts.id = :id
1033 and t.ticket = ts.ticket
1034 and t.category = uc.category
1035 and u."user" = ts."user"
1036 and ts.status = s.status
1037 )'");
1038 q.bindValue(":id", id);
1039 q.bindValue(":user", userId());
1040 q.exec();
1041 while (q.next()) {
1042 Dbt::TicketStatus x;
1043 int i=0;
1044 x.id = q.value(i++).toInt();
1045 x.ticket = q.value(i++).toInt();
1046 x.user = q.value(i++).toInt();
1047 x.date = q.value(i++).toDateTime();
1048 x.description = q.value(i++).toString();
1049 x.status = q.value(i++).toString();
1050 x.status_color = q.value(i++).toString();
1051 x.status_description = q.value(i++).toString();
1052 x.status_closed = q.value(i++).toBool();
1053 x.status_can_be_run = q.value(i++).toBool();
1054 x.status_ignored = q.value(i++).toBool();
1055 list << x;
1056 }
1057 return list;
1058}
1059
1060
1061
1062void DatabasePluginPostgres::remove(const Dbt::TicketStatus& id) {
1063 MSqlQuery q(m_db);
1064 q.prepare(R"'(delete from ticket_status where id = :id;)'");
1065 q.bindValue(":id", id.id);
1066 q.exec();
1067}
1068
1069
1070QVariant DatabasePluginPostgres::save(const Dbt::TicketStatus& data) {
1071 MSqlQuery q(m_db);
1072
1073 bool found = false;
1074 if (!data.created) {
1075 q.prepare(R"'(select 1 from ticket_status where id = :id;)'");
1076 q.bindValue(":id", data.id);
1077 q.exec();
1078 found = q.next();
1079 }
1080
1081 if (!data.created && found) {
1082 q.prepare(R"'(
1083 update ticket_status set
1084 ticket = :ticket,
1085 "user" = :user,
1086 date = :date,
1087 description = :description,
1088 status = :status
1089 where id = :id
1090 )'");
1091 q.bindValue(":id", data.id);
1092 q.bindValue(":user", data.user);
1093 q.bindValue(":ticket", data.ticket);
1094 q.bindValue(":date", data.date);
1095 q.bindValue(":description", data.description);
1096 q.bindValue(":status", data.status);
1097 q.exec();
1098 return QVariant(data.id);
1099 }
1100
1101 if (data.created || !found) {
1102 q.prepare(R"'(
1103 insert into ticket_status (ticket, "user", date, description, status)
1104 values (:ticket, :user, :date, :description, :status)
1105 )'");
1106 q.bindValue(":user", data.user);
1107 q.bindValue(":ticket", data.ticket);
1108 q.bindValue(":date", data.date);
1109 q.bindValue(":description", data.description);
1110 q.bindValue(":status", data.status);
1111 q.exec();
1112
1113 return currval("ticket_status_id_seq");
1114 }
1115
1116 Q_UNREACHABLE();
1117 qFatal("Should not happen");
1118 return QVariant();
1119}
1120
1121
1122QVariant DatabasePluginPostgres::save(const Dbt::AppendStatuses& data) {
1123 PDEBUG << data.toMap();
1124
1125 QString statusesX;
1126 QStringList statusesL;
1127 for (int i=0; i<data.recent_status.size(); i++) {
1128 statusesL << "'" + data.recent_status[i].toString() + "'";
1129 }
1130 statusesL.sort();
1131 statusesX = statusesL.join(",");
1132
1133 QString categoriesX;
1134 QStringList categoriesL;
1135 for (int i=0; i<data.categories.size(); i++) {
1136 categoriesL << "'" + data.categories[i].toString() + "'";
1137 }
1138 categoriesL.sort();
1139 categoriesX = categoriesL.join(",");
1140
1141 MSqlQuery q(m_db);
1142 q.prepare(QString(R"'(
1143 with
1144
1145 -- select statuses
1146 x_statuses as (
1147 select * from statuses
1148 where status in (%1)
1149 and not ignored
1150 ),
1151
1152 -- select new status, if possible
1153 x_newstatuses as (
1154 select distinct s.*
1155 from statuses s, status_order o
1156 where status = :status
1157 and s.status = o.next_status
1158 and o.previous_status in (select status from x_statuses)
1159 ),
1160
1161 -- select current user
1162 x_users as (
1163 select *
1164 from users
1165 where "user" = :userid
1166 ),
1167
1168 -- select valid categories
1169 x_categories as (
1170 select c.*
1171 from categories c, x_users u
1172 where c.category in (%2)
1173 and (u.admin = true
1174 or c.category in (select category from users_categories where "user" = u."user")
1175 )
1176 ),
1177
1178 -- select tickets with statuses, user, categories
1179 x_tickets as (
1180 select t.*, ls.*
1181 from tickets t
1182 join x_categories using (category)
1183 left join lateral (select ts.status
1184 from ticket_status ts,
1185 statuses s
1186 where t.ticket = ts.ticket
1187 and s.status = ts.status
1188 and not s.ignored
1189 order by date desc
1190 limit 1) ls on (true)
1191 where ls.status in (select status from x_statuses)
1192 )
1193
1194 insert into ticket_status (ticket, "user", description, status)
1195 select t.ticket, u."user", :description, n.status
1196 from x_tickets t, x_users u, x_newstatuses n
1197
1198 )'").arg(statusesX).arg(categoriesX));
1199 q.bindValue(":user", data.status);
1200 q.bindValue(":status", data.status);
1201 q.bindValue(":description", data.description);
1202 q.bindValue(":userid", userId());
1203 PDEBUG << q.lastBoundQuery();
1204 q.exec();
1205
1206 return QVariant();
1207}
1208
1209
1210QList<Dbt::TicketTimesheets> DatabasePluginPostgres::ticketTimesheets(int ticket, bool all) {
1211 createTemporaryTableTickets(ticket, all);
1212 QList<Dbt::TicketTimesheets> list;
1213 MSqlQuery q(m_db);
1214 q.prepare(R"'(
1215 select tt.id, tt.ticket, tt."user", tt.date_from, tt.date_to
1216 from temporary_tickets t, ticket_timesheets tt
1217 where t.ticket = tt.ticket
1218 and t.ticket = :ticket
1219 order by tt.date_from -- must be sorted!
1220 ;
1221 )'");
1222 q.bindValue(":ticket", ticket);
1223 q.exec();
1224 while (q.next()) {
1225 int i=0;
1226 Dbt::TicketTimesheets x;
1227 x.id = q.value(i++).toInt();
1228 x.ticket = q.value(i++).toInt();
1229 x.user = q.value(i++).toInt();
1230 x.date_from = q.value(i++).toDateTime();
1231 x.date_to = q.value(i++).toDateTime();
1232 list << x;
1233 }
1234 return list;
1235}
1236
1237
1238QList<Dbt::TicketTimesheets> DatabasePluginPostgres::ticketTimesheets(int id) {
1239 QList<Dbt::TicketTimesheets> list;
1240 MSqlQuery q(m_db);
1241 q.prepare(R"'(
1242 select tt.id, tt.ticket, tt."user", tt.date_from, tt.date_to
1243 from ticket_timesheets tt, tickets t, users_categories uc
1244 where t.ticket = tt.ticket
1245 and t.category = uc.category
1246 and uc."user" = :user
1247 and :id = tt.id
1248 order by tt.date_from
1249 ;
1250 )'");
1251 q.bindValue(":user", userId());
1252 q.bindValue(":id", id);
1253 q.exec();
1254 while (q.next()) {
1255 int i=0;
1256 Dbt::TicketTimesheets x;
1257 x.id = q.value(i++).toInt();
1258 x.ticket = q.value(i++).toInt();
1259 x.user = q.value(i++).toInt();
1260 x.date_from = q.value(i++).toDateTime();
1261 x.date_to = q.value(i++).toDateTime();
1262 list << x;
1263 }
1264 return list;
1265}
1266
1267
1268QList<Dbt::TicketTimesheets> DatabasePluginPostgres::ticketTimesheets(bool all) {
1269 return ticketTimesheets(-1, all);
1270}
1271
1272
1273QList<Dbt::TicketTimesheets> DatabasePluginPostgres::runningTimesheets(int ticket) {
1274 QList<Dbt::TicketTimesheets> list;
1275 MSqlQuery q(m_db);
1276 q.prepare(R"'(
1277 select tt.id, tt.ticket, tt."user", tt.date_from, tt.date_to
1278 from ticket_timesheets tt, tickets t, users_categories uc
1279 where t.ticket = tt.ticket
1280 and t.category = uc.category
1281 and uc."user" = :user
1282 and tt.date_to is null
1283 and (:ticket1 = tt.ticket or :ticket2 <= 0)
1284 ;
1285 )'");
1286 q.bindValue(":user", userId());
1287 q.bindValue(":ticket1", ticket);
1288 q.bindValue(":ticket2", ticket);
1289 q.exec();
1290 while (q.next()) {
1291 int i=0;
1292 Dbt::TicketTimesheets x;
1293 x.id = q.value(i++).toInt();
1294 x.ticket = q.value(i++).toInt();
1295 x.user = q.value(i++).toInt();
1296 x.date_from = q.value(i++).toDateTime();
1297 x.date_to = q.value(i++).toDateTime();
1298 list << x;
1299 }
1300 return list;
1301}
1302
1303
1304QList<Dbt::TicketTimesheets> DatabasePluginPostgres::startTimesheet(int ticket) {
1305 QList<Dbt::TicketTimesheets> list;
1306 MSqlQuery q(m_db);
1307
1308 // Zkontroluj, jestli je ticket dostupný pro uživatele a je zastavený
1309 q.prepare(R"'(
1310 select 1
1311 from ticket_timesheets tt, tickets t, users_categories uc
1312 where t.ticket = tt.ticket
1313 and t.category = uc.category
1314 and uc."user" = :user
1315 and :ticket = tt.ticket
1316 and tt.date_to is null
1317 ;
1318 )'");
1319 q.bindValue(":user", userId());
1320 q.bindValue(":ticket", ticket);
1321 q.exec();
1322 if (q.next()) {
1323 return list;
1324 }
1325
1326 // Zkontroluj, jestli je ticket dostupný pro uživatele a existující
1327 q.prepare(R"'(
1328 select 1
1329 from tickets t, users_categories uc
1330 where t.category = uc.category
1331 and uc."user" = :user
1332 and :ticket = t.ticket
1333 ;
1334 )'");
1335 q.bindValue(":user", userId());
1336 q.bindValue(":ticket", ticket);
1337 q.exec();
1338 if (!q.next()) {
1339 return list;
1340 }
1341
1342 // Vložit timesheet
1343 q.prepare(R"'(
1344 insert into ticket_timesheets (ticket, "user", date_from)
1345 values (:ticket, :user, now())
1346 )'");
1347 q.bindValue(":user", userId());
1348 q.bindValue(":ticket", ticket);
1349 q.exec();
1350 QVariant id = currval("ticket_timesheets_id_seq").toInt();
1351 if (id.isNull() || !id.isValid()) {
1352 return list;
1353 }
1354
1355 list = ticketTimesheets(id.toInt());
1356 return list;
1357}
1358
1359
1360QList<Dbt::TicketTimesheets> DatabasePluginPostgres::stopTimesheet(int ticket) {
1361 QList<Dbt::TicketTimesheets> list;
1362 MSqlQuery q(m_db);
1363
1364 q.begin();
1365 // Zkontroluj, jestli je ticket dostupný pro uživatele a je běžící
1366 q.prepare(R"'(
1367 select tt.id
1368 from ticket_timesheets tt, tickets t, users_categories uc
1369 where t.ticket = tt.ticket
1370 and t.category = uc.category
1371 and uc."user" = :user
1372 and :ticket = tt.ticket
1373 and tt.date_to is null
1374 ;
1375 )'");
1376 q.bindValue(":user", userId());
1377 q.bindValue(":ticket", ticket);
1378 q.setForwardOnly(false);
1379 q.exec();
1380 if (q.size() != 1) {
1381 q.commit();
1382 return list;
1383 }
1384 q.next();
1385 int id = q.value(0).toInt();
1386
1387 // Ukončit timesheet
1388 q.prepare(R"'(
1389 update ticket_timesheets set date_to = now() where id = :id;
1390 )'");
1391 q.bindValue(":id", id);
1392 q.exec();
1393
1394 list = ticketTimesheets(ticket, true);
1395
1396 int remove_secs = 10;
1397 int round2_mins = 5;
1398 int round5_mins = 5;
1399 int join_mins = 5;
1400 int remove_singles_mins = 3;
1401
1402 // step 1 - remove timesheets shorter than ...
1403 for (int i=list.size()-1; i>=0; i--) {
1404 Dbt::TicketTimesheets& x = list[i];
1405 if (x.date_from.secsTo(x.date_to) > remove_secs) { continue; }
1406 list.removeAt(i);
1407 }
1408
1409 // step2 - round timeshiits to ...
1410 for (int i=list.size()-1; i>=0; i--) {
1411 Dbt::TicketTimesheets& x = list[i];
1412 int secs;
1413 secs = x.date_from.secsTo(x.date_to);
1414 secs = secs / (round2_mins * 60) + ( (secs % (round2_mins*60)) ? 1 : 0);
1415 secs = secs * (round2_mins * 60);
1416 x.date_to = x.date_from.addSecs(secs);
1417 }
1418
1419 // step3 - join overlapinng records
1420 for (int i=list.size()-1; i>=1; i--) {
1421 Dbt::TicketTimesheets& x0 = list[i-1];
1422 Dbt::TicketTimesheets& x1 = list[i];
1423
1424 if (x0.date_to < x1.date_from) { continue; }
1425 x0.date_to = x1.date_to;
1426 list.removeAt(i);
1427 }
1428
1429 // step4 - join near records
1430 for (int i=list.size()-1; i>=1; i--) {
1431 Dbt::TicketTimesheets& x0 = list[i-1];
1432 Dbt::TicketTimesheets& x1 = list[i];
1433 if (x0.date_to.addSecs(join_mins*60) < x1.date_from) { continue; }
1434 x0.date_to = x1.date_to;
1435 list.removeAt(i);
1436 }
1437
1438 // step5 - round timetracks to ...
1439 for (int i=list.size()-1; i>=0; i--) {
1440 Dbt::TicketTimesheets& x = list[i];
1441 int secs;
1442 secs = x.date_from.secsTo(x.date_to);
1443 secs = secs + (round5_mins * 60) -1;
1444 secs = secs / (round5_mins * 60);
1445 secs = secs * (round5_mins * 60);
1446 x.date_to = x.date_from.addSecs(secs);
1447 }
1448
1449 // step6 - remove short single records
1450 for (int i=list.size()-1; i>=0; i--) {
1451 Dbt::TicketTimesheets& x = list[i];
1452 int secs = x.date_from.secsTo(x.date_to);
1453 if (secs <= 0) { continue; }
1454 if (secs >= remove_singles_mins * 60) { continue; }
1455 list.removeAt(i);
1456 }
1457
1458 q.prepare(R"'(delete from ticket_timesheets where ticket = :ticket;)'");
1459 q.bindValue(":ticket", ticket);
1460 q.exec();
1461
1462 q.prepare(R"'(insert into ticket_timesheets (ticket, "user", date_from, date_to)
1463 values (:ticket, :user, :date_from, :date_to);
1464 )'");
1465 for (int i=0; i<list.size(); i++) {
1466 Dbt::TicketTimesheets& x = list[i];
1467 q.bindValue(":ticket", ticket);
1468 q.bindValue(":user", userId());
1469 q.bindValue(":date_from", x.date_from);
1470 q.bindValue(":date_to", x.date_to);
1471 q.exec();
1472 }
1473
1474 q.commit();
1475 return list.mid(list.size()-1);
1476}
1477
1478
1479QList<Dbt::TicketTimesheets> DatabasePluginPostgres::toggleTimesheet(int ticket) {
1480 QList<Dbt::TicketTimesheets> list;
1481 MSqlQuery q(m_db);
1482
1483 // Zkontroluj, jestli je ticket dostupný pro uživatele a existující
1484 q.prepare(R"'(
1485 select 1
1486 from tickets t, users_categories uc
1487 where t.category = uc.category
1488 and uc."user" = :user
1489 and :ticket = t.ticket
1490 ;
1491 )'");
1492 q.bindValue(":user", userId());
1493 q.bindValue(":ticket", ticket);
1494 q.exec();
1495 if (!q.next()) {
1496 return list;
1497 }
1498
1499 // Zkontroluj, jestli je ticket dostupný pro uživatele a je běžící
1500 q.prepare(R"'(
1501 select tt.id
1502 from ticket_timesheets tt, tickets t, users_categories uc
1503 where t.ticket = tt.ticket
1504 and t.category = uc.category
1505 and uc."user" = :user
1506 and :ticket = tt.ticket
1507 and tt.date_to is null
1508 ;
1509 )'");
1510 q.bindValue(":user", userId());
1511 q.bindValue(":ticket", ticket);
1512 q.setForwardOnly(false);
1513 q.exec();
1514 bool found = (q.size() == 1);
1515 int id = (found) ? q.next(), q.value(0).toInt() : 0;
1516
1517 if (!found) {
1518 // Vložit timesheet
1519 q.prepare(R"'(
1520 insert into ticket_timesheets (ticket, "user", date_from)
1521 values (:ticket, :user, now())
1522 )'");
1523 q.bindValue(":user", userId());
1524 q.bindValue(":ticket", ticket);
1525 q.exec();
1526 QVariant newid = currval("ticket_timesheets_id_seq");
1527 if (newid.isNull() || !newid.isValid()) {
1528 return list;
1529 }
1530 id = newid.toInt();
1531 }
1532
1533 if (found) {
1534 // Ukončit timesheet
1535 q.prepare(R"'(
1536 update ticket_timesheets set date_to = now() where id = :id;
1537 )'");
1538 q.bindValue(":id", id);
1539 q.exec();
1540 }
1541
1542 list = ticketTimesheets(id);
1543 return list;
1544}
1545
1546
1547QVariant DatabasePluginPostgres::save(const Dbt::TicketTimesheets& data) {
1548 MSqlQuery q(m_db);
1549
1550 bool found = false;
1551 if (!data.created) {
1552 q.prepare(R"'(select 1 from ticket_timesheets where id = :id;)'");
1553 q.bindValue(":id", data.id);
1554 q.exec();
1555 found = q.next();
1556 }
1557
1558 if (!data.created && found) {
1559 q.prepare(R"'(
1560 update ticket_timesheets set
1561 ticket = :ticket,
1562 "user" = :user,
1563 date_from = :date_from,
1564 date_to = :date_to
1565 where id = :id
1566 )'");
1567 q.bindValue(":id", data.id);
1568 q.bindValue(":user", data.user);
1569 q.bindValue(":ticket", data.ticket);
1570 q.bindValue(":date_from", data.date_from);
1571 q.bindValue(":date_to", data.date_to);
1572 q.exec();
1573 return QVariant(data.id);
1574 }
1575
1576 if (data.created || !found) {
1577 q.prepare(R"'(
1578 insert into ticket_timesheets (ticket, "user", date_from, date_to)
1579 values (:ticket, :user, :date_from, :date_to)
1580 )'");
1581 q.bindValue(":user", data.user);
1582 q.bindValue(":ticket", data.ticket);
1583 q.bindValue(":date_from", data.date_from);
1584 q.bindValue(":date_to", data.date_to);
1585 q.exec();
1586
1587 return currval("ticket_timesheets_id_seq");
1588 }
1589
1590 Q_UNREACHABLE();
1591 return QVariant();
1592}
1593
1594
1595void DatabasePluginPostgres::remove(const Dbt::TicketTimesheets& id) {
1596 MSqlQuery q(m_db);
1597 q.prepare(R"'(delete from ticket_timesheets where id = :id;)'");
1598 q.bindValue(":id", id.id);
1599 q.exec();
1600}
1601
1602
1603QList<Dbt::TicketValues> DatabasePluginPostgres::ticketValues(int ticket, bool all) {
1604 createTemporaryTableTickets(ticket, all);
1605 QList<Dbt::TicketValues> list;
1606 MSqlQuery q(m_db);
1607 q.prepare(R"'(
1608 select tv.id, tt.ticket, tv.name, tv.value, tv."user", tv.date
1609 from temporary_tickets tt, ticket_values tv
1610 where tt.ticket = tv.ticket
1611 ;
1612 )'");
1613 q.exec();
1614 while (q.next()) {
1615 int i=0;
1616 Dbt::TicketValues x;
1617 x.id = q.value(i++).toInt();
1618 x.ticket = q.value(i++).toInt();
1619 x.name = q.value(i++).toString();
1620 x.value = JSON::data(q.value(i++).toByteArray());
1621 x.user = q.value(i++).toInt();
1622 x.date = q.value(i++).toDateTime();
1623 list << x;
1624 }
1625 return list;
1626}
1627
1628
1629QList<Dbt::TicketValues> DatabasePluginPostgres::ticketValues(int id) {
1630 QList<Dbt::TicketValues> list;
1631 MSqlQuery q(m_db);
1632 q.prepare(R"'(
1633 select tv.id, tv.ticket, tv.date, tv.name, tv.value, tv."user"
1634 from tickets tt, ticket_values tv, users_categories uc
1635 where tt.ticket = tv.ticket
1636 and tt.category = uc.category
1637 and uc.user = :user
1638 and :id = tv.id
1639 ;
1640 )'");
1641 q.bindValue(":id", id);
1642 q.bindValue(":user", userId());
1643 q.exec();
1644 while (q.next()) {
1645 int i=0;
1646 Dbt::TicketValues x;
1647 x.id = q.value(i++).toInt();
1648 x.ticket = q.value(i++).toInt();
1649 x.date = q.value(i++).toDateTime();
1650 x.name = q.value(i++).toString();
1651 x.value = JSON::data(q.value(i++).toByteArray());
1652 x.user = q.value(i++).toInt();
1653 list << x;
1654 }
1655 return list;
1656}
1657
1658
1659QList<Dbt::TicketValues> DatabasePluginPostgres::ticketValues(bool all) {
1660 return ticketValues(-1, all);
1661}
1662
1663
1664QVariant DatabasePluginPostgres::save(const Dbt::TicketValues& data) {
1665 MSqlQuery q(m_db);
1666
1667 bool found = false;
1668 if (!data.created) {
1669 q.prepare(R"'(select 1 from ticket_values where id = :id;)'");
1670 q.bindValue(":id", data.id);
1671 q.exec();
1672 found = q.next();
1673 }
1674
1675 if (!data.created && found) {
1676 q.prepare(QString(R"'(
1677 update ticket_values set
1678 ticket = :ticket,
1679 "user" = :user,
1680 date = :date,
1681 name = :name,
1682 value = '%1'
1683 where id = :id
1684 )'").arg(QString::fromUtf8(JSON::json(data.value))));
1685 q.bindValue(":id", data.id);
1686 q.bindValue(":ticket", data.ticket);
1687 q.bindValue(":user", data.user);
1688 q.bindValue(":date", data.date);
1689 q.bindValue(":name", data.name);
1690 // q.bindValue(":value", JSON::json(data.value));
1691 q.exec();
1692 return QVariant(data.id);
1693 }
1694
1695 if (data.created || !found) {
1696 q.prepare(QString(R"'(
1697 insert into ticket_values (ticket, "user", date, name, value)
1698 select :ticket, :user, :date, :name, '%1'
1699 where not exists (select 1 from ticket_values where id = :id);
1700 )'").arg(QString::fromUtf8(JSON::json(data.value))));
1701 q.bindValue(":id", data.id);
1702 q.bindValue(":ticket", data.ticket);
1703 q.bindValue(":user", data.user);
1704 q.bindValue(":date", data.date);
1705 q.bindValue(":name", data.name);
1706 // q.bindValue(":value", JSON::json(data.value));
1707 q.exec();
1708
1709 return currval("ticket_values_id_seq");
1710 }
1711
1712 Q_UNREACHABLE();
1713 qFatal("Should not happen");
1714 return QVariant();
1715}
1716
1717
1718void DatabasePluginPostgres::remove(const Dbt::TicketValues& id) {
1719 MSqlQuery q(m_db);
1720 q.prepare(R"'(delete from ticket_values where id = :id;)'");
1721 q.bindValue(":id", id.id);
1722 q.exec();
1723}
1724
1725
1726QList<Dbt::Statuses> DatabasePluginPostgres::statuses(const QString& id) {
1727 QList<Dbt::Statuses> list;
1728 MSqlQuery q(m_db);
1729 if (id.isEmpty() || id == "") {
1730 q.prepare(R"'(
1731 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored,
1732 n.status, n.description, n.abbreviation, n.color, n.closed, n.can_be_run, n.ignored
1733 from statuses s
1734 left join status_order o on (s.status = o.previous_status)
1735 left join statuses n on (o.next_status = n.status)
1736 order by s.status, n.status
1737 )'");
1738 } else {
1739 q.prepare(R"'(
1740 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored,
1741 n.status, n.description, n.abbreviation, n.color, n.closed, n.can_be_run, n.ignored
1742 from statuses s
1743 left join status_order o on (s.status = o.previous_status)
1744 left join statuses n on (o.next_status = n.status)
1745 where (:id = s.status)
1746 order by s.status, n.status
1747 )'");
1748 }
1749 q.bindValue(":id", id);
1750 q.exec();
1751 Dbt::Statuses x;
1752 while (q.next()) {
1753 int i=0;
1754 if (!x.status.isEmpty() && x.status != q.value(0).toString()) {
1755 list << x;
1756 x.clear();
1757 }
1758 x.status = q.value(i++).toString();
1759 x.description = q.value(i++).toString();
1760 x.abbreviation = q.value(i++).toString();
1761 x.color = q.value(i++).toString();
1762 x.closed = q.value(i++).toBool();
1763 x.can_be_run = q.value(i++).toBool();
1764 x.ignored = q.value(i++).toBool();
1765 x.can_have_next = true;
1766 if (!q.value(i).isNull()) {
1767 Dbt::Statuses n;
1768 n.status = q.value(i++).toString();
1769 n.description = q.value(i++).toString();
1770 n.abbreviation = q.value(i++).toString();
1771 n.color = q.value(i++).toString();
1772 n.closed = q.value(i++).toBool();
1773 n.can_be_run = q.value(i++).toBool();
1774 n.ignored = q.value(i++).toBool();
1775 x.next << n;
1776 }
1777 }
1778 if (!x.status.isEmpty()) {
1779 list << x;
1780 }
1781 return list;
1782}
1783
1784
1785QList<Dbt::Statuses> DatabasePluginPostgres::statuses(const QString& category, const QString& previousStatus) {
1786 if ((category.isEmpty() || category == "") &&
1787 (previousStatus.isEmpty() || previousStatus == "")) { return statuses(QString()); }
1788 int categoryi = category.toInt();
1789 MSqlQuery q(m_db);
1790 QString ps = (previousStatus.isEmpty()) ? "0" : previousStatus;
1791 auto results = [&q]() {
1792 QList<Dbt::Statuses> list;
1793 q.exec();
1794 while (q.next()) {
1795 Dbt::Statuses x;
1796 int i=0;
1797 x.status = q.value(i++).toString();
1798 x.description = q.value(i++).toString();
1799 x.abbreviation = q.value(i++).toString();
1800 x.color = q.value(i++).toString();
1801 x.closed = q.value(i++).toBool();
1802 x.can_be_run = q.value(i++).toBool();
1803 x.ignored = q.value(i++).toBool();
1804 list << x;
1805 }
1806 return list;
1807 };
1808
1809 bool findNullCategory = (category.isEmpty() || category == "");
1810 if (!findNullCategory) {
1811 q.prepare(R"'(select 1 from status_order where category = :category;)'");
1812 q.bindValue(":category", categoryi);
1813 q.exec();
1814 findNullCategory = !q.next();
1815 }
1816
1817 if (findNullCategory && (previousStatus.isEmpty() || previousStatus == "")) {
1818 q.prepare(R"'(
1819 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored
1820 from statuses s, status_order o
1821 where s.status = o.next_status
1822 and (o.previous_status is null or o.previous_status = '')
1823 and (o.category is null);
1824 )'");
1825 return results();
1826 }
1827
1828 if (findNullCategory) {
1829 q.prepare(R"'(
1830 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored
1831 from statuses s, status_order o
1832 where s.status = o.next_status
1833 and o.previous_status = :previous_status
1834 and (o.category is null);
1835 )'");
1836 q.bindValue(":previous_status", ps);
1837 return results();
1838 }
1839
1840 if (!findNullCategory && (previousStatus.isEmpty() || previousStatus == "")) {
1841 q.prepare(R"'(
1842 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored
1843 from statuses s, status_order o
1844 where s.status = o.next_status
1845 and (o.previous_status is null or o.previous_status = '')
1846 and o.category = :category;
1847 )'");
1848 q.bindValue(":category", categoryi);
1849 return results();
1850 }
1851
1852
1853 if (!findNullCategory) {
1854 q.prepare(R"'(
1855 select s.status, s.description, s.abbreviation, s.color, s.closed, s.can_be_run, s.ignored
1856 from statuses s, status_order o
1857 where s.status = o.next_status
1858 and o.previous_status = :previous_status
1859 and o.category = :category;
1860 )'");
1861 q.bindValue(":previous_status", ps);
1862 q.bindValue(":category", categoryi);
1863 return results();
1864 }
1865
1866 return QList<Dbt::Statuses>();
1867
1868}
1869
1870
1871void DatabasePluginPostgres::remove(const Dbt::Statuses& id) {
1872 MSqlQuery q(m_db);
1873 q.prepare(R"'(delete from statuses where status = :id;)'");
1874 q.bindValue(":id", id.status);
1875 q.exec();
1876}
1877
1878
1879QVariant DatabasePluginPostgres::save(const Dbt::Statuses& data) {
1880 MSqlQuery q(m_db);
1881
1882 begin();
1883 q.prepare(R"'(
1884 update statuses set
1885 description = :description,
1886 abbreviation = :abbreviation,
1887 color = :color,
1888 closed = :closed,
1889 can_be_run = :can_be_run,
1890 ignored = :ignored
1891 where status = :status
1892 )'");
1893 q.bindValue(":description", data.description);
1894 q.bindValue(":abbreviation", data.abbreviation);
1895 q.bindValue(":color", data.color);
1896 q.bindValue(":closed", data.closed);
1897 q.bindValue(":can_be_run", data.can_be_run);
1898 q.bindValue(":ignored", data.ignored);
1899 q.bindValue(":status", data.status);
1900 q.exec();
1901
1902 q.prepare(R"'(
1903 insert into statuses (status, description, abbreviation, color, closed, can_be_run, ignored)
1904 select :status1, :description, :abbreviation, :color, :closed, :can_be_run, :ignored
1905 where not exists (select 1 from statuses where status = :status2);
1906 )'");
1907 q.bindValue(":status1", data.status);
1908 q.bindValue(":description", data.description);
1909 q.bindValue(":abbreviation", data.abbreviation);
1910 q.bindValue(":color", data.color);
1911 q.bindValue(":closed", data.closed);
1912 q.bindValue(":can_be_run", data.can_be_run);
1913 q.bindValue(":ignored", data.ignored);
1914 q.bindValue(":status2", data.status);
1915 q.exec();
1916
1917 q.prepare(R"'(
1918 delete from status_order
1919 where previous_status = :status
1920 )'");
1921 q.bindValue(":status", data.status);
1922 q.exec();
1923
1924 for (int i=0; i<data.next.size(); i++) {
1925 q.prepare(R"'(
1926 insert into status_order (previous_status, next_status)
1927 values (:previous_status, :next_status);
1928 )'");
1929 q.bindValue(":previous_status", data.status);
1930 q.bindValue(":next_status", data.next[i].status);
1931 q.exec();
1932 }
1933
1934 commit();
1935
1936 return QVariant(data.status);
1937}
1938
1939
1940QList<Dbt::StatusTemplates> DatabasePluginPostgres::statusTemplates(int id) {
1941 Q_UNUSED(id);
1942 MSqlQuery q(m_db);
1943 QList<Dbt::StatusTemplates> list;
1944 return list;
1945}
1946
1947
1948QList<Dbt::TicketFiles> DatabasePluginPostgres::ticketFiles(int ticket, bool all) {
1949 createTemporaryTableTickets(ticket, all);
1950 QList<Dbt::TicketFiles> list;
1951 MSqlQuery q(m_db);
1952
1953 q.prepare(R"'(
1954 select f.id, f."user", f.date, f.ticket, f.name, f.type, f.content
1955 from temporary_tickets t, ticket_files f
1956 where t.ticket = f.ticket
1957 ;
1958 )'");
1959 q.exec();
1960 while (q.next()) {
1961 int i=0;
1962 Dbt::TicketFiles x;
1963 x.id = q.value(i++).toInt();
1964 x.user = q.value(i++).toInt();
1965 x.date = q.value(i++).toDateTime();
1966 x.ticket = q.value(i++).toInt();
1967 x.name = q.value(i++).toString();
1968 x.type = q.value(i++).toString();
1969 x.content = q.value(i++).toByteArray();
1970 list << x;
1971 }
1972
1973 return list;
1974}
1975
1976
1977QList<Dbt::TicketFiles> DatabasePluginPostgres::ticketFiles(int id) {
1978 QList<Dbt::TicketFiles> list;
1979 MSqlQuery q(m_db);
1980
1981 q.prepare(R"'(
1982 select f.id, f."user", f.date, f.ticket, f.name, f.type, f.content
1983 from ticket_files f, users u, tickets t, users_categories uc
1984 where f.id = :id
1985 and t.ticket = f.ticket
1986 and t.category = uc.category
1987 and u."user" = f."user"
1988 ;
1989 )'");
1990 q.bindValue(":id", id);
1991 q.bindValue(":user", userId());
1992 q.exec();
1993 while (q.next()) {
1994 int i=0;
1995 Dbt::TicketFiles x;
1996 x.id = q.value(i++).toInt();
1997 x.user = q.value(i++).toInt();
1998 x.date = q.value(i++).toDateTime();
1999 x.ticket = q.value(i++).toInt();
2000 x.name = q.value(i++).toString();
2001 x.type = q.value(i++).toString();
2002 x.content = q.value(i++).toByteArray();
2003 list << x;
2004 }
2005
2006 return list;
2007}
2008
2009
2010QList<Dbt::TicketFiles> DatabasePluginPostgres::ticketFiles(bool all) {
2011 return ticketFiles(-1, all);
2012}
2013
2014
2015QVariant DatabasePluginPostgres::save(const Dbt::TicketFiles& data) {
2016 MSqlQuery q(m_db);
2017
2018 bool found = false;
2019 if (!data.created) {
2020 q.prepare(R"'(select 1 from ticket_files where id = :id;)'");
2021 q.bindValue(":id", data.id);
2022 q.exec();
2023 found = q.next();
2024 }
2025
2026 if (!data.created && found) {
2027 q.prepare(R"'(
2028 update ticket_files set
2029 ticket = :ticket,
2030 date = :date,
2031 "user" = :user,
2032 name = :name,
2033 type = :type,
2034 content = :content
2035 where id = :id
2036 )'");
2037 q.bindValue(":id", data.id);
2038 q.bindValue(":ticket", data.ticket);
2039 q.bindValue(":user", data.user);
2040 q.bindValue(":date", data.date);
2041 q.bindValue(":name", data.name);
2042 q.bindValue(":type", data.type);
2043 q.bindValue(":content", data.content);
2044 q.exec();
2045 return QVariant(data.id);
2046 }
2047
2048 if (data.created || !found) {
2049 q.prepare(R"'(
2050 insert into ticket_files (ticket, "user", date, name, type, content)
2051 values (:ticket, :user, :date, :name, :type, :content);
2052 )'");
2053 q.bindValue(":id", data.id);
2054 q.bindValue(":ticket", data.ticket);
2055 q.bindValue(":user", data.user);
2056 q.bindValue(":date", data.date);
2057 q.bindValue(":name", data.name);
2058 q.bindValue(":type", data.type);
2059 q.bindValue(":content", data.content);
2060 q.exec();
2061 return currval("ticket_files_id_seq");
2062 }
2063
2064 Q_UNREACHABLE();
2065 return QVariant();
2066}
2067
2068
2069void DatabasePluginPostgres::remove(const Dbt::TicketFiles& id) {
2070 MSqlQuery q(m_db);
2071 q.prepare(R"'(delete from ticket_files where id = :id;)'");
2072 q.bindValue(":id", id.id);
2073 q.exec();
2074}
2075
2076
2077QList<Dbt::UsersCategories> DatabasePluginPostgres::usersCategories(int id, int user, const QString& category) {
2078 QList<Dbt::UsersCategories> list;
2079 MSqlQuery q(m_db);
2080 auto retvals = [&]() {
2081 q.exec();
2082 while (q.next()) {
2083 int i=0;
2084 Dbt::UsersCategories x;
2085 x.id = q.value(i++).toInt();
2086 x.user = q.value(i++).toInt();
2087 x.category = q.value(i++).toString();
2088 list << x;
2089 }
2090 return list;
2091 };
2092
2093 if (id > 0) {
2094 q.prepare(R"'(select id, "user", category from users_categories where id = :id)'");
2095 q.bindValue(":id", id);
2096 return retvals();
2097 }
2098
2099 if (id <= 0 && category.isEmpty() && user > 0) {
2100 q.prepare(R"'(select id, "user", category from users_categories where "user" = :user)'");
2101 q.bindValue(":user", user);
2102 return retvals();
2103 }
2104
2105 if (id <= 0 && category.isEmpty() && user <= 0) {
2106 q.prepare(R"'(select id, "user", category from users_categories)'");
2107 return retvals();
2108 }
2109
2110 if (id <= 0 && !category.isEmpty() && user > 0) {
2111 q.prepare(R"'(select id, "user", category from users_categories where "user" = :user and category = :category)'");
2112 q.bindValue(":user", user);
2113 q.bindValue(":category", category);
2114 return retvals();
2115 }
2116
2117 if (id <= 0 && !category.isEmpty() && user <= 0) {
2118 q.prepare(R"'(select id, "user", category from users_categories where category = :category)'");
2119 q.bindValue(":category", category);
2120 return retvals();
2121 }
2122
2123 return list;
2124}
2125
2126
2127
2128QList<Dbt::CategoriesOverview> DatabasePluginPostgres::categoriesOverview(const QStringList& statuses) {
2129 QString statusesX;
2130 QStringList statusesL;
2131 for (int i=0; i<statuses.size(); i++) {
2132 statusesL << "'" + statuses[i] + "'";
2133 }
2134 statusesL.sort();
2135 statusesX = statusesL.join(",");
2136
2137 QList<Dbt::CategoriesOverview> list;
2138 MSqlQuery q(m_db);
2139 q.prepare(QString(R"'(
2140 with recursive tree as (
2141 select category, parent_category, description, 0::int as depth, format('%04s', category) as ordering
2142 from categories where parent_category is null
2143 union
2144 select c.category, c.parent_category, c.description, t.depth + 1 as depth, t.ordering || format('%04s', c.category) as ordering
2145 from tree t, categories c
2146 where
2147 t.category = c.parent_category
2148 ),
2149 x_users as (
2150 select *
2151 from users
2152 where "user" = :userid
2153 ),
2154 x_statuses as (
2155 select *
2156 from statuses
2157 where status in (%1)
2158 and not ignored
2159 ),
2160 x_tickets as (
2161 select t.*, ls.*, tts.*
2162 from tickets t
2163 left join lateral (select ts.status
2164 from ticket_status ts,
2165 statuses s
2166 where t.ticket = ts.ticket
2167 and s.status = ts.status
2168 and not s.ignored
2169 order by date desc
2170 limit 1) ls on (true)
2171 left join lateral (select to_hours(sum(tt.date_to - coalesce(tt.date_from, now()))) as time
2172 from ticket_timesheets tt,
2173 x_users u
2174 where tt.ticket = t.ticket
2175 -- and (tt."user" = u."user" or u.admin = true)
2176 group by tt.ticket) tts on (true)
2177 where ls.status in (select status from x_statuses)
2178 )
2179
2180
2181 select c.depth, c.category, c.description, x.price, x.time, x.tickets_count, c.ordering
2182 into temporary table xxx
2183 from tree c
2184 join x_users u on true
2185 left join users_categories uc on (uc.category = c.category and uc."user" = u."user")
2186 left join (select category, count(1) as tickets_count, sum(time) as time, round(sum(price*time)) as price
2187 from x_tickets
2188 group by category
2189 ) x on (c.category = x.category)
2190 where (uc.category is not null or u.admin = true)
2191 order by c.ordering
2192 ;
2193 )'").arg(statusesX));
2194 q.bindValue(":userid", userId());
2195 q.exec();
2196 q.prepare(R"'(
2197 select 'DETAIL' as type, x.depth, x.category, x.description, x.price, x.time, x.tickets_count, x.ordering
2198 from xxx x
2199 union all
2200 select 'SUM', null, null, 'Total', sum(price), sum(time), sum(tickets_count), null
2201 from xxx
2202 ;
2203 )'");
2204 q.exec();
2205 while (q.next()) {
2206 int i=0;
2207 Dbt::CategoriesOverview x;
2208 x.type = q.value(i++).toString();
2209 x.depth = q.value(i++).toInt();
2210 x.category = q.value(i++).toString();
2211 x.description = q.value(i++).toString();
2212 x.price = q.value(i++).toDouble();
2213 x.time = q.value(i++).toDouble();
2214 x.tickets_count = q.value(i++).toInt();
2215 x.ordering = q.value(i++).toString();
2216 list << x;
2217 }
2218 return list;
2219}
2220
2221
2222QList<Dbt::Overview> DatabasePluginPostgres::overview(const QString& category, const QStringList& statuses) {
2223 QString statusesX;
2224 QStringList statusesL;
2225 for (int i=0; i<statuses.size(); i++) {
2226 statusesL << "'" + statuses[i] + "'";
2227 }
2228 statusesL.sort();
2229 statusesX = "array[" +statusesL.join(",") + "]";
2230
2231 QList<Dbt::Overview> list;
2232 MSqlQuery q(m_db);
2233 q.prepare(R"'(
2234 select key, category, statuses
2235 from overview_params
2236 where category = :category
2237 and statuses = )'" + statusesX );
2238 q.bindValue(":category", category);
2239 q.exec();
2240 if (q.next()) {
2241 list = overview(q.value(0).toString());
2242 return list;
2243 }
2244
2245 quint64 rnd = QRandomGenerator::global()->generate64();
2246 QString rnds = QString::number(rnd, 36).toUpper();
2247 q.prepare("insert into overview_params (key, category, statuses) values (:key, :category, " + statusesX + ");");
2248 q.bindValue(":key", rnds);
2249 q.bindValue(":category", category);
2250 q.exec();
2251 list = overview(rnds);
2252 return list;
2253
2254}
2255
2256
2257QList<Dbt::Overview> DatabasePluginPostgres::overview(const QString& overviewId) {
2258 QList<Dbt::Overview> list;
2259 MSqlQuery q(m_db);
2260 Dbt::Overview overview;
2261 overview.id = overviewId.toUpper();
2262
2263 q.prepare(R"'(
2264 select c.category, c.parent_category, c.description, c.price
2265 from categories c, overview_params p
2266 where c.category = p.category
2267 and p.key = :key
2268 )'");
2269 q.bindValue(":key", overviewId.toUpper());
2270 q.exec();
2271 if (!q.next()) { return list; }
2272 overview.category.category = q.value(0).toString();
2273 overview.category.parent_category = null(q.value(1).toString());
2274 overview.category.description = q.value(2).toString();
2275 overview.category.price = q.value(3).toDouble();
2276
2277 q.exec(R"'(create temporary table overview_categories_tmp(category int);)'");
2278 q.exec(R"'(create temporary table overview_statuses_tmp(status text);)'");
2279
2280 q.prepare(R"'(
2281 with recursive tree(category, parent_category, depth) as (
2282
2283 -- nerekurzivni
2284 select category, parent_category, 0 as depth
2285 from categories
2286 where category in (select category from overview_params where key = :key)
2287
2288 -- rekurzivni
2289 union all
2290 select c.category, c.parent_category, t.depth+1 as depth
2291 from tree t
2292 left join categories c on (c.parent_category = t.category)
2293 where depth < 10
2294 and c.category is not null
2295
2296 )
2297 insert into overview_categories_tmp (category)
2298 select category
2299 from tree;
2300 )'");
2301
2302 q.bindValue(":key", overviewId.toUpper());
2303 q.exec();
2304
2305 q.prepare(R"'(insert into overview_statuses_tmp select unnest(statuses) from overview_params where key = :key;)'");
2306 q.bindValue(":key", overviewId.toUpper());
2307 q.exec();
2308
2309 q.exec(R"'(
2310 with
2311 ticket_last_status as not materialized (
2312 select t.ticket, tl.status, st.description
2313 from tickets t
2314 left join lateral (select tn.ticket, tn.status
2315 from ticket_status tn, statuses s
2316 where tn.ticket = t.ticket
2317 and tn.status = s.status
2318 and not s.ignored
2319 order by ticket, date desc
2320 limit 1
2321 ) tl using (ticket)
2322 left join statuses st on (st.status = tl.status)
2323 ),
2324 ticket_timesheets_sum as not materialized (
2325 select ticket, "user", sum(date_to - date_from) as duration
2326 from ticket_timesheets
2327 group by ticket, "user"
2328 )
2329
2330 -- detaily
2331 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
2332 from tickets t
2333 left join ticket_last_status ls using (ticket)
2334 left join ticket_timesheets_sum ts using (ticket, "user")
2335 left join users u using ("user")
2336 where ls.status in (select status from overview_statuses_tmp)
2337 and category in (select category from overview_categories_tmp)
2338
2339 union all
2340 -- Součet za jednotlivé tickety
2341 -- 0 1 2 3 4 5 6 7
2342 select g.ticket, g.description, -1, null, null, g.duration, g.price, st.description
2343 from (
2344 select t.ticket, t.description, -1, null, null, to_hours(sum(ts.duration)) as duration, sum(to_hours(ts.duration) * t.price) as price
2345 from tickets t
2346 left join ticket_timesheets_sum ts using (ticket, "user")
2347 left join ticket_last_status ls using (ticket)
2348 where ls.status in (select status from overview_statuses_tmp)
2349 and category in (select category from overview_categories_tmp)
2350 group by t.ticket
2351 ) g
2352 left join ticket_last_status st using(ticket)
2353
2354 union all
2355 -- Součet za jednotlivé statusy
2356 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
2357 from ticket_last_status ls
2358 left join tickets t using (ticket)
2359 left join ticket_timesheets_sum ts using (ticket)
2360 where ls.status in (select status from overview_statuses_tmp)
2361 and category in (select category from overview_categories_tmp)
2362 group by ls.description
2363
2364 union all
2365 -- Součet za jednotlivé lidi
2366 -- 0 1 2 3 4 5 6 7
2367 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
2368 from ticket_last_status ls
2369 left join tickets t using (ticket)
2370 left join users u using ("user")
2371 left join ticket_timesheets_sum ts using (ticket)
2372 where ls.status in (select status from overview_statuses_tmp)
2373 and category in (select category from overview_categories_tmp)
2374 group by t."user", u.name
2375
2376 )'");
2377 while (q.next()) {
2378 if (q.value(2).toInt() == -1) {
2379 Dbt::Overview::TicketsSum s;
2380 s.ticket = q.value(0).toInt();
2381 s.description = q.value(1).toString();
2382 s.duration = q.value(5).toDouble();
2383 s.price = q.value(6).toDouble();
2384 s.status = q.value(7).toString();
2385 overview.ticketsSum << s;
2386 continue;
2387 }
2388 if (q.value(2).toInt() == -2) {
2389 Dbt::Overview::StatusSum s;
2390 s.duration = q.value(5).toDouble();
2391 s.price = q.value(6).toDouble();
2392 s.status = q.value(7).toString();
2393 overview.statusSum << s;
2394 continue;
2395 }
2396 if (q.value(2).toInt() == -3) {
2397 Dbt::Overview::UserSum s;
2398 s.user_name = q.value(3).toString();
2399 s.duration = q.value(5).toDouble();
2400 s.price = q.value(6).toDouble();
2401 overview.userSum << s;
2402 continue;
2403 }
2404
2405 int i = 0;
2406 Dbt::Overview::Tickets t;
2407 t.ticket = q.value(i++).toInt();
2408 t.description = q.value(i++).toString();
2409 t.user = q.value(i++).toInt();
2410 t.user_name = q.value(i++).toString();
2411 t.hour_price = q.value(i++).toDouble();
2412 t.duration = q.value(i++).toDouble();
2413 t.price = q.value(i++).toDouble();
2414 t.status = q.value(i++).toString();
2415 overview.tickets << t;
2416 }
2417
2418 q.exec(R"'(
2419 with
2420 ticket_last_status as not materialized (
2421 select t.ticket, tl.status
2422 from tickets t
2423 left join lateral (select tn.ticket, tn.status
2424 from ticket_status tn, statuses s
2425 where tn.ticket = t.ticket
2426 and tn.status = s.status
2427 and not s.ignored
2428 order by ticket, date desc
2429 limit 1
2430 ) tl using (ticket)
2431 ),
2432 ticket_timesheets_sum as not materialized (
2433 select ticket, "user", date_to::date as date, sum(date_to - date_from) as duration
2434 from ticket_timesheets
2435 group by ticket, "user", date_to::date
2436 )
2437
2438 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)
2439 from tickets t
2440 left join users u using ("user")
2441 left join ticket_last_status ls using (ticket)
2442 left join ticket_timesheets_sum ts using (ticket, "user")
2443 left join categories c using (category)
2444 where ls.status in (select status from overview_statuses_tmp)
2445 and category in (select category from overview_categories_tmp)
2446
2447 union all
2448 -- 0 1 2 3 4 5 6 7
2449 select null, null, -1, null, null, null, to_hours(sum(duration)), sum(round(to_hours(ts.duration) * t.price))
2450 from tickets t
2451 left join ticket_last_status ls using (ticket)
2452 left join ticket_timesheets_sum ts using (ticket, "user")
2453 where ls.status in (select status from overview_statuses_tmp)
2454 and category in (select category from overview_categories_tmp)
2455 order by date
2456 )'");
2457 while (q.next()) {
2458 if (q.value(2).toInt() == -1) {
2459 overview.sum.duration = q.value(6).toDouble();
2460 overview.sum.price = q.value(7).toDouble();
2461 continue;
2462 }
2463 int i = 0;
2464 Dbt::Overview::Days t;
2465 t.ticket = q.value(i++).toInt();
2466 t.description = q.value(i++).toString();
2467 t.user = q.value(i++).toInt();
2468 t.user_name = q.value(i++).toString();
2469 t.hour_price = q.value(i++).toDouble();
2470 t.date = q.value(i++).toDateTime();
2471 t.duration = q.value(i++).toDouble();
2472 t.price = q.value(i++).toDouble();
2473 overview.days << t;
2474 }
2475
2476 list << overview;
2477 return list;
2478}
2479
2480
2481QList<Dbt::OverviewList> DatabasePluginPostgres::overviewList() {
2482 MSqlQuery q(m_db);
2483 QList<Dbt::OverviewList> list;
2484
2485 QList<Dbt::Statuses> statusesList = statuses(QString());
2486 QHash<QString, Dbt::Statuses> statusesHash;
2487 for (int i=0; i<statusesList.size(); i++) {
2488 const Dbt::Statuses& item = statusesList[i];
2489 statusesHash[item.status] = item;
2490 }
2491
2492 q.exec(R"'(
2493 select o.key, o.statuses, c.category, c.parent_category, c.description, c.price
2494 from overview_params o
2495 left join categories c using (category)
2496 )'");
2497 while (q.next()) {
2498 int i=0;
2499 Dbt::OverviewList x;
2500 x.key = q.value(i++).toString();
2501
2502 QStringList statuses = q.value(i++).toString().replace("{","").replace("}","").split(",");
2503 for (int i=0; i<statuses.size(); i++) {
2504 x.statuses << statusesHash[statuses[i]];
2505 }
2506
2507 Dbt::Categories c;
2508 c.category = q.value(i++).toString();
2509 c.parent_category = q.value(i++).toString();
2510 c.description = q.value(i++).toString();
2511 c.price = q.value(i++).toDouble();
2512 x.category = c;
2513
2514 list << x;
2515 }
2516
2517 return list;
2518}
2519
2520void DatabasePluginPostgres::remove(const Dbt::OverviewList& x) {
2521 MSqlQuery q(m_db);
2522 q.prepare(R"'(delete from overview_params where key = :key;)'");
2523 q.bindValue(":key", x.key);
2524 q.exec();
2525}
2526
2527
2528QList<Dbt::Departments> DatabasePluginPostgres::departments(int department) {
2529 MSqlQuery q(m_db);
2530 QList<Dbt::Departments> list;
2531 q.prepare(R"'(select department, abbr, description from attendance.departments where department = :key1 or :key2 <= 0;)'");
2532 q.bindValue(":key1", department);
2533 q.bindValue(":key2", department);
2534 q.exec();
2535 while (q.next()) {
2536 Dbt::Departments x;
2537 int i=0;
2538 x.department = q.value(i++).toInt();
2539 x.abbr = q.value(i++).toString();
2540 x.description = q.value(i++).toString();
2541 list << x;
2542 }
2543 return list;
2544}
2545
2546
2547QList<Dbt::Doors> DatabasePluginPostgres::doors(int door) {
2548 MSqlQuery q(m_db);
2549 QList<Dbt::Doors> list;
2550 q.prepare(R"'(select door, description from attendance.doors where door = :key1 or :key2 <= 0;)'");
2551 q.bindValue(":key1", door);
2552 q.bindValue(":key2", door);
2553 q.exec();
2554 while (q.next()) {
2555 Dbt::Doors x;
2556 int i=0;
2557 x.door = q.value(i++).toInt();
2558 x.description = q.value(i++).toString();
2559 list << x;
2560 }
2561 return list;
2562}
2563
2564QList<Dbt::Employees> DatabasePluginPostgres::employees(int employee) {
2565 MSqlQuery q(m_db);
2566 QList<Dbt::Employees> list;
2567 q.prepare(R"'(select e.employee, e.firstname, e.surname, e.active, e."user", coalesce(u.login, '') as login,
2568 e.work_hours_mode, e.rounding_interval,
2569 e.saturdays_paid, e.sundays_paid, e.auto_breaks, e.overtime_paid
2570 from attendance.employees e
2571 left join users u on (u."user" = e."user")
2572 where e.employee = :key1 or :key2 <= 0;)'");
2573 q.bindValue(":key1", employee);
2574 q.bindValue(":key2", employee);
2575 q.exec();
2576 while (q.next()) {
2577 Dbt::Employees x;
2578 int i=0;
2579 x.employee = q.value(i++).toInt();
2580 x.firstname = q.value(i++).toString();
2581 x.surname = q.value(i++).toString();
2582 x.active = q.value(i++).toBool();
2583 x.user = q.value(i++).toInt();
2584 x.login = q.value(i++).toString();
2585 x.work_hours_mode = q.value(i++).toString();
2586 x.rounding_interval = q.value(i++).toString();
2587 x.saturdays_paid = q.value(i++).toBool();
2588 x.sundays_paid = q.value(i++).toBool();
2589 x.auto_breaks = q.value(i++).toBool();
2590 x.overtime_paid = q.value(i++).toBool();
2591 list << x;
2592 }
2593 return list;
2594}
2595
2596QList<Dbt::EventTypes> DatabasePluginPostgres::eventTypes(const QString& eventType) {
2597 MSqlQuery q(m_db);
2598 QList<Dbt::EventTypes> list;
2599 q.prepare(R"X(select event_type, description, end_state, passage, arrival, vacation, sick_leave,
2600 compensatory_leave, business_trip, break_time, unpaid_leave, sick_care, doctor, paid_obstacle
2601 from attendance.event_types
2602 where event_type = :key1 or :key2 = '')X");
2603 q.bindValue(":key1", ((eventType.isNull()) ? QString("") : eventType));
2604 q.bindValue(":key2", ((eventType.isNull()) ? QString("") : eventType));
2605 q.exec();
2606 while (q.next()) {
2607 Dbt::EventTypes x;
2608 int i=0;
2609 x.event_type = q.value(i++).toString();
2610 x.description = q.value(i++).toString();
2611 x.end_state = q.value(i++).toBool();
2612 x.passage = q.value(i++).toBool();
2613 x.arrival = q.value(i++).toBool();
2614 x.vacation = q.value(i++).toBool();
2615 x.sick_leave = q.value(i++).toBool();
2616 x.compensatory_leave = q.value(i++).toBool();
2617 x.business_trip = q.value(i++).toBool();
2618 x.break_time = q.value(i++).toBool();
2619 x.unpaid_leave = q.value(i++).toBool();
2620 x.sick_care = q.value(i++).toBool();
2621 x.doctor = q.value(i++).toBool();
2622 x.paid_obstacle = q.value(i++).toBool();
2623 list << x;
2624 }
2625 return list;
2626}
2627
2628QList<Dbt::Events> DatabasePluginPostgres::events(int event, int employee, const QDate& month, int limit, int offset) {
2629 MSqlQuery q(m_db);
2630 QList<Dbt::Events> list;
2631 QString sql = QStringLiteral(R"X(select
2632 event,
2633 date,
2634 event_type,
2635 event_description,
2636 employee,
2637 firstname,
2638 surname,
2639 valid,
2640 user_edited,
2641 user_edited_name,
2642 error,
2643 note
2644 from attendance.events_view
2645 where (event = :key1 or :key2 <= 0)
2646 and (:employee <= 0 or employee = :employee))X");
2647 if (month.isValid()) {
2648 sql += QStringLiteral(" and date >= :date_from and date < :date_to");
2649 }
2650 sql += QStringLiteral(" order by date");
2651 if (limit > 0) { sql += QStringLiteral(" limit :limit"); }
2652 if (offset > 0) { sql += QStringLiteral(" offset :offset"); }
2653 sql += QStringLiteral(";");
2654
2655 q.prepare(sql);
2656 q.bindValue(":key1", event);
2657 q.bindValue(":key2", event);
2658 q.bindValue(":employee", employee);
2659 if (month.isValid()) {
2660 QDateTime from(QDate(month.year(), month.month(), 1), QTime(0,0,0));
2661 QDateTime to = from.addMonths(1);
2662 q.bindValue(":date_from", from);
2663 q.bindValue(":date_to", to);
2664 }
2665 if (limit > 0) q.bindValue(":limit", limit);
2666 if (offset > 0) q.bindValue(":offset", offset);
2667 q.exec();
2668 while (q.next()) {
2669 Dbt::Events x;
2670 int i=0;
2671 x.event = q.value(i++).toInt();
2672 x.date = q.value(i++).toDateTime();
2673 x.event_type = q.value(i++).toString();
2674 x.event_description = q.value(i++).toString();
2675 x.employee = q.value(i++).toInt();
2676 x.firstname = q.value(i++).toString();
2677 x.surname = q.value(i++).toString();
2678 x.valid = q.value(i++).toBool();
2679 x.user_edited = q.value(i++).toInt();
2680 x.user_edited_name = q.value(i++).toString();
2681 x.error = q.value(i++).toString();
2682 x.note = q.value(i++).toString();
2683 list << x;
2684 }
2685 return list;
2686}
2687
2688QList<Dbt::DepartmentHasManager> DatabasePluginPostgres::departmentHasManager(const Dbt::DepartmentHasManager& p) {
2689 MSqlQuery q(m_db);
2690 QList<Dbt::DepartmentHasManager> list;
2691 q.prepare(R"'(select department, "user" from attendance.department_has_manager
2692 where (department = :key11 or :key12 <= 0)
2693 and ("user" = :key21 or :key22 <= 0);
2694 )'");
2695 q.bindValue(":key11", p.department);
2696 q.bindValue(":key12", p.department);
2697 q.bindValue(":key21", p.user);
2698 q.bindValue(":key22", p.user);
2699 q.exec();
2700 while (q.next()) {
2701 Dbt::DepartmentHasManager x;
2702 int i=0;
2703 x.department = q.value(i++).toInt();
2704 x.user = q.value(i++).toInt();
2705 list << x;
2706 }
2707 return list;
2708}
2709
2710QList<Dbt::DepartmentHasMember> DatabasePluginPostgres::departmentHasMember(const Dbt::DepartmentHasMember& p) {
2711 MSqlQuery q(m_db);
2712 QList<Dbt::DepartmentHasMember> list;
2713 q.prepare(R"'(select department, employee from attendance.department_has_member
2714 where (department = :key11 or :key12 <= 0)
2715 and (employee = :key21 or :key22 <= 0);
2716 )'");
2717 q.bindValue(":key11", p.department);
2718 q.bindValue(":key12", p.department);
2719 q.bindValue(":key21", p.employee);
2720 q.bindValue(":key22", p.employee);
2721 q.exec();
2722 while (q.next()) {
2723 Dbt::DepartmentHasMember x;
2724 int i=0;
2725 x.department = q.value(i++).toInt();
2726 x.employee = q.value(i++).toInt();
2727 list << x;
2728 }
2729 return list;
2730}
2731
2732QList<Dbt::EmployeeCanOpenDoor> DatabasePluginPostgres::employeeCanOpenDoor(const Dbt::EmployeeCanOpenDoor& p) {
2733 MSqlQuery q(m_db);
2734 QList<Dbt::EmployeeCanOpenDoor> list;
2735 q.prepare(R"'(select door, employee from attendance.employee_can_open_door
2736 where (door = :key11 or :key12 <= 0)
2737 and (employee = :key21 or :key22 <= 0);
2738 )'");
2739 q.bindValue(":key11", p.door);
2740 q.bindValue(":key12", p.door);
2741 q.bindValue(":key21", p.employee);
2742 q.bindValue(":key22", p.employee);
2743 q.exec();
2744 while (q.next()) {
2745 Dbt::EmployeeCanOpenDoor x;
2746 int i=0;
2747 x.door = q.value(i++).toInt();
2748 x.employee = q.value(i++).toInt();
2749 list << x;
2750 }
2751 return list;
2752}
2753
2754QList<Dbt::EmployeeHasRfid> DatabasePluginPostgres::employeeHasRfid(const Dbt::EmployeeHasRfid& p) {
2755 MSqlQuery q(m_db);
2756 QList<Dbt::EmployeeHasRfid> list;
2757 q.prepare(R"'(select employee, rfid from attendance.employee_has_rfid
2758 where (employee = :key11 or :key12 <= 0)
2759 and (rfid = :key21 or :key22 <= 0);
2760 )'");
2761 q.bindValue(":key11", p.employee);
2762 q.bindValue(":key12", p.employee);
2763 q.bindValue(":key21", p.rfid);
2764 q.bindValue(":key22", p.rfid);
2765 q.exec();
2766 while (q.next()) {
2767 Dbt::EmployeeHasRfid x;
2768 int i=0;
2769 x.employee = q.value(i++).toInt();
2770 x.rfid = q.value(i++).toInt();
2771 list << x;
2772 }
2773 return list;
2774}
2775
2776QList<Dbt::Rfids> DatabasePluginPostgres::rfids(int rfid) {
2777 MSqlQuery q(m_db);
2778 QList<Dbt::Rfids> list;
2779 q.prepare(R"'(
2780 select r.rfid, r.rfid_id, r.valid, r.note,
2781 coalesce(ehr.employee, 0) as employee,
2782 coalesce(e.firstname, '') as firstname,
2783 coalesce(e.surname, '') as surname
2784 from attendance.rfids r
2785 left join attendance.employee_has_rfid ehr using (rfid)
2786 left join attendance.employees e on (e.employee = ehr.employee)
2787 where r.rfid = :key1 or :key2 <= 0;
2788 )'");
2789 q.bindValue(":key1", rfid);
2790 q.bindValue(":key2", rfid);
2791 q.exec();
2792 while (q.next()) {
2793 Dbt::Rfids x;
2794 int i=0;
2795 x.rfid = q.value(i++).toInt();
2796 x.rfid_id = q.value(i++).toString();
2797 x.valid = q.value(i++).toBool();
2798 x.note = q.value(i++).toString();
2799 x.employee = q.value(i++).toInt();
2800 x.name = q.value(i++).toString();
2801 x.surname = q.value(i++).toString();
2802 list << x;
2803 }
2804 return list;
2805}
2806
2807QList<Dbt::Rfids> DatabasePluginPostgres::rfidsById(const QString& rfidId) {
2808 MSqlQuery q(m_db);
2809 QList<Dbt::Rfids> list;
2810 q.prepare(R"'(
2811 select r.rfid, r.rfid_id, r.valid, r.note,
2812 coalesce(ehr.employee, 0) as employee,
2813 coalesce(e.firstname, '') as firstname,
2814 coalesce(e.surname, '') as surname
2815 from attendance.rfids r
2816 left join attendance.employee_has_rfid ehr using (rfid)
2817 left join attendance.employees e on (e.employee = ehr.employee)
2818 where r.rfid_id = :rfid_id;
2819 )'");
2820 q.bindValue(":rfid_id", rfidId);
2821 q.exec();
2822 while (q.next()) {
2823 Dbt::Rfids x;
2824 int i=0;
2825 x.rfid = q.value(i++).toInt();
2826 x.rfid_id = q.value(i++).toString();
2827 x.valid = q.value(i++).toBool();
2828 x.note = q.value(i++).toString();
2829 x.employee = q.value(i++).toInt();
2830 x.name = q.value(i++).toString();
2831 x.surname = q.value(i++).toString();
2832 list << x;
2833 }
2834 return list;
2835}
2836
2837QList<Dbt::Holidays> DatabasePluginPostgres::holidays(const QDate& date) {
2838 MSqlQuery q(m_db);
2839 QList<Dbt::Holidays> list;
2840 if (date.isValid()) {
2841 q.prepare(R"'(select date, description from attendance.holidays where date = :d;)'");
2842 q.bindValue(":d", date);
2843 } else {
2844 q.prepare(R"'(select date, description from attendance.holidays order by date desc;)'");
2845 }
2846 q.exec();
2847 while (q.next()) {
2848 Dbt::Holidays x;
2849 int i=0;
2850 x.date = q.value(i++).toDate();
2851 x.description = q.value(i++).toString();
2852 list << x;
2853 }
2854 return list;
2855}
2856
2857QList<Dbt::WorkCalendar> DatabasePluginPostgres::workCalendar(const QDate& period) {
2858 MSqlQuery q(m_db);
2859 QList<Dbt::WorkCalendar> list;
2860 if (period.isValid()) {
2861 q.prepare(R"'(select period, working_days, holidays, hours8, hours85 from attendance.work_calendar where period = :p order by period;)'");
2862 q.bindValue(":p", period);
2863 } else {
2864 q.prepare(R"'(select period, working_days, holidays, hours8, hours85 from attendance.work_calendar order by period;)'");
2865 }
2866 q.exec();
2867 while (q.next()) {
2868 Dbt::WorkCalendar x;
2869 int i=0;
2870 x.period = q.value(i++).toDate();
2871 x.working_days = q.value(i++).toInt();
2872 x.holidays = q.value(i++).toInt();
2873 x.hours8 = q.value(i++).toString();
2874 x.hours85 = q.value(i++).toString();
2875 list << x;
2876 }
2877 return list;
2878}
2879
2880void DatabasePluginPostgres::generateWorkCalendar(int year) {
2881 MSqlQuery q(m_db);
2882 q.prepare("select attendance.generate_work_calendar(:y);");
2883 q.bindValue(":y", year);
2884 q.exec();
2885}
2886
2887void DatabasePluginPostgres::copyHolidays(int fromYear, int toYear) {
2888 MSqlQuery q(m_db);
2889 q.prepare("select attendance.copy_holidays(:fy, :ty);");
2890 q.bindValue(":fy", fromYear);
2891 q.bindValue(":ty", toYear);
2892 q.exec();
2893}
2894
2895void DatabasePluginPostgres::remove(const Dbt::Departments& data) {
2896 PDEBUG << data.department;
2897 MSqlQuery q(m_db);
2898 q.prepare(R"'(delete from attendance.departments where department = :key;)'");
2899 q.bindValue(":key", data.department);
2900 q.exec();
2901}
2902
2903void DatabasePluginPostgres::remove(const Dbt::Doors& data) {
2904 MSqlQuery q(m_db);
2905 q.prepare(R"'(delete from attendance.doors where door = :key;)'");
2906 q.bindValue(":key", data.door);
2907 q.exec();
2908}
2909
2910void DatabasePluginPostgres::remove(const Dbt::Employees& data) {
2911 MSqlQuery q(m_db);
2912 q.prepare(R"'(delete from attendance.employees where employee = :key;)'");
2913 q.bindValue(":key", data.employee);
2914 q.exec();
2915}
2916
2917void DatabasePluginPostgres::remove(const Dbt::EventTypes& data) {
2918 MSqlQuery q(m_db);
2919 q.prepare(R"'(delete from attendance.event_types where event_type = :key;)'");
2920 q.bindValue(":key", data.event_type);
2921 q.exec();
2922}
2923
2924void DatabasePluginPostgres::remove(const Dbt::Events& data) {
2925 MSqlQuery q(m_db);
2926 q.prepare(R"'(delete from attendance.events where event = :key;)'");
2927 q.bindValue(":key", data.event);
2928 q.exec();
2929}
2930
2931void DatabasePluginPostgres::remove(const Dbt::DepartmentHasManager& data) {
2932 MSqlQuery q(m_db);
2933 q.prepare(R"'(delete from attendance.department_has_manager where department = :key1 and "user" = :key2;)'");
2934 q.bindValue(":key1", data.department);
2935 q.bindValue(":key2", data.user);
2936 q.exec();
2937}
2938
2939void DatabasePluginPostgres::remove(const Dbt::DepartmentHasMember& data) {
2940 MSqlQuery q(m_db);
2941 q.prepare(R"'(delete from attendance.department_has_member where department = :key and employee = :key2;)'");
2942 q.bindValue(":key1", data.department);
2943 q.bindValue(":key2", data.employee);
2944 q.exec();
2945}
2946
2947void DatabasePluginPostgres::remove(const Dbt::EmployeeCanOpenDoor& data) {
2948 MSqlQuery q(m_db);
2949 q.prepare(R"'(delete from attendance.employee_can_open_door where employee = :key1 and door = :key2;)'");
2950 q.bindValue(":key1", data.employee);
2951 q.bindValue(":key2", data.door);
2952 q.exec();
2953}
2954
2955void DatabasePluginPostgres::remove(const Dbt::EmployeeHasRfid& data) {
2956 MSqlQuery q(m_db);
2957 q.prepare(R"'(delete from attendance.employee_has_rfid where employee = :key1 and rfid = :key2;)'");
2958 q.bindValue(":key1", data.employee);
2959 q.bindValue(":key2", data.rfid);
2960 q.exec();
2961}
2962
2963void DatabasePluginPostgres::remove(const Dbt::Rfids& data) {
2964 MSqlQuery q(m_db);
2965 q.prepare(R"'(delete from attendance.rfids where rfid = :key;)'");
2966 q.bindValue(":key", data.rfid);
2967 q.exec();
2968}
2969
2970void DatabasePluginPostgres::remove(const Dbt::Holidays& data) {
2971 MSqlQuery q(m_db);
2972 q.prepare(R"'(delete from attendance.holidays where date = :key;)'");
2973 q.bindValue(":key", data.date);
2974 q.exec();
2975}
2976
2977void DatabasePluginPostgres::remove(const Dbt::WorkCalendar& data) {
2978 MSqlQuery q(m_db);
2979 q.prepare(R"'(delete from attendance.work_calendar where period = :key;)'");
2980 q.bindValue(":key", data.period);
2981 q.exec();
2982}
2983
2984void DatabasePluginPostgres::remove(const Dbt::AttendanceSummary& data) {
2985 MSqlQuery q(m_db);
2986 q.prepare(R"'(delete from attendance.summary where employee = :key1 and month = :key2;)'");
2987 q.bindValue(":key1", data.employee);
2988 q.bindValue(":key2", data.month);
2989 q.exec();
2990}
2991
2992
2993QVariant DatabasePluginPostgres::save(const Dbt::Departments& data) {
2994 MSqlQuery q(m_db);
2995
2996 q.prepare(R"'(select 1 from attendance.departments where department = :key)'");
2997 q.bindValue(":key", data.department);
2998 q.exec();
2999 if (q.next()) {
3000 q.prepare(R"'(
3001 update attendance.departments set
3002 abbr = :abbr,
3003 description = :description
3004 where department = :department
3005 )'");
3006 q.bindValue(":abbr", data.abbr);
3007 q.bindValue(":description", data.description);
3008 q.bindValue(":department", data.department);
3009 q.exec();
3010 return QVariant(data.department);
3011
3012 } else {
3013
3014 q.prepare(R"'(
3015 insert into attendance.departments (abbr, description)
3016 values (:abbr, :description)
3017 )'");
3018 q.bindValue(":abbr", data.abbr);
3019 q.bindValue(":description", data.description);
3020 q.exec();
3021 return currval("attendance.departments_department_seq");
3022 }
3023
3024 return QVariant();
3025
3026}
3027
3028
3029QVariant DatabasePluginPostgres::save(const Dbt::Employees& data) {
3030 MSqlQuery q(m_db);
3031
3032 q.prepare(R"'(select 1 from attendance.employees where employee = :key)'");
3033 q.bindValue(":key", data.employee);
3034 q.exec();
3035 if (q.next()) {
3036 q.prepare(R"'(
3037 update attendance.employees set
3038 firstname = :firstname,
3039 surname = :surname,
3040 active = :active,
3041 "user" = :user,
3042 work_hours_mode = :work_hours_mode,
3043 rounding_interval = :rounding_interval,
3044 saturdays_paid = :saturdays_paid,
3045 sundays_paid = :sundays_paid,
3046 auto_breaks = :auto_breaks,
3047 overtime_paid = :overtime_paid
3048 where employee = :employee
3049 )'");
3050 q.bindValue(":firstname", data.firstname);
3051 q.bindValue(":surname", data.surname);
3052 q.bindValue(":active", data.active);
3053 q.bindValue(":user", (data.user == 0) ? QVariant() : data.user);
3054 q.bindValue(":work_hours_mode", data.work_hours_mode);
3055 q.bindValue(":rounding_interval", data.rounding_interval);
3056 q.bindValue(":saturdays_paid", data.saturdays_paid);
3057 q.bindValue(":sundays_paid", data.sundays_paid);
3058 q.bindValue(":auto_breaks", data.auto_breaks);
3059 q.bindValue(":overtime_paid", data.overtime_paid);
3060 q.bindValue(":employee", data.employee);
3061 q.exec();
3062 return QVariant(data.employee);
3063
3064 } else {
3065
3066 q.prepare(R"'(
3067 insert into attendance.employees (firstname, surname, active, "user", work_hours_mode,
3068 rounding_interval, saturdays_paid, sundays_paid,
3069 auto_breaks, overtime_paid)
3070 values (:firstname, :surname, :active, :user, :work_hours_mode,
3071 :rounding_interval, :saturdays_paid, :sundays_paid,
3072 :auto_breaks, :overtime_paid)
3073 )'");
3074 q.bindValue(":firstname", data.firstname);
3075 q.bindValue(":surname", data.surname);
3076 q.bindValue(":active", data.active);
3077 q.bindValue(":user", (data.user == 0) ? QVariant() : data.user);
3078 q.bindValue(":work_hours_mode", data.work_hours_mode);
3079 q.bindValue(":rounding_interval", data.rounding_interval);
3080 q.bindValue(":saturdays_paid", data.saturdays_paid);
3081 q.bindValue(":sundays_paid", data.sundays_paid);
3082 q.bindValue(":auto_breaks", data.auto_breaks);
3083 q.bindValue(":overtime_paid", data.overtime_paid);
3084 q.exec();
3085 return currval("attendance.employees_employee_seq");
3086 }
3087
3088 return QVariant();
3089
3090}
3091
3092
3093QVariant DatabasePluginPostgres::save(const Dbt::Doors& data) {
3094 MSqlQuery q(m_db);
3095
3096 q.prepare(R"'(select 1 from attendance.doors where door = :key)'");
3097 q.bindValue(":key", data.door);
3098 q.exec();
3099 if (q.next()) {
3100 q.prepare(R"'(
3101 update attendance.doors set
3102 description = :description
3103 where door = :door
3104 )'");
3105 q.bindValue(":description", data.description);
3106 q.bindValue(":door", data.door);
3107 q.exec();
3108 return QVariant(data.door);
3109
3110 } else {
3111
3112 q.prepare(R"'(
3113 insert into attendance.doors (description)
3114 values (:description)
3115 )'");
3116 q.bindValue(":description", data.description);
3117 q.exec();
3118 return currval("attendance.doors_door_seq");
3119 }
3120
3121 return QVariant();
3122
3123}
3124
3125
3126QVariant DatabasePluginPostgres::save(const Dbt::EventTypes& data) {
3127 MSqlQuery q(m_db);
3128
3129 q.prepare(R"'(select 1 from attendance.event_types where event_type = :key)'");
3130 q.bindValue(":key", data.event_type);
3131 q.exec();
3132 if (q.next()) {
3133 q.prepare(R"'(
3134 update attendance.event_types set
3135 description = :description,
3136 passage = :passage,
3137 end_state = :end_state,
3138 arrival = :arrival,
3139 vacation = :vacation,
3140 sick_leave = :sick_leave,
3141 compensatory_leave = :compensatory_leave,
3142 business_trip = :business_trip,
3143 break_time = :break_time,
3144 unpaid_leave = :unpaid_leave,
3145 sick_care = :sick_care,
3146 doctor = :doctor,
3147 paid_obstacle = :paid_obstacle
3148 where event_type = :event_type
3149 )'");
3150
3151 q.bindValue(":description", data.description);
3152 q.bindValue(":passage", data.passage);
3153 q.bindValue(":end_state", data.end_state);
3154 q.bindValue(":arrival", data.arrival);
3155 q.bindValue(":vacation", data.vacation);
3156 q.bindValue(":sick_leave", data.sick_leave);
3157 q.bindValue(":compensatory_leave", data.compensatory_leave);
3158 q.bindValue(":business_trip", data.business_trip);
3159 q.bindValue(":break_time", data.break_time);
3160 q.bindValue(":unpaid_leave", data.unpaid_leave);
3161 q.bindValue(":sick_care", data.sick_care);
3162 q.bindValue(":event_type", data.event_type);
3163 q.bindValue(":doctor", data.doctor);
3164 q.bindValue(":paid_obstacle", data.paid_obstacle);
3165 q.exec();
3166 return QVariant(data.event_type);
3167
3168 } else {
3169
3170 q.prepare(R"'(
3171 insert into attendance.event_types (
3172 event_type, description,
3173 passage, end_state, arrival, vacation, sick_leave, compensatory_leave,
3174 business_trip, break_time, unpaid_leave, sick_care, doctor, paid_obstacle)
3175 values (
3176 :event_type, :description,
3177 :passage, :end_state, :arrival, :vacation, :sick_leave, :compensatory_leave,
3178 :business_trip, :break_time, :unpaid_leave, :sick_care, :doctor, :paid_obstacle
3179 );
3180 )'");
3181 q.bindValue(":event_type", data.event_type);
3182 q.bindValue(":description", data.description);
3183 q.bindValue(":passage", data.passage);
3184 q.bindValue(":end_state", data.end_state);
3185 q.bindValue(":arrival", data.arrival);
3186 q.bindValue(":vacation", data.vacation);
3187 q.bindValue(":sick_leave", data.sick_leave);
3188 q.bindValue(":compensatory_leave", data.compensatory_leave);
3189 q.bindValue(":business_trip", data.business_trip);
3190 q.bindValue(":break_time", data.break_time);
3191 q.bindValue(":unpaid_leave", data.unpaid_leave);
3192 q.bindValue(":sick_care", data.sick_care);
3193 q.bindValue(":doctor", data.doctor);
3194 q.bindValue(":paid_obstacle", data.paid_obstacle);
3195 q.exec();
3196 return data.event_type;
3197 }
3198
3199 return QVariant();
3200
3201}
3202
3203
3204
3205QVariant DatabasePluginPostgres::save(const Dbt::Events& data) {
3206 MSqlQuery q(m_db);
3207 int event = data.event;
3208
3209 q.begin();
3210 q.prepare(R"'(select 1 from attendance.events where event = :key)'");
3211 q.bindValue(":key", data.event);
3212 q.exec();
3213 if (q.next()) {
3214 q.prepare(R"'(
3215 update attendance.events set
3216 date = :date,
3217 event_type = :etype,
3218 employee = :employee,
3219 valid = :valid,
3220 user_edited = :user_edited
3221 where event = :event
3222 )'");
3223 q.bindValue(":date", data.date.isValid() ? data.date : QDateTime::currentDateTime());
3224 q.bindValue(":etype", data.event_type);
3225 q.bindValue(":employee", data.employee);
3226 q.bindValue(":valid", data.valid);
3227 q.bindValue(":user_edited", data.user_edited);
3228 q.bindValue(":event", data.event);
3229 q.exec();
3230
3231 } else {
3232
3233 q.prepare(R"'(
3234 insert into attendance.events
3235 (date, event_type, employee, valid, user_edited)
3236 values
3237 (:date, :event_type, :employee, :valid, :user_edited)
3238 ;
3239 )'");
3240 q.bindValue(":date", data.date.isValid() ? data.date : QDateTime::currentDateTime());
3241 q.bindValue(":event_type", data.event_type);
3242 q.bindValue(":employee", data.employee);
3243 q.bindValue(":valid", data.valid);
3244 q.bindValue(":user_edited", data.user_edited);
3245 q.exec();
3246 event = currval("attendance.events_event_seq").toInt();
3247 }
3248
3249 q.prepare(R"'(delete from attendance.event_notes where event = :key)'");
3250 q.bindValue(":key", event);
3251 q.exec();
3252
3253 if (!data.note.isNull() && data.note != "") {
3254 q.prepare(R"'(insert into attendance.event_notes (event, note) values (:key, :note);)'");
3255 q.bindValue(":key", event);
3256 q.bindValue(":note", data.note);
3257 q.exec();
3258 }
3259
3260
3261 q.commit();
3262
3263 return QVariant(event);
3264
3265}
3266
3267QVariant DatabasePluginPostgres::save(const Dbt::Rfids& data) {
3268 MSqlQuery q(m_db);
3269 QVariant key;
3270
3271 PDEBUG << data.rfid << data.employee;
3272
3273 q.begin();
3274
3275 q.prepare(R"'(select 1 from attendance.rfids where rfid = :key)'");
3276 q.bindValue(":key", data.rfid);
3277 q.exec();
3278 if (q.next()) {
3279 q.prepare(R"'(
3280 update attendance.rfids set
3281 rfid_id = :rfid_id,
3282 valid = :valid,
3283 note = :note
3284 where rfid = :rfid
3285 )'");
3286 q.bindValue(":rfid_id", data.rfid_id);
3287 q.bindValue(":valid", data.valid);
3288 q.bindValue(":note", data.note);
3289 q.bindValue(":rfid", data.rfid);
3290 q.exec();
3291 key = data.rfid;
3292
3293 } else {
3294
3295 q.prepare(R"'(
3296 insert into attendance.rfids (rfid_id, valid, note)
3297 values (:rfid_id, :valid, :note)
3298 )'");
3299 q.bindValue(":rfid_id", data.rfid_id);
3300 q.bindValue(":valid", data.valid);
3301 q.bindValue(":note", data.note);
3302 q.exec();
3303 key = currval("attendance.rfids_rfid_seq");
3304 }
3305
3306 if (data.employee <= 0) {
3307 q.prepare(R"'(delete from attendance.employee_has_rfid where rfid = :rfid;)'");
3308 q.bindValue(":rfid", key);
3309 q.exec();
3310 }
3311
3312 if (data.employee > 0) {
3313 q.prepare(R"'(
3314 insert into attendance.employee_has_rfid (rfid, employee)
3315 values (:rfid, :employee)
3316 on conflict (rfid) do update set employee = excluded.employee
3317 )'");
3318 q.bindValue(":rfid", key);
3319 q.bindValue(":employee", data.employee);
3320 q.exec();
3321 }
3322
3323 q.commit();
3324
3325 return key;
3326
3327}
3328
3329
3330QVariant DatabasePluginPostgres::save(const Dbt::Holidays& data) {
3331 MSqlQuery q(m_db);
3332 q.prepare(R"'(select 1 from attendance.holidays where date = :key)'");
3333 q.bindValue(":key", data.date);
3334 q.exec();
3335 if (q.next()) {
3336 q.prepare(R"'(update attendance.holidays set description = :description where date = :date)'");
3337 } else {
3338 q.prepare(R"'(insert into attendance.holidays (date, description) values (:date, :description))'");
3339 }
3340 q.bindValue(":date", data.date);
3341 q.bindValue(":description", data.description);
3342 q.exec();
3343 return data.date;
3344}
3345
3346
3347QVariant DatabasePluginPostgres::save(const Dbt::WorkCalendar& data) {
3348 MSqlQuery q(m_db);
3349 q.prepare(R"'(select 1 from attendance.work_calendar where period = :key)'");
3350 q.bindValue(":key", data.period);
3351 q.exec();
3352 if (q.next()) {
3353 q.prepare(R"'(update attendance.work_calendar set working_days = :working_days, holidays = :holidays, hours8 = :hours8, hours85 = :hours85 where period = :period)'");
3354 } else {
3355 q.prepare(R"'(insert into attendance.work_calendar (period, working_days, holidays, hours8, hours85) values (:period, :working_days, :holidays, :hours8, :hours85))'");
3356 }
3357 q.bindValue(":period", data.period);
3358 q.bindValue(":working_days", data.working_days);
3359 q.bindValue(":holidays", data.holidays);
3360 q.bindValue(":hours8", data.hours8);
3361 q.bindValue(":hours85", data.hours85);
3362 q.exec();
3363 return data.period;
3364}
3365
3366
3367Dbt::UserEmployeeAccess DatabasePluginPostgres::canAccessAttendance(int employee) {
3368 MSqlQuery q(m_db);
3369 q.prepare(R"'(
3370 with params as (
3371 select :user::integer as "user",
3372 :employee::integer as "employee"
3373 ),
3374
3375 employee_in_my_departments as (
3376 select distinct mem.employee, true as allowed
3377 from attendance.department_has_manager man,
3378 attendance.department_has_member mem,
3379 attendance.employees e,
3380 params p
3381 where mem.employee = e.employee
3382 and mem.employee = p.employee
3383 and man."user" = p."user"
3384 and man.department = mem.department
3385 ),
3386
3387 employee_in_my_user as (
3388 select distinct e.employee, true as allowed
3389 from attendance.employees e,
3390 params p
3391 where e."user" = p."user"
3392 and e.employee = p.employee
3393 )
3394
3395 select p.employee,
3396 p."user",
3397 coalesce((select allowed from employee_in_my_departments), false) as can_write,
3398 coalesce((select allowed from employee_in_my_departments), (select allowed from employee_in_my_user), false) as can_read
3399 from params p
3400
3401 ;)'");
3402 q.bindValue(":user", userId());
3403 q.bindValue(":employee", employee);
3404 q.exec();
3405 Dbt::UserEmployeeAccess x;
3406 x.user = userId();
3407 x.employee = employee;
3408 if (q.next()) {
3409 x.can_write = q.value(2).toBool();
3410 x.can_read = q.value(3).toBool();
3411 }
3412 return x;
3413}
3414
3415
3416QList<Dbt::Employees> DatabasePluginPostgres::attendanceChecklist(const QDate& month) {
3417 QList<Dbt::Employees> list;
3418 MSqlQuery q(m_db);
3419 q.prepare(R"'(
3420 with params as (
3421 select
3422 :user::integer as "user",
3423 :month::date as month
3424 ),
3425
3426 employees_list as (
3427 select employee
3428 from attendance.events e
3429 join params p on true
3430 join attendance.event_types et using (event_type)
3431 where e.valid
3432 and e.date >= p.month
3433 and e.date < p.month + '1month'::interval
3434 and not et.passage -- průchody nebrad
3435 and not et.end_state -- ukončení nebrat
3436 group by employee
3437 ),
3438
3439 employees_accessible as (
3440 select distinct employee
3441 from attendance.department_has_manager man,
3442 attendance.department_has_member mem,
3443 params p
3444 where man."user" = p."user"
3445 and man.department = mem.department
3446
3447 )
3448
3449 select e.employee, e.firstname, e.surname, e.active, e."user", coalesce(u.login, '') as login,
3450 e.work_hours_mode, e.rounding_interval,
3451 e.saturdays_paid, e.sundays_paid, e.auto_breaks, e.overtime_paid
3452 from employees_list el
3453 join employees_accessible ea using (employee)
3454 left join attendance.employees e using (employee)
3455 left join users u using ("user")
3456 where e.active
3457 order by e.surname, e.firstname
3458 ;)'");
3459 q.bindValue(":user", userId());
3460 q.bindValue(":month", month);
3461 q.exec();
3462 while (q.next()) {
3463 Dbt::Employees x;
3464 int i=0;
3465 x.employee = q.value(i++).toInt();
3466 x.firstname = q.value(i++).toString();
3467 x.surname = q.value(i++).toString();
3468 x.active = q.value(i++).toBool();
3469 x.user = q.value(i++).toInt();
3470 x.login = q.value(i++).toString();
3471 x.work_hours_mode = q.value(i++).toString();
3472 x.rounding_interval = q.value(i++).toString();
3473 x.saturdays_paid = q.value(i++).toBool();
3474 x.sundays_paid = q.value(i++).toBool();
3475 x.auto_breaks = q.value(i++).toBool();
3476 x.overtime_paid = q.value(i++).toBool();
3477 list << x;
3478 }
3479 return list;
3480}
3481
3482
3483QList<Dbt::AttendanceChecklist> DatabasePluginPostgres::attendanceChecklist(int employee, const QDate& month) {
3484 QList<Dbt::AttendanceChecklist> list;
3485 Dbt::UserEmployeeAccess access = canAccessAttendance(employee);
3486 if (!access.can_read) { return list; }
3487 MSqlQuery q(m_db);
3488 q.prepare(R"'(
3489 with
3490
3491 params as (
3492 select
3493 :employee::integer as employee,
3494 :month::timestamp with time zone as month
3495 ),
3496
3497 /* Vybere záznamy z tabulky attendance.events a označí všechny chyby */
3498 events_errors as (
3499 select
3500 e.event,
3501 e.date,
3502 e.event_type,
3503 t.description as event_description,
3504 e.employee,
3505 e.valid,
3506 e.user_edited,
3507 case when e.valid and t.end_state and prev.end_state then 'U' else -- unexpected end
3508 case when e.valid and not t.end_state and not next.end_state then 'M' else -- missing end
3509 case when e.valid and not t.end_state and next.end_state is null then 'M' else -- missing end, last record
3510 null::text
3511 end end end as error
3512
3513 from attendance.events e
3514 left join attendance.event_types t using (event_type)
3515 join params p on true
3516
3517 /* Předchozí záznam */
3518 left join lateral (select *
3519 from attendance.events ep
3520 left join attendance.event_types tp using (event_type)
3521 where e.valid
3522 and ep.valid
3523 and ep.date < e.date
3524 and ep.employee = e.employee
3525 and not tp.passage
3526 order by date desc
3527 limit 1
3528 ) prev on true
3529
3530 /* Následující záznam */
3531 left join lateral (select *
3532 from attendance.events en
3533 left join attendance.event_types tn using (event_type)
3534 where e.valid
3535 and en.valid
3536 and en.date > e.date
3537 and en.employee = e.employee
3538 and not tn.passage
3539 order by date
3540 limit 1
3541 ) next on true
3542
3543 where p.employee = e.employee
3544 and e.date > p.month -- Omezení na aktuální měsíc a kousekzení na aktuální měsíc a kousekzení na aktuální měsíc a kousekzení na aktuální měsíc a kousek
3545 and e.date < p.month + '1 month'::interval + '2 days'::interval
3546 and not t.passage -- Omezení na datové typy, které se týkají docházky, tj. typu "Záčátek" a "Konec"
3547 ),
3548
3549 /* Vyhodí záznamy s chybami "nadbytečný odchod" */
3550 events_fixed1 as (
3551 select event, date, event_type, false as generated, employee, valid, user_edited, error, n.note as note
3552 from events_errors
3553 left join attendance.event_notes n using (event)
3554 where valid
3555 and (error is null or error != 'U')
3556 ),
3557
3558
3559 /* Vybere záznamy s chybou "chybějící odchod", upraví je tak, aby se tvářily jako chybějící záznam */
3560 events_generated_ends as (
3561 select null::integer as event,
3562 case when next.date is null then ef1.date + '8 hours'::interval else next.date - '1 second'::interval end as date,
3563 et.event_type,
3564 true as generated,
3565 ef1.employee,
3566 true as valid,
3567 u."user" as user_edited,
3568 null::text as error,
3569 null::text as note
3570
3571 from events_fixed1 ef1
3572 join (select * from attendance.event_types where end_state limit 1) et on true
3573 join (select * from users where admin order by "user" limit 1) u on true
3574
3575 /* Následující záznam */
3576 left join lateral (select *
3577 from attendance.events ep
3578 left join attendance.event_types tn using (event_type)
3579 where ep.valid
3580 and ep.date > ef1.date
3581 and ep.employee = ef1.employee
3582 and not tn.passage
3583 order by date
3584 limit 1
3585 ) next on true
3586
3587 where error = 'M'
3588
3589 ),
3590
3591 /*
3592 Vytvoří opravený seznam, výstupem jsou vždy perfektně seřazené dvojice BEGIN - END za sebou.
3593 */
3594 events_fixed as (
3595 select * from (
3596 -- vybere všechny bezchybné záznamy
3597 select * from events_fixed1
3598 union all
3599 -- vybere všechny vygenerované ukončovací záznamy
3600 -- a připojí je k těm bezchybnatým
3601 select * from events_generated_ends
3602 ) x
3603 order by x.date
3604 ),
3605
3606 /*
3607 Spojí související dvojice BEGIN - END do jednoho širokého záznamu
3608 */
3609 events_paired as (
3610 select x.*
3611 from (
3612 select
3613 e.employee,
3614 e.event as start_event,
3615 e.date as start_date,
3616 e.event_type as start_event_type,
3617 e.note as start_note,
3618 e.error as start_error,
3619 e.user_edited as start_user_edited,
3620 lead(e.event) over w as end_event,
3621 lead(e.date) over w as end_date,
3622 lead(e.event_type) over w as end_event_type,
3623 lead(e.note) over w as end_note,
3624 lead(e.error) over w as end_error,
3625 lead(e.user_edited) over w as end_user_edited,
3626 lead(e.generated) over w as end_generated
3627 from events_fixed e
3628 where e.valid
3629 window w as (partition by employee order by e.date)
3630 order by e.date
3631 ) x, params p
3632 where x.start_event_type != 'END'
3633 and x.start_date < p.month + '1 month'::interval
3634 order by start_date
3635 ),
3636
3637 /*
3638 Doplní k záznamům zaokrouhlení dolů, výsledek dá do soupce "hours"
3639 */
3640 events_rounded as (
3641 select ep.*,
3642 make_interval(secs =>
3643 floor (
3644 extract(epoch from (end_date - start_date)) /
3645 nullif(extract(epoch from emp.rounding_interval), 0)
3646 ) * extract(epoch from emp.rounding_interval)
3647 ) as rounded_hours
3648 from events_paired ep
3649 join attendance.employees emp using (employee)
3650 ),
3651
3652
3653 /*
3654 Denní součty
3655 */
3656 events_daily as (
3657 select x2.*,
3658 sum(x2.should_be) over (partition by employee order by x2.start_date) as should_be_cumulative
3659 from (select x1.*,
3660 emp.work_hours_mode
3661 * case when not emp.saturdays_paid and extract(dow from x1.start_date) = 6 then 0 else 1 end
3662 * case when not emp.sundays_paid and extract(dow from x1.start_date) = 0 then 0 else 1 end
3663 as should_be
3664 from ( select
3665 p.employee,
3666 null::integer as start_event,
3667 d.day as start_date,
3668 null::text as start_event_type,
3669 null::text as start_note,
3670 null::text as start_error,
3671 null::integer as start_user_edited,
3672 null::integer as end_event,
3673 null::timestamp with time zone as end_date,
3674 null::text as end_event_type,
3675 null::text as end_note,
3676 null::text as end_error,
3677 null::integer as end_user_edited,
3678 false as end_generated,
3679 sum(er.rounded_hours) as rounded_hours,
3680 sum(sum(er.rounded_hours)) over (partition by p.employee order by d.day) as cumulative_hours
3681 from params p
3682 cross join generate_series(
3683 date_trunc('month', p.month),
3684 date_trunc('month', p.month) + '1 month'::interval - '1 day'::interval,
3685 '1 day'::interval
3686 ) as d(day)
3687 left join events_rounded er on (er.employee = p.employee and date_trunc('day', er.start_date) = d.day)
3688 group by p.employee, d.day
3689 ) x1
3690 join attendance.employees emp using (employee)
3691 ) x2
3692 ),
3693
3694 events_daily_plus_records as (
3695 select *, null::interval as cumulative_hours, null::interval as should_be, null::interval should_be_cumulative
3696 from events_rounded
3697 union all
3698 select *
3699 from events_daily
3700 order by employee, start_date
3701 )
3702
3703 select employee, start_event, start_date, start_event_type, start_note, start_error, start_user_edited, end_event,
3704 end_date, end_event_type, end_note, end_error, end_user_edited, end_generated,
3705 rounded_hours, cumulative_hours, should_be, should_be_cumulative
3706 into temporary table attendance_checklist
3707 from events_daily_plus_records;
3708 )'");
3709 q.bindValue(":employee", employee);
3710 q.bindValue(":month", month);
3711 q.exec();
3712
3713 q.exec(R"'(
3714 select extract('dow' from ac.start_date) as dow, h.date is not null as holiday, h.description as holiday_description,
3715 ac.start_event, ac.start_date, ac.start_event_type, st.description as start_event_type_description, ac.start_note, ac.start_error, ac.start_user_edited, su.name as start_user_edited_name,
3716 ac.end_event, ac.end_date, ac.end_event_type, et.description as end_event_type_description, ac.end_note, ac.end_error, ac.end_user_edited, eu.name as end_user_edited_name, ac.end_generated,
3717 extract(epoch from ac.rounded_hours)/3600.0 as rounded_hours,
3718 extract(epoch from ac.cumulative_hours)/3600.0 as cumulative_hours,
3719 extract(epoch from ac.should_be)/3600.0 as should_be,
3720 extract(epoch from ac.should_be_cumulative)/3600.0 as should_be_cumulative
3721 from attendance_checklist ac
3722 left join users su on (su."user" = ac.start_user_edited)
3723 left join users eu on (eu."user" = ac.end_user_edited)
3724 left join attendance.event_types st on (st.event_type = ac.start_event_type)
3725 left join attendance.event_types et on (et.event_type = ac.end_event_type)
3726 left join attendance.holidays h on (h.date = ac.start_date)
3727 ;
3728 )'");
3729
3730 Dbt::AttendanceChecklist checklist;
3731 checklist.month = month;
3732 checklist.can_write = access.can_write;
3733 while (q.next()) {
3734 int i=0;
3735 Dbt::AttendanceDays x;
3736 x.dow = q.value(i++).toInt();
3737 x.holiday = q.value(i++).toBool();
3738 x.holiday_description = q.value(i++).toString();
3739 // Začátek
3740 x.start_event = q.value(i++).toInt();
3741 x.start_date = q.value(i++).toDateTime();
3742 x.start_event_type = q.value(i++).toString();
3743 x.start_event_description = q.value(i++).toString();
3744 x.start_event_note = q.value(i++).toString();
3745 x.start_event_error = q.value(i++).toString();
3746 x.start_user_edited = q.value(i++).toInt();
3747 x.start_user_edited_name = q.value(i++).toString();
3748 // Konec
3749 x.end_event = q.value(i++).toInt();
3750 x.end_date = q.value(i++).toDateTime();
3751 x.end_event_type = q.value(i++).toString();
3752 x.end_event_description = q.value(i++).toString();
3753 x.end_event_note = q.value(i++).toString();
3754 x.end_event_error = q.value(i++).toString();
3755 x.end_user_edited = q.value(i++).toInt();
3756 x.end_user_edited_name = q.value(i++).toString();
3757 x.end_generated = q.value(i++).toBool();
3758 x.rounded_hours = q.value(i++).toDouble(); // interval (hours)
3759 x.cumulative_hours = q.value(i++).toDouble(); // interval (hours)
3760 x.should_be = q.value(i++).toDouble(); // interval (hours)
3761 x.should_be_cumulative = q.value(i++).toDouble(); // interval (hours)
3762 checklist.days << x;
3763 }
3764
3765 QList<Dbt::Employees> ex = employees(employee);
3766 if (!ex.isEmpty()) {
3767 checklist.employee = ex[0];
3768 }
3769
3770 q.exec(R"'(
3771
3772 with extracted as (
3773 select ac.*, et.*, extract(epoch from rounded_hours)/3600.0 as hours
3774 from attendance_checklist ac
3775 left join attendance.event_types et on (et.event_type = ac.start_event_type)
3776 ),
3777 day_counts as (
3778 select count(1) as days
3779 from ( select date(e.start_date)
3780 from extracted e
3781 where e.arrival
3782 group by date(e.start_date)
3783 order by date(e.start_date)
3784 ) x
3785 ),
3786 calendar as (
3787 select wc.working_days, wc.holidays, wc.hours8, wc.hours85
3788 from attendance.work_calendar wc
3789 where wc.period = date((select date_trunc('month', start_date) from extracted limit 1))
3790 limit 1
3791 ),
3792 afternoons as (
3793 select sum(
3794 case when e.start_date < e.end_date and (e.end_date - start_date ) >= '5 hours'::interval
3795 then extract(epoch from make_interval(secs =>
3796 floor (
3797 extract(epoch from (e.end_date - e.start_date)) /
3798 nullif(extract(epoch from emp.rounding_interval), 0)
3799 ) * extract(epoch from emp.rounding_interval))) / 3600.0
3800 else 0.0
3801 end) as afternoon
3802 from (select employee,
3803 greatest (start_date, date_trunc('day', start_date) + '14 hours'::interval) as start_date,
3804 least (end_date, date_trunc('day', start_date) + '22 hours'::interval) as end_date
3805 from extracted x
3806 where arrival
3807 ) e
3808 join attendance.employees emp using (employee)
3809 ),
3810 nights as (
3811 select sum(
3812 case when e.start_date < e.end_date and (e.end_date - start_date ) >= '2 hours'::interval
3813 then extract(epoch from make_interval(secs =>
3814 floor (
3815 extract(epoch from (e.end_date - e.start_date)) /
3816 nullif(extract(epoch from emp.rounding_interval), 0)
3817 ) * extract(epoch from emp.rounding_interval))) / 3600.0
3818 else 0.0
3819 end) as night
3820 from (select employee,
3821 greatest (start_date, date_trunc('day', start_date) + '22 hours'::interval) as start_date,
3822 least (end_date, date_trunc('day', start_date) + '30 hours'::interval) as end_date
3823 from extracted x
3824 where arrival
3825 ) e
3826 join attendance.employees emp using (employee)
3827 ),
3828 weekdays as (
3829 select
3830 sum(case when extract('dow' from e.start_date) = 0 and e.start_date < e.end_date and (e.end_date - start_date ) >= '1 hours'::interval
3831 then extract(epoch from make_interval(secs =>
3832 floor (
3833 extract(epoch from (e.end_date - e.start_date)) /
3834 nullif(extract(epoch from emp.rounding_interval), 0)
3835 ) * extract(epoch from emp.rounding_interval))) / 3600.0
3836 else 0.0
3837 end) as sunday,
3838 sum(case when extract('dow' from e.start_date) = 6 and e.start_date < e.end_date and (e.end_date - start_date ) >= '1 hours'::interval
3839 then extract(epoch from make_interval(secs =>
3840 floor (
3841 extract(epoch from (e.end_date - e.start_date)) /
3842 nullif(extract(epoch from emp.rounding_interval), 0)
3843 ) * extract(epoch from emp.rounding_interval))) / 3600.0
3844 else 0.0
3845 end) as saturday
3846 from (select employee,
3847 greatest(start_date, date_trunc('day', start_date)) as start_date,
3848 least (end_date, date_trunc('day', start_date) + '1day'::interval) as end_date
3849 from extracted x
3850 where arrival
3851 ) e
3852 join attendance.employees emp using (employee)
3853 ),
3854
3855 summaries as (
3856 select
3857 sum(case when arrival then er.hours else 0 end) as arrival,
3858 sum(case when vacation then er.hours else 0 end) as vacation,
3859 sum(case when sick_leave then er.hours else 0 end) as sick_leave,
3860 sum(case when compensatory_leave then er.hours else 0 end) as compensatory_leave,
3861 sum(case when business_trip then er.hours else 0 end) as business_trip,
3862 sum(case when break_time then er.hours else 0 end) as break_time,
3863 sum(case when unpaid_leave then er.hours else 0 end) as unpaid_leave,
3864 sum(case when sick_care then er.hours else 0 end) as sick_care,
3865 sum(case when paid_obstacle then er.hours else 0 end) as paid_obstacle,
3866 sum(case when doctor then er.hours else 0 end) as doctor,
3867 sum(case when arrival and h.date is not null then 1 else 0 end) as holidays
3868 from extracted er
3869 left join attendance.holidays h on (h.date = date(er.start_date))
3870 )
3871 select
3872 d.days,
3873 c.working_days as calendar_working_days,
3874 c.holidays as calendar_holidays,
3875 -- c.hours8 as calendar_hours8,
3876 -- c.hours85 as calendar_hours85,
3877 a.afternoon,
3878 n.night,
3879 w.sunday,
3880 w.saturday,
3881 s.arrival,
3882 s.vacation,
3883 s.sick_leave,
3884 s.compensatory_leave,
3885 s.business_trip,
3886 s.break_time,
3887 s.unpaid_leave,
3888 s.sick_care,
3889 s.paid_obstacle,
3890 s.doctor,
3891 s.holidays
3892 from summaries s, day_counts d, calendar c, afternoons a, nights n, weekdays w
3893 ;
3894 )'");
3895 while (q.next()) {
3896 int i=0;
3897 Dbt::AttendanceSummary& x = checklist.summary_calculated;
3898 x.days = q.value(i++).toInt();
3899 x.calendar_working_days = q.value(i++).toInt();
3900 x.calendar_holidays = q.value(i++).toInt();
3901 x.afternoon = q.value(i++).toDouble();
3902 x.night = q.value(i++).toDouble();
3903 x.sunday = q.value(i++).toDouble();
3904 x.saturday = q.value(i++).toDouble();
3905 x.arrival = q.value(i++).toDouble();
3906 x.vacation = q.value(i++).toDouble();
3907 x.sick_leave = q.value(i++).toDouble();
3908 x.compensatory_leave = q.value(i++).toDouble();
3909 x.business_trip = q.value(i++).toDouble();
3910 x.break_time = q.value(i++).toDouble();
3911 x.unpaid_leave = q.value(i++).toDouble();
3912 x.sick_care = q.value(i++).toDouble();
3913 x.paid_obstacle = q.value(i++).toDouble();
3914 x.doctor = q.value(i++).toDouble();
3915 }
3916
3917 const QList<Dbt::AttendanceSummary> summaryList = attendanceSummary(employee, month);
3918 if (!summaryList.isEmpty()) {
3919 checklist.summary_saved = summaryList.first();
3920 }
3921
3922 list << checklist;
3923 return list;
3924
3925}
3926
3927
3928
3929QList<Dbt::AttendanceSummary> DatabasePluginPostgres::attendanceSummary(int employee, const QDate& month) {
3930 QList<Dbt::AttendanceSummary> list;
3931 MSqlQuery q(m_db);
3932 QString sql = QStringLiteral(R"'(
3933 select
3934 s.month,
3935 s.employee,
3936 e.firstname,
3937 e.surname,
3938 s.days,
3939 s.locked,
3940 s.locked_user,
3941 u.name,
3942 s.work,
3943 s.vacation,
3944 s.sick_leave,
3945 s.compensatory_leave,
3946 s.business_trip,
3947 s.break_time,
3948 s.unpaid_leave,
3949 s.sick_care,
3950 s.paid_obstacle,
3951 s.doctor,
3952 s.afternoon,
3953 s.night,
3954 s.sunday,
3955 s.saturday,
3956 s.holiday,
3957 wc.working_days,
3958 wc.holidays
3959 from attendance.summary s
3960 left join users u on u."user" = s.locked_user
3961 left join attendance.employees e using (employee)
3962 left join attendance.work_calendar wc on wc.period = s.month
3963 where (s.employee = :key1 or :key2 <= 0)
3964 )'");
3965 if (month.isValid()) {
3966 sql += QStringLiteral(" and s.month = :month");
3967 }
3968 sql += QStringLiteral(" order by s.month, e.surname, e.firstname;");
3969 q.prepare(sql);
3970 q.bindValue(":key1", employee);
3971 q.bindValue(":key2", employee);
3972 if (month.isValid()) {
3973 q.bindValue(":month", month);
3974 }
3975 q.exec();
3976 while (q.next()) {
3977 int i = 0;
3978 Dbt::AttendanceSummary x;
3979 x.month = q.value(i++).toDate();
3980 x.employee = q.value(i++).toInt();
3981 x.firstname = q.value(i++).toString();
3982 x.surname = q.value(i++).toString();
3983 x.days = q.value(i++).toInt();
3984 x.locked = q.value(i++).toBool();
3985 x.locked_user = q.value(i++).toInt();
3986 x.locked_user_name = q.value(i++).toString();
3987 x.arrival = q.value(i++).toDouble();
3988 x.vacation = q.value(i++).toDouble();
3989 x.sick_leave = q.value(i++).toDouble();
3990 x.compensatory_leave = q.value(i++).toDouble();
3991 x.business_trip = q.value(i++).toDouble();
3992 x.break_time = q.value(i++).toDouble();
3993 x.unpaid_leave = q.value(i++).toDouble();
3994 x.sick_care = q.value(i++).toDouble();
3995 x.paid_obstacle = q.value(i++).toDouble();
3996 x.doctor = q.value(i++).toDouble();
3997 x.afternoon = q.value(i++).toDouble();
3998 x.night = q.value(i++).toDouble();
3999 x.sunday = q.value(i++).toDouble();
4000 x.saturday = q.value(i++).toDouble();
4001 x.holiday = q.value(i++).toDouble();
4002 x.calendar_working_days = q.value(i++).toInt();
4003 x.calendar_holidays = q.value(i++).toInt();
4004 list << x;
4005 }
4006 return list;
4007}
4008
4009QVariant DatabasePluginPostgres::save(const Dbt::AttendanceSummary& data) {
4010 PDEBUG << data.employee << data.month;
4011 MSqlQuery q(m_db);
4012 q.prepare(R"(select 1 from attendance.summary where employee = :employee and month = :month)");
4013 q.bindValue(":employee", data.employee);
4014 q.bindValue(":month", data.month);
4015 q.exec();
4016 if (q.next()) {
4017 q.prepare(R"'(
4018 update attendance.summary set
4019 days = :days,
4020 work = :arrival,
4021 vacation = :vacation,
4022 sick_leave = :sick_leave,
4023 compensatory_leave = :compensatory_leave,
4024 business_trip = :business_trip,
4025 break_time = :break_time,
4026 unpaid_leave = :unpaid_leave,
4027 sick_care = :sick_care,
4028 paid_obstacle = :paid_obstacle,
4029 doctor = :doctor,
4030 afternoon = :afternoon,
4031 night = :night,
4032 sunday = :sunday,
4033 saturday = :saturday,
4034 holiday = :holiday,
4035 locked = :locked,
4036 locked_user = :locked_user
4037 where employee = :employee and month = :month
4038 )'");
4039 } else {
4040 q.prepare(R"'(
4041 insert into attendance.summary (
4042 employee, month, days, work, vacation, sick_leave,
4043 compensatory_leave, business_trip, break_time, unpaid_leave,
4044 sick_care, paid_obstacle, doctor, afternoon, night,
4045 sunday, saturday, holiday, locked, locked_user
4046 ) values (
4047 :employee, :month, :days, :arrival, :vacation, :sick_leave,
4048 :compensatory_leave, :business_trip, :break_time, :unpaid_leave,
4049 :sick_care, :paid_obstacle, :doctor, :afternoon, :night,
4050 :sunday, :saturday, :holiday, :locked, :locked_user
4051 )
4052 )'");
4053 }
4054 q.bindValue(":days", data.days);
4055 q.bindValue(":arrival", data.arrival);
4056 q.bindValue(":vacation", data.vacation);
4057 q.bindValue(":sick_leave", data.sick_leave);
4058 q.bindValue(":compensatory_leave", data.compensatory_leave);
4059 q.bindValue(":business_trip", data.business_trip);
4060 q.bindValue(":break_time", data.break_time);
4061 q.bindValue(":unpaid_leave", data.unpaid_leave);
4062 q.bindValue(":sick_care", data.sick_care);
4063 q.bindValue(":paid_obstacle", data.paid_obstacle);
4064 q.bindValue(":doctor", data.doctor);
4065 q.bindValue(":afternoon", data.afternoon);
4066 q.bindValue(":night", data.night);
4067 q.bindValue(":sunday", data.sunday);
4068 q.bindValue(":saturday", data.saturday);
4069 q.bindValue(":holiday", data.holiday);
4070 q.bindValue(":locked", data.locked);
4071 q.bindValue(":locked_user", data.locked_user == 0 ? QVariant() : data.locked_user);
4072 q.bindValue(":employee", data.employee);
4073 q.bindValue(":month", data.month);
4074 q.exec();
4075 return QVariant();
4076}
4077
4078QList<Dbt::AttendancePresent> DatabasePluginPostgres::attendancePresent() {
4079 QList<Dbt::AttendancePresent> list;
4080 MSqlQuery q(m_db);
4081 q.prepare(R"'(
4082 select p.employee, p.firstname, p.surname, p.active, p."user",
4083 coalesce(u.login, '') as login,
4084 p.work_hours_mode, p.rounding_interval,
4085 p.saturdays_paid, p.sundays_paid, p.auto_breaks, p.overtime_paid,
4086 p.date, p.event_type, p.present
4087 from attendance.present p
4088 left join users u on u."user" = p."user"
4089 order by p.surname, p.firstname
4090 )'");
4091 q.exec();
4092 while (q.next()) {
4093 int i = 0;
4094 Dbt::AttendancePresent x;
4095 x.employee.employee = q.value(i++).toInt();
4096 x.employee.firstname = q.value(i++).toString();
4097 x.employee.surname = q.value(i++).toString();
4098 x.employee.active = q.value(i++).toBool();
4099 x.employee.user = q.value(i++).toInt();
4100 x.employee.login = q.value(i++).toString();
4101 x.employee.work_hours_mode = q.value(i++).toString();
4102 x.employee.rounding_interval = q.value(i++).toString();
4103 x.employee.saturdays_paid = q.value(i++).toBool();
4104 x.employee.sundays_paid = q.value(i++).toBool();
4105 x.employee.auto_breaks = q.value(i++).toBool();
4106 x.employee.overtime_paid = q.value(i++).toBool();
4107 x.date = q.value(i++).toDateTime();
4108 x.event_type = q.value(i++).toString();
4109 x.present = q.value(i++).toBool();
4110 list << x;
4111 }
4112 return list;
4113}
4114
4115QList<Dbt::AttendanceRecent> DatabasePluginPostgres::attendanceRecent(int employee) {
4116 QList<Dbt::AttendanceRecent> list;
4117 Dbt::UserEmployeeAccess access = canAccessAttendance(employee);
4118 if (!access.can_read) { return list; }
4119 MSqlQuery q(m_db);
4120 q.prepare(R"'(
4121 with
4122 params as (
4123 select
4124 :employee::integer as employee
4125 ),
4126
4127 /* Vybere záznamy z tabulky attendance.events a označí všechny chyby */
4128 events_errors as (
4129 select
4130 e.event,
4131 e.date,
4132 e.event_type,
4133 t.description as event_description,
4134 e.employee,
4135 e.valid,
4136 e.user_edited,
4137 case when e.valid and t.end_state and prev.end_state then 'U' else -- unexpected end
4138 case when e.valid and not t.end_state and not next.end_state then 'M' else -- missing end
4139 case when e.valid and not t.end_state and next.end_state is null then 'M' else -- missing end, last record
4140 null::text
4141 end end end as error
4142
4143 from attendance.events e
4144 left join attendance.event_types t using (event_type)
4145 cross join params p
4146
4147 /* Předchozí záznam */
4148 left join lateral (select *
4149 from attendance.events ep
4150 left join attendance.event_types tp using (event_type)
4151 where e.valid
4152 and ep.valid
4153 and ep.date < e.date
4154 and ep.employee = e.employee
4155 and not tp.passage
4156 order by date desc
4157 limit 1
4158 ) prev on true
4159
4160 /* Následující záznam */
4161 left join lateral (select *
4162 from attendance.events en
4163 left join attendance.event_types tn using (event_type)
4164 where e.valid
4165 and en.valid
4166 and en.date > e.date
4167 and en.employee = e.employee
4168 and not tn.passage
4169 order by date
4170 limit 1
4171 ) next on true
4172
4173 where p.employee = e.employee
4174 and e.date > now() - '1month'::interval
4175 and not t.passage -- Omezení na datové typy, které se týkají docházky, tj. typu "Záčátek" a "Konec"
4176 ),
4177
4178 /* Vyhodí záznamy s chybami "nadbytečný odchod" */
4179 events_fixed1 as (
4180 select event, date, event_type, false as generated, employee, valid, user_edited, error, n.note as note
4181 from events_errors
4182 left join attendance.event_notes n using (event)
4183 where valid
4184 and (error is null or error != 'U')
4185 ),
4186
4187
4188 /* Vybere záznamy s chybou "chybějící odchod", upraví je tak, aby se tvářily jako chybějící záznam */
4189 events_generated_ends as (
4190 select null::integer as event,
4191 case when next.date is null then ef1.date + '8 hours'::interval else next.date - '1 second'::interval end as date,
4192 et.event_type,
4193 true as generated,
4194 ef1.employee,
4195 true as valid,
4196 u."user" as user_edited,
4197 null::text as error,
4198 null::text as note
4199
4200 from events_fixed1 ef1
4201 join (select * from attendance.event_types where end_state limit 1) et on true
4202 join (select * from users where admin order by "user" limit 1) u on true
4203
4204 /* Následující záznam */
4205 left join lateral (select *
4206 from attendance.events ep
4207 left join attendance.event_types tn using (event_type)
4208 where ep.valid
4209 and ep.date > ef1.date
4210 and ep.employee = ef1.employee
4211 and not tn.passage
4212 order by date
4213 limit 1
4214 ) next on true
4215
4216 where error = 'M'
4217
4218 ),
4219
4220 /*
4221 Vytvoří opravený seznam, výstupem jsou vždy perfektně seřazené dvojice BEGIN - END za sebou.
4222 */
4223 events_fixed as (
4224 select * from (
4225 -- vybere všechny bezchybné záznamy
4226 select * from events_fixed1
4227 union all
4228 -- vybere všechny vygenerované ukončovací záznamy
4229 -- a připojí je k těm bezchybnatým
4230 select * from events_generated_ends
4231 ) x
4232 order by x.date
4233 ),
4234
4235 /*
4236 Spojí související dvojice BEGIN - END do jednoho širokého záznamu
4237 */
4238 events_paired as (
4239 select x.*
4240 from (
4241 select
4242 e.employee,
4243 e.event as start_event,
4244 e.date as start_date,
4245 e.event_type as start_event_type,
4246 e.note as start_note,
4247 e.error as start_error,
4248 e.user_edited as start_user_edited,
4249 lead(e.event) over w as end_event,
4250 lead(e.date) over w as end_date,
4251 lead(e.event_type) over w as end_event_type,
4252 lead(e.note) over w as end_note,
4253 lead(e.error) over w as end_error,
4254 lead(e.user_edited) over w as end_user_edited,
4255 lead(e.generated) over w as end_generated
4256 from events_fixed e
4257 where e.valid
4258 window w as (partition by employee order by e.date)
4259 order by e.date
4260 ) x, params p
4261 where x.start_event_type != 'END'
4262 order by start_date
4263 )
4264
4265 select *
4266 from (select *
4267 from events_paired
4268 order by start_date
4269 limit 3
4270 ) y
4271 union all
4272 select x.employee, x.event, x.date, x.event_type, x.note, null, null, null, null, null, null, null, null, null
4273 from (
4274 select distinct on (e.employee) e.*, n.note
4275 from attendance.events e
4276 cross join params p
4277 left join attendance.event_types t using (event_type)
4278 left join attendance.event_notes n using (event)
4279 where p.employee = e.employee
4280 and t.passage
4281 and (e.date > (select max(start_date) from events_paired)
4282 or e.date > (select max(end_date) from events_paired where not end_generated))
4283 order by e.employee, e.date desc
4284 ) x
4285 ;)'");
4286 q.bindValue(":employee", employee);
4287 q.exec();
4288 while (q.next()) {
4289 Dbt::AttendanceRecent x;
4290 int i=0;
4291 x.employee = q.value(i++).toInt();
4292 x.start_event = q.value(i++).toInt();
4293 x.start_date = q.value(i++).toDateTime();
4294 x.start_event_type = q.value(i++).toString();
4295 x.start_note = q.value(i++).toString();
4296 x.start_error = q.value(i++).toString();
4297 x.start_user_edited = q.value(i++).toInt();
4298 x.end_event = q.value(i++).toInt();
4299 x.end_date = q.value(i++).toDateTime();
4300 x.end_event_type = q.value(i++).toString();
4301 x.end_note = q.value(i++).toString();
4302 x.end_error = q.value(i++).toString();
4303 x.end_user_edited = q.value(i++).toInt();
4304 x.end_generated = q.value(i++).toBool();
4305 list << x;
4306 }
4307 return list;
4308}
4309
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:363
QString description
new status description
Definition dbt.h:366
QString status
new status
Definition dbt.h:365
QVariantList recent_status
recent statuses
Definition dbt.h:364