|
@@ -1,4 +1,6 @@
|
1
|
1
|
<window id="navwindow">
|
|
2
|
+ <layout type="horizontal">
|
|
3
|
+ <layout type="vertical">
|
2
|
4
|
<layout type="grid">
|
3
|
5
|
<row>
|
4
|
6
|
<column>
|
|
@@ -74,6 +76,10 @@
|
74
|
76
|
</column>
|
75
|
77
|
</row>
|
76
|
78
|
</layout>
|
|
79
|
+ <stretch />
|
|
80
|
+ </layout>
|
|
81
|
+ <webview id="dashboard" stretch="1" />
|
|
82
|
+ </layout>
|
77
|
83
|
<menu name="Reports" type="reports" src="Reports" />
|
78
|
84
|
<menu name="Database">
|
79
|
85
|
<item id="resetconnection">Forget Connection Details</item>
|
|
@@ -105,10 +111,10 @@
|
105
|
111
|
schedule.clicked.connect(function() {
|
106
|
112
|
createWindow("schedule");
|
107
|
113
|
});
|
108
|
|
- var manual = findChildObject(this, 'manual');
|
109
|
|
- manual.clicked.connect(function() {
|
110
|
|
- createWindow("manualLogEntry");
|
111
|
|
- });
|
|
114
|
+ var manual = findChildObject(this, 'manual');
|
|
115
|
+ manual.clicked.connect(function() {
|
|
116
|
+ createWindow("manualLogEntry");
|
|
117
|
+ });
|
112
|
118
|
var profilehistory = findChildObject(this, 'profilehistory');
|
113
|
119
|
profilehistory.clicked.connect(function() {
|
114
|
120
|
createWindow("profilehistory");
|
|
@@ -346,6 +352,8 @@
|
346
|
352
|
query.exec("CREATE TRIGGER notify_transactions_changed AFTER INSERT OR UPDATE OR DELETE ON purchase FOR EACH STATEMENT EXECUTE PROCEDURE notify_transactions_changed()");
|
347
|
353
|
query.exec("CREATE TRIGGER notify_transactions_changed AFTER INSERT OR UPDATE OR DELETE ON sale FOR EACH STATEMENT EXECUTE PROCEDURE notify_transactions_changed()");
|
348
|
354
|
query.exec("CREATE TRIGGER notify_transactions_changed AFTER INSERT OR UPDATE OR DELETE ON use FOR EACH STATEMENT EXECUTE PROCEDURE notify_transactions_changed()");
|
|
355
|
+ query.exec("CREATE OR REPLACE FUNCTION notify_reminders_changed() RETURNS trigger AS $$ BEGIN NOTIFY RemindersChange; RETURN NULL; END; $$ LANGUAGE plpgsql");
|
|
356
|
+ query.exec("CREATE TRIGGER notify_reminders_changed AFTER INSERT OR UPDATE OR DELETE ON reminders FOR EACH STATEMENT EXECUTE PROCEDURE notify_reminders_changed()");
|
349
|
357
|
|
350
|
358
|
query.exec("UPDATE TypicaFeatures SET version = 8 WHERE feature = 'base-features'");
|
351
|
359
|
query = query.invalidate();
|
|
@@ -443,6 +451,297 @@
|
443
|
451
|
var newUserDialog = new NewTypicaUser();
|
444
|
452
|
newUserDialog.exec();
|
445
|
453
|
});
|
|
454
|
+ var dashboard = findChildObject(this, 'dashboard');
|
|
455
|
+ var refresh = function() {
|
|
456
|
+ var buffer = new QBuffer;
|
|
457
|
+ buffer.open(3);
|
|
458
|
+ var output = new XmlWriter(buffer);
|
|
459
|
+ output.writeStartDocument("1.0");
|
|
460
|
+ output.writeDTD('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg.dtd">');
|
|
461
|
+ output.writeStartElement("html");
|
|
462
|
+ output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
463
|
+ output.writeStartElement("head");
|
|
464
|
+
|
|
465
|
+ output.writeStartElement("link");
|
|
466
|
+ output.writeAttribute("rel", "stylesheet");
|
|
467
|
+ output.writeAttribute("href", QSettings.value("config") + "/Scripts/dashboard.css");
|
|
468
|
+ output.writeEndElement();
|
|
469
|
+
|
|
470
|
+ output.writeEndElement(); // head
|
|
471
|
+ output.writeStartElement("body");
|
|
472
|
+
|
|
473
|
+ output.writeStartElement("div");
|
|
474
|
+ output.writeAttribute("class", "container");
|
|
475
|
+
|
|
476
|
+ var query = new QSqlQuery;
|
|
477
|
+ drawReminders(output, query);
|
|
478
|
+ drawSchedule(output, query);
|
|
479
|
+ drawLatest(output, query);
|
|
480
|
+ drawMostRoasted(output, query);
|
|
481
|
+ drawLeastRoasted(output, query);
|
|
482
|
+ query = query.invalidate();
|
|
483
|
+
|
|
484
|
+ output.writeEndElement(); // End of container
|
|
485
|
+
|
|
486
|
+ output.writeEndElement(); // body
|
|
487
|
+ output.writeEndElement(); // html
|
|
488
|
+ output.writeEndDocument();
|
|
489
|
+
|
|
490
|
+ dashboard.setContent(buffer);
|
|
491
|
+ buffer.close();
|
|
492
|
+ }
|
|
493
|
+ var startCell = function(output, title) {
|
|
494
|
+ output.writeStartElement("div");
|
|
495
|
+ output.writeAttribute("class", "cell");
|
|
496
|
+ output.writeStartElement("div");
|
|
497
|
+ output.writeAttribute("class", "cell-wrapper");
|
|
498
|
+ output.writeStartElement("div");
|
|
499
|
+ output.writeAttribute("class", "cell-title");
|
|
500
|
+ output.writeCharacters(title);
|
|
501
|
+ output.writeEndElement();
|
|
502
|
+ }
|
|
503
|
+ var startStage = function(output) {
|
|
504
|
+ output.writeStartElement("div");
|
|
505
|
+ output.writeAttribute("class", "cell-stage");
|
|
506
|
+ }
|
|
507
|
+ var endStage = function(output) {
|
|
508
|
+ output.writeEndElement();
|
|
509
|
+ }
|
|
510
|
+ var endCell = function(output, summary) {
|
|
511
|
+ if(arguments.length > 1) {
|
|
512
|
+ output.writeStartElement("div");
|
|
513
|
+ output.writeAttribute("class", "cell-notes");
|
|
514
|
+ output.writeCharacters(summary);
|
|
515
|
+ output.writeEndElement();
|
|
516
|
+ }
|
|
517
|
+ output.writeEndElement();
|
|
518
|
+ output.writeEndElement();
|
|
519
|
+ }
|
|
520
|
+ var drawReminders = function(output, query) {
|
|
521
|
+ query.exec("SELECT id, reminder FROM reminders");
|
|
522
|
+ e = new Array();
|
|
523
|
+ while(query.next()) {
|
|
524
|
+ var reminder = JSON.parse(query.value(1));
|
|
525
|
+ reminder.dbid = query.value(0);
|
|
526
|
+ var start_time = "" + reminder.start_year + "-" + reminder.start_month + "-" + reminder.start_day + " " + reminder.start_time;
|
|
527
|
+ if(reminder.condition == "PRODUCTIONWEIGHT") {
|
|
528
|
+ var convert = 1;
|
|
529
|
+ var unittext = TTR("navwindow", " Lb");
|
|
530
|
+ if(reminder.unit == "KG") {
|
|
531
|
+ convert = 2.2;
|
|
532
|
+ unittext = TTR("navwindow", " Kg");
|
|
533
|
+ }
|
|
534
|
+ var dq = new QSqlQuery;
|
|
535
|
+ dq.prepare("SELECT sum(roasted_quantity)/:conversion FROM roasting_log WHERE time > :since");
|
|
536
|
+ dq.bind(":conversion", convert);
|
|
537
|
+ dq.bind(":since", start_time);
|
|
538
|
+ dq.exec();
|
|
539
|
+ dq.next();
|
|
540
|
+ var proportion;
|
|
541
|
+ var remain;
|
|
542
|
+ if(reminder.value == 0 || (reminder.value < Number(dq.value(0)))) {
|
|
543
|
+ proportion = 1;
|
|
544
|
+ } else {
|
|
545
|
+ proportion = Number(dq.value(0)) / reminder.value;
|
|
546
|
+ }
|
|
547
|
+ remain = (reminder.value - Number(dq.value(0))).toFixed(0);
|
|
548
|
+ reminder.completion = proportion;
|
|
549
|
+ reminder.detail = remain + unittext;
|
|
550
|
+ dq = dq.invalidate();
|
|
551
|
+ } else if (reminder.condition == "DAYS") {
|
|
552
|
+ var dq = new QSqlQuery;
|
|
553
|
+ dq.prepare("SELECT 'now'::date - :since::date");
|
|
554
|
+ dq.bind(":since", start_time);
|
|
555
|
+ dq.exec();
|
|
556
|
+ dq.next();
|
|
557
|
+ var proportion;
|
|
558
|
+ var remain;
|
|
559
|
+ if(reminder.value == 0 || (reminder.value < Number(dq.value(0)))) {
|
|
560
|
+ proportion = 1;
|
|
561
|
+ } else {
|
|
562
|
+ proportion = Number(dq.value(0)) / reminder.value;
|
|
563
|
+ }
|
|
564
|
+ remain = reminder.value - Number(dq.value(0));
|
|
565
|
+ reminder.completion = proportion;
|
|
566
|
+ reminder.detail = remain + TTR("navwindow", " Days");
|
|
567
|
+ dq = dq.invalidate();
|
|
568
|
+ } else if (reminder.condition == "PRODUCTIONBATCHES") {
|
|
569
|
+ var dq = new QSqlQuery;
|
|
570
|
+ dq.prepare("SELECT count(1) FROM roasting_log WHERE time > :since");
|
|
571
|
+ dq.bind(":since", start_time);
|
|
572
|
+ dq.exec();
|
|
573
|
+ dq.next();
|
|
574
|
+ var proportion;
|
|
575
|
+ var remain;
|
|
576
|
+ if(reminder.value == 0 || (reminder.value < Number(dq.value(0)))) {
|
|
577
|
+ proportion = 1;
|
|
578
|
+ } else {
|
|
579
|
+ proportion = Number(dq.value(0)) / reminder.value;
|
|
580
|
+ }
|
|
581
|
+ remain = reminder.value - Number(dq.value(0));
|
|
582
|
+ reminder.completion = proportion;
|
|
583
|
+ reminder.detail = remain + TTR("navwindow", " Batches");
|
|
584
|
+ dq = dq.invalidate();
|
|
585
|
+ } else if (reminder.condition == "PRODUCTIONHOURS") {
|
|
586
|
+ var dq = new QSqlQuery;
|
|
587
|
+ dq.prepare("SELECT extract(epoch FROM (SELECT sum(duration) FROM roasting_log WHERE time > :since) / 3600)");
|
|
588
|
+ dq.bind(":since", start_time);
|
|
589
|
+ dq.exec();
|
|
590
|
+ dq.next();
|
|
591
|
+ var proportion;
|
|
592
|
+ var remain;
|
|
593
|
+ if(reminder.value == 0 || (reminder.value < Number(dq.value(0)))) {
|
|
594
|
+ proportion = 1;
|
|
595
|
+ } else {
|
|
596
|
+ proportion = Number(dq.value(0)) / reminder.value;
|
|
597
|
+ }
|
|
598
|
+ remain = reminder.value - Number(dq.value(0));
|
|
599
|
+ reminder.completion = proportion;
|
|
600
|
+ reminder.detail = remain.toFixed(1) + TTR("navwindow", " Hours");
|
|
601
|
+ dq = dq.invalidate();
|
|
602
|
+ }
|
|
603
|
+ e[reminder.dbid] = reminder;
|
|
604
|
+ }
|
|
605
|
+ var s = e.filter(function(n) {
|
|
606
|
+ return n.hasOwnProperty("completion")}).sort(function(a, b) {
|
|
607
|
+ return b.completion - a.completion});
|
|
608
|
+ var c = 0;
|
|
609
|
+ var so = 0;
|
|
610
|
+ s.forEach(function(item) {
|
|
611
|
+ if(item.completion >= 1) {
|
|
612
|
+ c += 1;
|
|
613
|
+ } else if (item.completion >= 0.8) {
|
|
614
|
+ so += 1;
|
|
615
|
+ }
|
|
616
|
+ });
|
|
617
|
+ if(c > 0 || s > 0) {
|
|
618
|
+ output.writeStartElement("a");
|
|
619
|
+ output.writeAttribute("href", "typica://script/reminders");
|
|
620
|
+ startCell(output, TTR("navwindow", "Reminders"));
|
|
621
|
+ startStage(output);
|
|
622
|
+ var summaryText;
|
|
623
|
+ if(c > 0) {
|
|
624
|
+ summaryText = "" + c + TTR("navwindow", " reminders due");
|
|
625
|
+ } else {
|
|
626
|
+ summaryText = "" + s + TTR("navwindow", " reminders due soon");
|
|
627
|
+ }
|
|
628
|
+ output.writeTextElement("p", s[0].title);
|
|
629
|
+ output.writeTextElement("p", Math.floor(s[0].completion * 100) + "%");
|
|
630
|
+ output.writeTextElement("p", s[0].detail);
|
|
631
|
+ endStage(output);
|
|
632
|
+ endCell(output, summaryText);
|
|
633
|
+ output.writeEndElement();
|
|
634
|
+ }
|
|
635
|
+ }
|
|
636
|
+ var kilounit = TTR("navwindow", "Kg");
|
|
637
|
+ var poundunit = TTR("navwindow", "Lb");
|
|
638
|
+ var unitData = function() {
|
|
639
|
+ var retval = new Object;
|
|
640
|
+ if(Number(QSettings.value("script/report_unit")) == 0) {
|
|
641
|
+ retval.conversion = 2.2;
|
|
642
|
+ retval.unittext = "Kg";
|
|
643
|
+ } else {
|
|
644
|
+ retval.conversion = 1;
|
|
645
|
+ retval.unittext = "Lb";
|
|
646
|
+ }
|
|
647
|
+ return retval;
|
|
648
|
+ }
|
|
649
|
+ var drawSchedule = function(output, query) {
|
|
650
|
+ var c = 0;
|
|
651
|
+ var u = unitData();
|
|
652
|
+ query.prepare("SELECT (SELECT name FROM items WHERE id = (data#>>'{roasted}')::numeric), (data#>>'{green_weight}')::numeric/:conversion FROM scheduled_roasts WHERE machine IS NULL");
|
|
653
|
+ query.bind(":conversion", u.conversion);
|
|
654
|
+ query.exec();
|
|
655
|
+ if(query.next()) {
|
|
656
|
+ output.writeStartElement("a");
|
|
657
|
+ output.writeAttribute("href", "typica://script/schedule");
|
|
658
|
+ startCell(output, TTR("navwindow", "Scheduled Roasts"));
|
|
659
|
+ startStage(output);
|
|
660
|
+ do {
|
|
661
|
+ c += 1;
|
|
662
|
+ output.writeTextElement("p", query.value(1) + u.unittext + " " + query.value(0));
|
|
663
|
+ } while(query.next());
|
|
664
|
+ endStage(output);
|
|
665
|
+ endCell(output, "" + c + TTR("navwindow", " batches scheduled"));
|
|
666
|
+ output.writeEndElement();
|
|
667
|
+ }
|
|
668
|
+ }
|
|
669
|
+ var drawLatest = function(output, query) {
|
|
670
|
+ var u = unitData();
|
|
671
|
+ query.prepare("SELECT time, roasted_quantity/:conversion, (SELECT name FROM items WHERE id = roasted_id), approval FROM roasting_log ORDER BY time DESC LIMIT 5");
|
|
672
|
+ query.bind(":conversion", u.conversion);
|
|
673
|
+ query.exec();
|
|
674
|
+ if(query.next()) {
|
|
675
|
+ output.writeStartElement("a");
|
|
676
|
+ output.writeAttribute("href", "typica://script/log");
|
|
677
|
+ startCell(output, TTR("navwindow", "Latest Batches"));
|
|
678
|
+ startStage(output);
|
|
679
|
+ do {
|
|
680
|
+ output.writeStartElement("p");
|
|
681
|
+ if(query.value(3) == "false") {
|
|
682
|
+ output.writeAttribute("style", "color: #FF0000;");
|
|
683
|
+ }
|
|
684
|
+ output.writeCharacters(query.value(0).replace("T", " ") + " " + query.value(1) + u.unittext + " " + query.value(2));
|
|
685
|
+ output.writeEndElement();
|
|
686
|
+ } while(query.next());
|
|
687
|
+ endStage(output);
|
|
688
|
+ endCell(output);
|
|
689
|
+ output.writeEndElement();
|
|
690
|
+ }
|
|
691
|
+ }
|
|
692
|
+ var drawMostRoasted = function(output, query) {
|
|
693
|
+ var u = unitData();
|
|
694
|
+ query.prepare("SELECT (sum(roasted_quantity)/:conversion) AS sum, (SELECT name FROM items WHERE id = roasted_id) FROM roasting_log WHERE time > 'now'::date - '28 days'::interval AND approval = true GROUP BY roasted_id ORDER BY sum DESC LIMIT 5");
|
|
695
|
+ query.bind(":conversion", u.conversion);
|
|
696
|
+ query.exec();
|
|
697
|
+ if(query.next()) {
|
|
698
|
+ startCell(output, TTR("navwindow", "Most Roasted Items (Last 28 Days)"));
|
|
699
|
+ startStage(output);
|
|
700
|
+ do {
|
|
701
|
+ output.writeTextElement("p", query.value(0) + u.unittext + " " + query.value(1));
|
|
702
|
+ } while(query.next());
|
|
703
|
+ endStage(output);
|
|
704
|
+ endCell(output);
|
|
705
|
+ }
|
|
706
|
+ }
|
|
707
|
+ var drawLeastRoasted = function(output, query) {
|
|
708
|
+ var u = unitData();
|
|
709
|
+ query.prepare("SELECT (sum(roasted_quantity)/:conversion) AS sum, (SELECT name FROM items WHERE id = roasted_id) FROM roasting_log WHERE time > 'now'::date - '28 days'::interval AND approval = true GROUP BY roasted_id ORDER BY sum ASC LIMIT 5");
|
|
710
|
+ query.bind(":conversion", u.conversion);
|
|
711
|
+ query.exec();
|
|
712
|
+ if(query.next()) {
|
|
713
|
+ startCell(output, TTR("navwindow", "Least Roasted Items (Last 28 Days)"));
|
|
714
|
+ startStage(output);
|
|
715
|
+ do {
|
|
716
|
+ output.writeTextElement("p", query.value(0) + u.unittext + " " + query.value(1));
|
|
717
|
+ } while(query.next());
|
|
718
|
+ endStage(output);
|
|
719
|
+ endCell(output);
|
|
720
|
+ }
|
|
721
|
+ }
|
|
722
|
+ refresh();
|
|
723
|
+ dashboard.scriptLinkClicked.connect(function(url) {
|
|
724
|
+ if(url == "reminders") {
|
|
725
|
+ createReport("reminders.xml");
|
|
726
|
+ } else if(url == "log") {
|
|
727
|
+ createReport("historyreport.xml");
|
|
728
|
+ } else if(url == "schedule") {
|
|
729
|
+ createWindow("schedule");
|
|
730
|
+ }
|
|
731
|
+ });
|
|
732
|
+
|
|
733
|
+ var remindernotification = Application.subscribe("reminderschange");
|
|
734
|
+ remindernotification.notify.connect(function() {
|
|
735
|
+ refresh();
|
|
736
|
+ });
|
|
737
|
+ var schedulenotification = Application.subscribe("scheduledroastschange");
|
|
738
|
+ schedulenotification.notify.connect(function() {
|
|
739
|
+ refresh();
|
|
740
|
+ });
|
|
741
|
+ var lognotification = Application.subscribe("roastinglogchange");
|
|
742
|
+ lognotification.notify.connect(function() {
|
|
743
|
+ refresh();
|
|
744
|
+ });
|
446
|
745
|
]]>
|
447
|
746
|
</program>
|
448
|
747
|
</window>
|