Browse Source

Merge branch 'release-1.7'

Neal Wilson 8 years ago
parent
commit
0485b77027
100 changed files with 47626 additions and 37270 deletions
  1. 1
    1
      README
  2. 191
    148
      config/Reports/auco.xml
  3. 167
    116
      config/Reports/chart.xml
  4. 164
    143
      config/Reports/cogr.xml
  5. 83
    28
      config/Reports/dailyproductiondetail.xml
  6. 25
    13
      config/Reports/fypurchase.xml
  7. 28
    13
      config/Reports/greensales.xml
  8. 320
    0
      config/Reports/historyreport.xml
  9. 24
    25
      config/Reports/invchange.xml
  10. 30
    30
      config/Reports/inventory.xml
  11. 156
    0
      config/Reports/invoices.xml
  12. 42
    36
      config/Reports/itemtransactions.xml
  13. 117
    106
      config/Reports/monthcompare.xml
  14. 186
    0
      config/Reports/productionsummary.xml
  15. 186
    0
      config/Reports/reminders.xml
  16. 110
    61
      config/Reports/rwacp.xml
  17. 68
    0
      config/Scripts/reminders.css
  18. 23
    0
      config/Scripts/reminders.js
  19. BIN
      config/Translations/config.de.qm
  20. 2810
    0
      config/Translations/config.de.ts
  21. 2309
    0
      config/Translations/config.ts
  22. 0
    178
      config/Windows/batchdetails.xml
  23. 336
    325
      config/Windows/batchdetailsnew.xml
  24. 56
    40
      config/Windows/editbatchdetails.xml
  25. 6
    4
      config/Windows/editfee.xml
  26. 210
    0
      config/Windows/editreminder.xml
  27. 6
    6
      config/Windows/export.xml
  28. 0
    276
      config/Windows/externalroaster.xml
  29. 76
    89
      config/Windows/greeninventory.xml
  30. 3
    3
      config/Windows/greensales.xml
  31. 0
    47
      config/Windows/history.xml
  32. 1
    1
      config/Windows/importprofiles.xml
  33. 4
    1
      config/Windows/invoiceinfo.xml
  34. 0
    28
      config/Windows/invoicelist.xml
  35. 202
    198
      config/Windows/navigation.xml
  36. 484
    275
      config/Windows/newbatch.xml
  37. 42
    9
      config/Windows/newsamplebatch.xml
  38. 8
    8
      config/Windows/offline.xml
  39. 6
    6
      config/Windows/print.xml
  40. 723
    705
      config/Windows/productionroaster.xml
  41. 4
    4
      config/Windows/roastmanager.xml
  42. 92
    0
      config/Windows/roastspec.xml
  43. 1
    1
      config/Windows/setsampleparameters.xml
  44. 24
    24
      config/config.xml
  45. 0
    1
      docs/documentation.html
  46. 4
    8
      docs/documentation/part2.html
  47. 79
    0
      docs/documentation/windowreference/databaseconnection.html
  48. BIN
      docs/documentation/windowreference/databaseconnection.png
  49. BIN
      docs/documentation/windowreference/databasemenu.png
  50. BIN
      docs/documentation/windowreference/inventoryreports.png
  51. 202
    0
      docs/documentation/windowreference/logging.html
  52. BIN
      docs/documentation/windowreference/logging.png
  53. BIN
      docs/documentation/windowreference/loggingbatch.png
  54. BIN
      docs/documentation/windowreference/loggingfile.png
  55. BIN
      docs/documentation/windowreference/logginggraph.png
  56. BIN
      docs/documentation/windowreference/logginglog.png
  57. 156
    0
      docs/documentation/windowreference/navigation.html
  58. BIN
      docs/documentation/windowreference/navigation.png
  59. 38
    0
      docs/documentation/windowreference/openconfigurationfile.html
  60. BIN
      docs/documentation/windowreference/openconfigurationfile.png
  61. BIN
      docs/documentation/windowreference/productionreports.png
  62. BIN
      docs/documentation/windowreference/purchasereports.png
  63. BIN
      docs/documentation/windowreference/reportsmenu.png
  64. BIN
      docs/documentation/windowreference/salesreport.png
  65. 1396
    0
      src/Translations/Typica.ts
  66. BIN
      src/Translations/Typica_de.qm
  67. 1396
    0
      src/Translations/Typica_de.ts
  68. 3
    0
      src/Typica.pro
  69. 5
    5
      src/abouttypica.cpp
  70. 2
    2
      src/abouttypica.h
  71. 20
    20
      src/daterangeselector.cpp
  72. 4
    4
      src/daterangeselector.h
  73. 1
    1
      src/daterangeselector.w
  74. 2
    2
      src/draglabel.cpp
  75. 2
    2
      src/draglabel.h
  76. 7
    7
      src/helpmenu.cpp
  77. 2
    2
      src/helpmenu.h
  78. 69
    0
      src/licensewindow.cpp
  79. 24
    0
      src/licensewindow.h
  80. 6
    3
      src/moc_helpmenu.cpp
  81. 15
    6
      src/moc_typica.cpp
  82. 31810
    31572
      src/qrc_resources.cpp
  83. 6
    6
      src/resources/Info.plist
  84. 7
    83
      src/resources/html/about.html
  85. 6
    6
      src/scale.cpp
  86. 2
    2
      src/scale.h
  87. 2697
    2453
      src/typica.cpp
  88. 6
    6
      src/typica.rc
  89. 274
    25
      src/typica.w
  90. 6
    6
      src/units.cpp
  91. 2
    2
      src/units.h
  92. 5
    5
      src/webelement.cpp
  93. 2
    2
      src/webelement.h
  94. 11
    11
      src/webview.cpp
  95. 2
    2
      src/webview.h
  96. 11
    31
      web/output/download-mac-latest.html
  97. 11
    31
      web/output/download-windows-latest.html
  98. 3
    3
      web/output/downloads.html
  99. 8
    13
      web/output/index.html
  100. 0
    0
      web/src/pages/download-mac-latest.m4

+ 1
- 1
README View File

@@ -6,7 +6,7 @@ Project web site: http://www.randomfield.com/programs/typica/
6 6
 
7 7
 Typica is free software released under the MIT license as follows:
8 8
 
9
-Copyright 2007-2015 Neal Evan Wilson
9
+Copyright 2007-2016 Neal Evan Wilson
10 10
 
11 11
 Permission is hereby granteed, free of charge, to any person obtaining a copy
12 12
 of this software and associated documentation files (the "Software"), to deal

+ 191
- 148
config/Reports/auco.xml View File

@@ -2,45 +2,64 @@
2 2
 	<reporttitle>Production:->Average Use and Cost by Origin</reporttitle>
3 3
     <layout type="vertical">
4 4
         <layout type="horizontal">
5
+            <daterange id="dates" initial="23" /><!-- Lifetime -->
5 6
             <label>Sort Order:</label>
6 7
             <sqldrop id="sort" />
7
-			<label>Weight Unit:</label>
8
-			<sqldrop id="unit" />
8
+            <label>Weight Unit:</label>
9
+            <sqldrop id="unit" />
9 10
             <stretch />
10 11
         </layout>
11 12
         <webview id="report" />
12 13
     </layout>
13 14
     <menu name="File">
14
-        <item id="print" shortcut="Ctrl+P">Print</item>
15
+        <item id="print" shortcut="Ctrl+P">Print...</item>
15 16
     </menu>
16 17
     <program>
17 18
         <![CDATA[
18
-            this.windowTitle = "Typica - Average Use and Cost by Origin";
19
+            this.windowTitle = TTR("useandcostreport", "Typica - Average Use and Cost by Origin");
19 20
             var report = findChildObject(this, 'report');
20 21
             var printMenu = findChildObject(this, 'print');
21 22
             printMenu.triggered.connect(function() {
22 23
                 report.print();
23 24
             });
24 25
             var sortBox = findChildObject(this, 'sort');
25
-            sortBox.addItem("Origin A-Z");
26
-            sortBox.addItem("Origin Z-A");
27
-            sortBox.addItem("Avg. Rate Ascending");
28
-            sortBox.addItem("Avg. Rate Descending");
29
-            sortBox.addItem("Avg. Cost Ascending");
30
-            sortBox.addItem("Avg. Cost Descending");
26
+            sortBox.addItem(TTR("useandcostreport","Origin A-Z"));
27
+            sortBox.addItem(TTR("useandcostreport", "Origin Z-A"));
28
+            sortBox.addItem(TTR("useandcostreport", "Avg. Rate Ascending"));
29
+            sortBox.addItem(TTR("useandcostreport", "Avg. Rate Descending"));
30
+            sortBox.addItem(TTR("useandcostreport", "Avg. Cost Ascending"));
31
+            sortBox.addItem(TTR("useandcostreport", "Avg. Cost Descending"));
31 32
             sortBox.currentIndex = QSettings.value("auco_sort", 0);
32
-			var unitBox = findChildObject(this, 'unit');
33
-			unitBox.addItem("Kg");
34
-			unitBox.addItem("Lb");
35
-			unitBox.currentIndex = QSettings.value("script/report_unit", 1);
36
-			unitBox['currentIndexChanged(int)'].connect(function() {
37
-				QSettings.setValue("script/report_unit", unitBox.currentIndex);
38
-				refresh();
39
-			});
40
-			var rowData = new Array();
41
-			var rowIndex;
33
+            var unitBox = findChildObject(this, 'unit');
34
+            unitBox.addItem(TTR("useandcostreport", "Kg"));
35
+            unitBox.addItem(TTR("useandcostreport", "Lb"));
36
+            unitBox.currentIndex = QSettings.value("script/report_unit", 1);
37
+            unitBox['currentIndexChanged(int)'].connect(function() {
38
+                QSettings.setValue("script/report_unit", unitBox.currentIndex);
39
+                refresh();
40
+            });
41
+            var dateSelect = findChildObject(this, 'dates');
42
+            var dateQuery = new QSqlQuery;
43
+            dateQuery.exec("SELECT time::date FROM transactions WHERE time = (SELECT min(time) FROM transactions) OR time = (SELECT max(time) FROM transactions) ORDER BY time ASC");
44
+            dateQuery.next();
45
+            var lifetimeStartDate = dateQuery.value(0);
46
+            var lifetimeEndDate;
47
+            if(dateQuery.next()) {
48
+                lifetimeEndDate = dateQuery.value(0);
49
+            } else {
50
+                lifetimeEndDate = lifetimeStartDate;
51
+            }
52
+            dateSelect.setLifetimeRange(lifetimeStartDate, lifetimeEndDate);
53
+            dateQuery = dateQuery.invalidate();
54
+            dateSelect.rangeUpdated.connect(refresh);
55
+            var rowData = new Array();
56
+            var rowIndex;
42 57
             function refresh() {
43
-				rowIndex = 0;
58
+                rowData.length = 0;
59
+                var dateRange = dateSelect.currentRange();
60
+                var startDate = dateRange[0];
61
+                var endDate = dateRange[dateRange.length - 1];
62
+                rowIndex = 0;
44 63
                 var buffer = new QBuffer;
45 64
                 buffer.open(3);
46 65
                 var output = new XmlWriter(buffer);
@@ -49,133 +68,136 @@
49 68
                 output.writeStartElement("html");
50 69
                 output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
51 70
                 output.writeStartElement("head");
52
-                output.writeTextElement("title", "Recent Use and Cost by Origin");
71
+                output.writeTextElement("title", TTR("useandcostreport", "Recent Use and Cost by Origin"));
53 72
                 output.writeEndElement();
54 73
                 output.writeStartElement("body");
55
-                output.writeTextElement("h1", "Average Use and Cost by Origin");
56
-				switch(unitBox.currentIndex)
57
-				{
58
-					case 0:
59
-						output.writeTextElement("p", "This is a report of average rate of use in kilograms per day and cost of unroasted coffee.");
60
-						break;
61
-					case 1:
62
-						output.writeTextElement("p", "This is a report of average rate of use in pounds per day and cost of unroasted coffee.");
63
-						break;
64
-				}
74
+                output.writeTextElement("h1", TTR("useandcostreport", "Average Use and Cost by Origin ") + startDate + " - " + endDate);
75
+                switch(unitBox.currentIndex)
76
+                {
77
+                    case 0:
78
+                        output.writeTextElement("p", TTR("useandcostreport", "This is a report of average rate of use in kilograms per day and cost of unroasted coffee."));
79
+                            break;
80
+                    case 1:
81
+                        output.writeTextElement("p", TTR("useandcostreport", "This is a report of average rate of use in pounds per day and cost of unroasted coffee."));
82
+                    break;
83
+                }
65 84
                 output.writeStartElement("table");
66 85
                 output.writeAttribute("rules", "groups");
67 86
                 output.writeAttribute("cellpadding", "3px");
68 87
                 output.writeStartElement("thead");
69 88
                 output.writeStartElement("tr");
70 89
                 output.writeStartElement("th");
71
-                output.writeAttribute("colspan", "8");
72
-                output.writeCharacters("Regular Coffees");
90
+                output.writeAttribute("colspan", "9");
91
+                output.writeCharacters(TTR("useandcostreport", "Regular Coffees"));
73 92
                 output.writeEndElement();
74 93
                 output.writeEndElement();
75 94
                 output.writeStartElement("tr");
76
-                output.writeTextElement("th", "Origin");
77
-                output.writeTextElement("th", "Avg. Rate");
78
-                output.writeTextElement("th", "Avg. Cost");
79
-				output.writeTextElement("th", "Last Cost");
80
-				output.writeTextElement("th", "Last Purchase Date");
81
-				output.writeTextElement("th", "Bag Size (min)");
82
-				output.writeTextElement("th", "Bag Size (max)");
83
-				output.writeTextElement("th", "Bag Size (mean)");
95
+                output.writeTextElement("th", TTR("useandcostreport", "Origin"));
96
+                output.writeTextElement("th", TTR("useandcostreport", "Avg. Rate"));
97
+                output.writeTextElement("th", TTR("useandcostreport", "Avg. Cost"));
98
+                output.writeTextElement("th", TTR("useandcostreport", "Last Cost"));
99
+                output.writeTextElement("th", TTR("useandcostreport", "Last Purchase Date"));
100
+                output.writeTextElement("th", TTR("useandcostreport", "Bag Size (min)"));
101
+                output.writeTextElement("th", TTR("useandcostreport", "Bag Size (max)"));
102
+                output.writeTextElement("th", TTR("useandcostreport", "Bag Size (mean)"));
103
+                output.writeTextElement("th", TTR("useandcostreport", "Purchases"));
84 104
                 output.writeEndElement();
85 105
                 output.writeEndElement();
86 106
                 output.writeStartElement("tbody");
87 107
                 var query = new QSqlQuery();
88
-				var conversion = 1;
89
-				if(unitBox.currentIndex == 0) {
90
-					conversion = 2.2;
91
-				}
92
-				var orderClause;
93
-				switch(sortBox.currentIndex)
94
-				{
95
-					case 0:
96
-						orderClause = "origin ASC";
97
-						break;
98
-					case 1:
99
-						orderClause = "origin DESC";
100
-						break;
101
-					case 2:
102
-						orderClause = "rate ASC";
103
-						break;
104
-					case 3:
105
-						orderClause = "rate DESC";
106
-						break;
107
-					case 4:
108
-						orderClause = "cost ASC";
109
-						break;
110
-					case 5:
111
-						orderClause = "cost DESC";
112
-						break;
113
-				}
114
-				query.prepare("SELECT DISTINCT origin, (avg(rate)/:conversion)::numeric(10,2) AS rate, (SELECT avg(cost)*:conversion2 FROM purchase WHERE item IN (SELECT id FROM regular_coffees WHERE origin = coffee_history.origin))::numeric(10,2) AS cost, (SELECT avg(cost)*:conversion3 FROM purchase WHERE item IN (SELECT id FROM regular_coffees WHERE origin = coffee_history.origin) AND time = (SELECT max(time) FROM purchase WHERE item IN (SELECT id FROM regular_coffees WHERE origin = coffee_history.origin))), (SELECT max(time)::date FROM purchase WHERE item IN (SELECT id FROM regular_coffees WHERE origin = coffee_history.origin)), (SELECT min(conversion)/:conversion4 FROM lb_bag_conversion WHERE item IN (SELECT id FROM regular_coffees WHERE origin = coffee_history.origin))::numeric(10,2) AS minbag, (SELECT max(conversion)/:conversion5 FROM lb_bag_conversion WHERE item IN (SELECT id FROM regular_coffees WHERE origin = coffee_history.origin))::numeric(10,2) AS maxbag, (SELECT avg(conversion)/:conversion6 FROM lb_bag_conversion WHERE item IN (SELECT id FROM regular_coffees WHERE origin = coffee_history.origin))::numeric(10,2) AS meanbag FROM coffee_history WHERE id IN (SELECT id FROM regular_coffees) GROUP BY origin ORDER BY " + orderClause);
115
-				query.bind(":conversion", conversion);
116
-				query.bind(":conversion2", conversion);
117
-				query.bind(":conversion3", conversion);
118
-				query.bind(":conversion4", conversion);
119
-				query.bind(":conversion5", conversion);
120
-				query.bind(":conversion6", conversion);
121
-				query.exec();
108
+                var conversion = 1;
109
+                if(unitBox.currentIndex == 0) {
110
+                    conversion = 2.2;
111
+                }
112
+                var orderClause;
113
+                switch(sortBox.currentIndex)
114
+                {
115
+                    case 0:
116
+                        orderClause = "origin ASC";
117
+                        break;
118
+                    case 1:
119
+                        orderClause = "origin DESC";
120
+                        break;
121
+                    case 2:
122
+                        orderClause = "rate ASC";
123
+                        break;
124
+                    case 3:
125
+                        orderClause = "rate DESC";
126
+                        break;
127
+                    case 4:
128
+                        orderClause = "cost ASC";
129
+                        break;
130
+                    case 5:
131
+                        orderClause = "cost DESC";
132
+                        break;
133
+                }
134
+                query.prepare("WITH q AS (SELECT id, origin, rate/:c1 AS rate, (SELECT cost*:c2 FROM purchase WHERE item = id) AS cost, (SELECT min(time) FROM purchase WHERE item = id) AS purchase_time, (SELECT conversion/:c3 FROM lb_bag_conversion WHERE item = id) AS bag_weight FROM coffee_history WHERE id IN (SELECT id FROM regular_coffees) AND id IN (SELECT item FROM transactions WHERE time >= :startDate AND time < :endDate::date + interval '1 day')) SELECT DISTINCT origin, avg(rate)::numeric(10,2) AS rate, avg(cost)::numeric(10,2) AS cost, (SELECT (cost*:c4)::numeric(10,2) FROM purchase WHERE item = max(q.id)) AS last_cost, max(purchase_time)::date AS last_purchase, min(bag_weight)::numeric(10,2) AS min_weight, max(bag_weight)::numeric(10,2) AS max_weight, avg(bag_weight)::numeric(10,2) AS mean_weight, count(1) AS n FROM q GROUP BY origin ORDER BY " + orderClause);
135
+                query.bind(":c1", conversion);
136
+                query.bind(":c2", conversion);
137
+                query.bind(":c3", conversion);
138
+                query.bind(":c4", conversion);
139
+                query.bind(":startDate", startDate);
140
+                query.bind(":endDate", endDate);
141
+                query.exec();
122 142
                 while(query.next())
123 143
                 {
124 144
                     output.writeStartElement("tr");
125
-					output.writeAttribute("id", "r" + rowIndex);
126
-					output.writeStartElement("td");
127
-					output.writeStartElement("a");
128
-					output.writeAttribute("href", "typica://script/r" + rowIndex);
129
-					rowIndex++;
130
-					rowData.push(query.value(0));
131
-					output.writeCharacters(query.value(0));
132
-					output.writeEndElement();
133
-					output.writeEndElement();
145
+                    output.writeAttribute("id", "r" + rowIndex);
146
+                    output.writeStartElement("td");
147
+                    output.writeStartElement("a");
148
+                    output.writeAttribute("href", "typica://script/r" + rowIndex);
149
+                    rowIndex++;
150
+                    rowData.push(query.value(0));
151
+                    output.writeCharacters(query.value(0));
152
+                    output.writeEndElement();
153
+                    output.writeEndElement();
134 154
                     output.writeTextElement("td", query.value(1));
135 155
                     output.writeTextElement("td", query.value(2));
136
-					output.writeTextElement("td", query.value(3));
137
-					output.writeTextElement("td", query.value(4));
138
-					output.writeTextElement("td", query.value(5));
139
-					output.writeTextElement("td", query.value(6));
140
-					output.writeTextElement("td", query.value(7));
156
+                    output.writeTextElement("td", query.value(3));
157
+                    output.writeTextElement("td", query.value(4));
158
+                    output.writeTextElement("td", query.value(5));
159
+                    output.writeTextElement("td", query.value(6));
160
+                    output.writeTextElement("td", query.value(7));
161
+                    output.writeTextElement("td", query.value(8));
141 162
                     output.writeEndElement();
142 163
                 }
143 164
                 output.writeStartElement("tr");
144 165
                 output.writeStartElement("th");
145
-                output.writeAttribute("colspan", "8");
146
-                output.writeCharacters("Decaffeinated Coffees");
166
+                output.writeAttribute("colspan", "9");
167
+                output.writeCharacters(TTR("useandcostreport", "Decaffeinated Coffees"));
147 168
                 output.writeEndElement();
148 169
                 output.writeEndElement();
149
-				query.prepare("SELECT DISTINCT origin, (avg(rate)/:conversion)::numeric(10,2) AS rate, (SELECT avg(cost)*:conversion2 FROM purchase WHERE item IN (SELECT id FROM decaf_coffees WHERE origin = coffee_history.origin))::numeric(10,2) AS cost, (SELECT avg(cost)*:conversion3 FROM purchase WHERE item IN (SELECT id FROM decaf_coffees WHERE origin = coffee_history.origin) AND time = (SELECT max(time) FROM purchase WHERE item IN (SELECT id FROM decaf_coffees WHERE origin = coffee_history.origin))), (SELECT max(time)::date FROM purchase WHERE item IN (SELECT id FROM decaf_coffees WHERE origin = coffee_history.origin)), (SELECT min(conversion)/:conversion4 FROM lb_bag_conversion WHERE item IN (SELECT id FROM decaf_coffees WHERE origin = coffee_history.origin))::numeric(10,2) AS minbag, (SELECT max(conversion)/:conversion5 FROM lb_bag_conversion WHERE item IN (SELECT id FROM decaf_coffees WHERE origin = coffee_history.origin))::numeric(10,2) AS maxbag, (SELECT avg(conversion)/:conversion6 FROM lb_bag_conversion WHERE item IN (SELECT id FROM decaf_coffees WHERE origin = coffee_history.origin))::numeric(10,2) AS meanbag FROM coffee_history WHERE id IN (SELECT id FROM decaf_coffees) GROUP BY origin ORDER BY " + orderClause);
150
-				query.bind(":conversion", conversion);
151
-				query.bind(":conversion2", conversion);
152
-				query.bind(":conversion3", conversion);
153
-				query.bind(":conversion4", conversion);
154
-				query.bind(":conversion5", conversion);
155
-				query.bind(":conversion6", conversion);
156
-				query.exec();
170
+                query.prepare("WITH q AS (SELECT id, origin, rate/:c1 AS rate, (SELECT cost*:c2 FROM purchase WHERE item = id) AS cost, (SELECT min(time) FROM purchase WHERE item = id) AS purchase_time, (SELECT conversion/:c3 FROM lb_bag_conversion WHERE item = id) AS bag_weight FROM coffee_history WHERE id IN (SELECT id FROM decaf_coffees) AND id IN (SELECT item FROM transactions WHERE time >= :startDate AND time < :endDate::date + interval '1 day')) SELECT DISTINCT origin, avg(rate)::numeric(10,2) AS rate, avg(cost)::numeric(10,2) AS cost, (SELECT (cost*:c4)::numeric(10,2) FROM purchase WHERE item = max(q.id)) AS last_cost, max(purchase_time)::date AS last_purchase, min(bag_weight)::numeric(10,2) AS min_weight, max(bag_weight)::numeric(10,2) AS max_weight, avg(bag_weight)::numeric(10,2) AS mean_weight, count(1) AS n FROM q GROUP BY origin ORDER BY " + orderClause);
171
+                query.bind(":c1", conversion);
172
+                query.bind(":c2", conversion);
173
+                query.bind(":c3", conversion);
174
+                query.bind(":c4", conversion);
175
+                query.bind(":startDate", startDate);
176
+                query.bind(":endDate", endDate);
177
+                query.exec();
157 178
                 while(query.next())
158 179
                 {
159 180
                     output.writeStartElement("tr");
160
-					output.writeAttribute("id", "d" + rowIndex);
161
-					output.writeStartElement("td");
162
-					output.writeStartElement("a");
163
-					output.writeAttribute("href", "typica://script/d" + rowIndex);
164
-					rowIndex++;
165
-					rowData.push(query.value(0));
166
-					output.writeCharacters(query.value(0));
167
-					output.writeEndElement();
168
-					output.writeEndElement();
181
+                    output.writeAttribute("id", "d" + rowIndex);
182
+                    output.writeStartElement("td");
183
+                    output.writeStartElement("a");
184
+                    output.writeAttribute("href", "typica://script/d" + rowIndex);
185
+                    rowIndex++;
186
+                    rowData.push(query.value(0));
187
+                    output.writeCharacters(query.value(0));
188
+                    output.writeEndElement();
189
+                    output.writeEndElement();
169 190
                     output.writeTextElement("td", query.value(1));
170 191
                     output.writeTextElement("td", query.value(2));
171
-					output.writeTextElement("td", query.value(3));
172
-					output.writeTextElement("td", query.value(4));
173
-					output.writeTextElement("td", query.value(5));
174
-					output.writeTextElement("td", query.value(6));
175
-					output.writeTextElement("td", query.value(7));
192
+                    output.writeTextElement("td", query.value(3));
193
+                    output.writeTextElement("td", query.value(4));
194
+                    output.writeTextElement("td", query.value(5));
195
+                    output.writeTextElement("td", query.value(6));
196
+                    output.writeTextElement("td", query.value(7));
197
+                    output.writeTextElement("td", query.value(8));
176 198
                     output.writeEndElement();
177 199
                 }
178
-				query = query.invalidate();
200
+                query = query.invalidate();
179 201
                 output.writeEndElement();
180 202
                 output.writeEndElement();
181 203
                 output.writeEndElement();
@@ -189,39 +211,60 @@
189 211
                 QSettings.setValue("auco_sort", sortBox.currentIndex);
190 212
                 refresh();
191 213
             });
192
-			report.scriptLinkClicked.connect(function(url) {
193
-				var element = new WebElement(report.findFirstElement("#" + url));
194
-				var regular = url[0] == 'r';
195
-				var index = url.slice(1, url.length);
196
-				var tableref;
197
-				if(regular) {
198
-					tableref = "regular_coffees";
199
-				} else {
200
-					tableref = "decaf_coffees";
201
-				}
202
-				var origin = rowData[Number(url.slice(1, url.length))];
203
-				var details = '<tr><td /><td colspan="6"><table><tr><th>Id</th><th>Name</th><th>Rate</th><th>Inventory</th><th>First Use</th><th>Last Use</th></tr>';
204
-				var query = new QSqlQuery();
205
-				query.prepare("SELECT id, name, (rate/:conversion1)::numeric(12,3), (stock/:conversion2)::numeric(12,3), (SELECT min(time)::date FROM use WHERE item = id) AS first_use, (SELECT max(time)::date FROM use WHERE item = id) AS last_use FROM coffee_history WHERE origin = :origin AND id IN (SELECT id FROM " + tableref + ") ORDER BY first_use DESC");
206
-				var conversion = 1;
207
-				if(unitBox.currentIndex == 0) {
208
-					conversion = 2.2;
209
-				}
210
-				query.bind(":conversion1", conversion);
211
-				query.bind(":conversion2", conversion);
212
-				query.bind(":origin", origin);
213
-				query.exec();
214
-				while(query.next()) {
215
-					details += "<tr>";
216
-					for(var i = 0; i < 6; i++) {
217
-						details += "<td>" + query.value(i) + "</td>";
218
-					}
219
-					details += "</tr>";
220
-				}
221
-				query = query.invalidate();
222
-				details += "</table></td></tr>";
223
-				element.appendOutside(details);
224
-			});
214
+            report.scriptLinkClicked.connect(function(url) {
215
+                if(url[0] == 'i') {
216
+                    url = url.slice(1, url.length);
217
+                    var itemReport = createReport("itemtransactions.xml");
218
+                    var sIB = findChildObject(itemReport, 'item');
219
+                    sIB.currentIndex = sIB.findData(url);
220
+                    return;
221
+                }
222
+                var element = new WebElement(report.findFirstElement("#" + url));
223
+                var regular = url[0] == 'r';
224
+                var index = url.slice(1, url.length);
225
+                var tableref;
226
+                if(regular) {
227
+                    tableref = "regular_coffees";
228
+                } else {
229
+                    tableref = "decaf_coffees";
230
+                }
231
+                var origin = rowData[Number(url.slice(1, url.length))];
232
+                var details = '<tr><td /><td colspan="6"><table><tr><th>' +
233
+                                    TTR("useandcostreport", "Id") + '</th><th>' +
234
+                                    TTR("useandcostreport", "Name") + '</th><th>' +
235
+                                    TTR("useandcostreport", "Rate") + '</th><th>' +
236
+                                    TTR("useandcostreport", "Cost") + '</th><th>' +
237
+                                    TTR("useandcostreport", "Inventory") + '</th><th>' +
238
+                                    TTR("useandcostreport", "First Use") + '</th><th>' +
239
+                                    TTR("useandcostreport", "Last Use") + '</th></tr>';
240
+                var query = new QSqlQuery();
241
+                query.prepare("SELECT id, name, (rate/:conversion1)::numeric(12,3), (SELECT (cost*:conversion2)::numeric(12,2) FROM purchase WHERE item = id), (stock/:conversion3)::numeric(12,3), (SELECT min(time)::date FROM use WHERE item = id) AS first_use, (SELECT max(time)::date FROM use WHERE item = id) AS last_use FROM coffee_history WHERE origin = :origin AND id IN (SELECT id FROM " + tableref + ") AND id IN (SELECT item FROM transactions WHERE time >= :startDate AND time < :endDate::date + interval '1 day') ORDER BY first_use DESC");
242
+                var conversion = 1;
243
+                if(unitBox.currentIndex == 0) {
244
+                    conversion = 2.2;
245
+                }
246
+                var dateRange = dateSelect.currentRange();
247
+                var startDate = dateRange[0];
248
+                var endDate = dateRange[dateRange.length - 1];
249
+                query.bind(":conversion1", conversion);
250
+                query.bind(":conversion2", conversion);
251
+                query.bind(":conversion3", conversion);
252
+                query.bind(":origin", origin);
253
+                query.bind(":startDate", startDate);
254
+                query.bind(":endDate", endDate);
255
+                query.exec();
256
+                while(query.next()) {
257
+                    details += "<tr>";
258
+                    details += '<td><a href="typica://script/i' + query.value(0) + '">' + query.value(0) + "</a></td>";
259
+                    for(var i = 1; i < 7; i++) {
260
+                        details += "<td>" + query.value(i) + "</td>";
261
+                    }
262
+                    details += "</tr>";
263
+                }
264
+                query = query.invalidate();
265
+                details += "</table></td></tr>";
266
+                element.appendOutside(details);
267
+            });
225 268
         ]]>
226 269
     </program>
227 270
 </window>

+ 167
- 116
config/Reports/chart.xml View File

@@ -2,42 +2,64 @@
2 2
 	<reporttitle>Production:->Previous Year Production Comparison</reporttitle>
3 3
     <layout type="vertical">
4 4
         <layout type="horizontal">
5
-			<daterange id="dates" initial="19" /><!-- Current Year to Date -->
6
-            <label>Days to Average</label>
5
+            <label>Batch Type:</label>
6
+            <sqldrop id="batchtype" />
7
+            <label>Approval:</label>
8
+            <sqldrop id="approval" />
9
+            <daterange id="dates" initial="19" /><!-- Current Year to Date -->
10
+            <label>Days to Average:</label>
7 11
             <line validator="integer" id="days">7</line>
8
-			<label>Weight Unit:</label>
9
-			<sqldrop id="unit" />
12
+            <label>Weight Unit:</label>
13
+            <sqldrop id="unit" />
10 14
             <stretch />
11 15
         </layout>
12 16
         <webview id="report" />
13 17
     </layout>
14 18
     <menu name="File">
15
-        <item id="print" shortcut="Ctrl+P">Print</item>
19
+        <item id="print" shortcut="Ctrl+P">Print...</item>
16 20
     </menu>
17 21
     <program>
18 22
         <![CDATA[
19
-            this.windowTitle = "Typica - Previous Year Production Comparison";
20
-			var dateSelect = findChildObject(this, 'dates');
21
-			dateSelect.removeIndex(23); // Remove Lifetime range
23
+            this.windowTitle = TTR("pytdprodcomp", "Typica - Previous Year Production Comparison");
24
+            var dateSelect = findChildObject(this, 'dates');
25
+            dateSelect.removeIndex(23); // Remove Lifetime range
22 26
             var view = findChildObject(this, 'report');
23 27
             var printMenu = findChildObject(this, 'print');
24 28
             printMenu.triggered.connect(function() {
25 29
                 view.print();
26 30
             });
27 31
             var avgField = findChildObject(this, 'days');
28
-			var unitBox = findChildObject(this, 'unit');
29
-			unitBox.addItem("Kg");
30
-			unitBox.addItem("Lb");
31
-			unitBox.currentIndex = QSettings.value("script/report_unit", 1);
32
-			unitBox['currentIndexChanged(int)'].connect(function() {
33
-				QSettings.setValue("script/report_unit", unitBox.currentIndex);
34
-				refresh();
35
-			});
32
+            var unitBox = findChildObject(this, 'unit');
33
+            unitBox.addItem(TTR("pytdprodcomp", "Kg"));
34
+            unitBox.addItem(TTR("pytdprodcomp", "Lb"));
35
+            unitBox.currentIndex = QSettings.value("script/report_unit", 1);
36
+            unitBox['currentIndexChanged(int)'].connect(function() {
37
+                QSettings.setValue("script/report_unit", unitBox.currentIndex);
38
+                refresh();
39
+            });
40
+            var batchType = findChildObject(this, 'batchtype');
41
+            batchType.addItem(TTR("pytdprodcomp", "Any"));
42
+            batchType.addItem(TTR("pytdprodcomp", "Production Roasts"));
43
+            batchType.addItem(TTR("pytdprodcomp", "Sample Roasts"));
44
+            batchType.currentIndex = QSettings.value("script/batchtypefilter", 1);
45
+            batchType['currentIndexChanged(int)'].connect(function() {
46
+                QSettings.setValue("script/batchtypefilter", batchType.currentIndex);
47
+                refresh();
48
+            });
49
+            var approval = findChildObject(this, 'approval');
50
+            approval.addItem(TTR("pytdprodcomp", "Any"));
51
+            approval.addItem(TTR("pytdprodcomp", "Approved"));
52
+            approval.addItem(TTR("pytdprodcomp", "Not Approved"));
53
+            approval.currentIndex = QSettings.value("script/approvalfilter", 1);
54
+            approval['currentIndexChanged(int)'].connect(function() {
55
+                QSettings.setValue("script/approvalfilter", approval.currentIndex);
56
+                refresh();
57
+            });
36 58
             function refresh() {
37
-				var conversion = 1;
38
-				if(unitBox.currentIndex == 0) {
39
-					conversion = 2.2;
40
-				}
59
+                var conversion = 1;
60
+                if(unitBox.currentIndex == 0) {
61
+                    conversion = 2.2;
62
+                }
41 63
                 var buffer = new QBuffer;
42 64
                 buffer.open(3);
43 65
                 var output = new XmlWriter(buffer);
@@ -46,11 +68,11 @@
46 68
                 output.writeStartElement("html");
47 69
                 output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
48 70
                 output.writeStartElement("head");
49
-                output.writeTextElement("title", "Previous Year Production Comparison");
71
+                output.writeTextElement("title", TTR("pytdprodcomp", "Previous Year Production Comparison"));
50 72
                 output.writeEndElement();
51 73
                 output.writeStartElement("body");
52
-                output.writeTextElement("h1", "Previous Year Production Comparison");
53
-                output.writeTextElement("p", "This report provides an itemized and overall comparison of roasted coffee production for the dates specified with those dates in the previous year. A chart of this data along with percent change and rolling average of the percent change is also produced.");
74
+                output.writeTextElement("h1", TTR("pytdprodcomp", "Previous Year Production Comparison"));
75
+                output.writeTextElement("p", TTR("pytdprodcomp", "This report provides an itemized and overall comparison of roasted coffee production for the dates specified with those dates in the previous year. A chart of this data along with percent change and rolling average of the percent change is also produced."));
54 76
                 output.writeStartElement("table");
55 77
                 output.writeAttribute("style", "page-break-after:auto;");
56 78
                 output.writeAttribute("rules", "groups");
@@ -58,34 +80,58 @@
58 80
                 output.writeStartElement("thead");
59 81
                 output.writeStartElement("tr");
60 82
                 output.writeTextElement("th", "Coffee");
61
-				switch(unitBox.currentIndex) {
62
-					case 0:
63
-						output.writeTextElement("th", "Previous (Kg)");
64
-						output.writeTextElement("th", "Current (Kg)");
65
-						output.writeTextElement("th", "Change (Kg)");
66
-						break;
67
-					case 1:
68
-						output.writeTextElement("th", "Previous (Lb)");
69
-						output.writeTextElement("th", "Current (Lb)");
70
-						output.writeTextElement("th", "Change (Lb)");
71
-						break;
72
-				}
83
+                switch(unitBox.currentIndex) {
84
+                    case 0:
85
+                        output.writeTextElement("th", TTR("pytdprodcomp", "Previous (Kg)"));
86
+                        output.writeTextElement("th", TTR("pytdprodcomp", "Current (Kg)"));
87
+                        output.writeTextElement("th", TTR("pytdprodcomp", "Change (Kg)"));
88
+                        break;
89
+                    case 1:
90
+                        output.writeTextElement("th", TTR("pytdprodcomp", "Previous (Lb)"));
91
+                        output.writeTextElement("th", TTR("pytdprodcomp", "Current (Lb)"));
92
+                        output.writeTextElement("th", TTR("pytdprodcomp", "Change (Lb)"));
93
+                        break;
94
+                }
73 95
                 output.writeEndElement();
74 96
                 output.writeEndElement();
75 97
                 output.writeStartElement("tbody");
76 98
                 var query = new QSqlQuery();
77 99
                 query.exec("START TRANSACTION");
78
-				var dateRange = dateSelect.currentRange();
79
-				var curStartDate = "'"+dateRange[0]+"'";
80
-				var curEndDate = "'"+dateRange[dateRange.length - 1]+"'";
100
+                var dateRange = dateSelect.currentRange();
101
+                var curStartDate = "'"+dateRange[0]+"'";
102
+                var curEndDate = "'"+dateRange[dateRange.length - 1]+"'";
81 103
                 query.exec("SELECT "+curStartDate+"::date - interval '1 year', "+curEndDate+"::date - interval '1 year' + interval '1 day', "+curEndDate+"::date + interval '1 day'");
82
-				query.next();
104
+                query.next();
83 105
                 curEndDate = "'"+query.value(2)+"'";
84 106
                 var prevStartDate = "'"+query.value(0)+"'";
85 107
                 var prevEndDate = "'"+query.value(1)+"'";
86
-                var q = "CREATE TEMPORARY TABLE previous ON COMMIT DROP AS SELECT roasted_id, sum(roasted_quantity) AS p FROM roasting_log WHERE time > "+prevStartDate+" AND time < "+prevEndDate+" AND roasted_id IS NOT NULL GROUP BY roasted_id";
108
+                var transaction_filter;
109
+                var approval_filter;
110
+                switch(batchType.currentIndex) {
111
+                    case 0:
112
+                        transaction_filter = "";
113
+                        break;
114
+                    case 1:
115
+                        transaction_filter = " AND transaction_type = 'ROAST'";
116
+                        break;
117
+                    case 2:
118
+                        transaction_filter = " AND transaction_type = 'SAMPLEROAST'";
119
+                        break;
120
+                }
121
+                switch(approval.currentIndex) {
122
+                    case 0:
123
+                        approval_filter = "";
124
+                        break;
125
+                    case 1:
126
+                        approval_filter = " AND approval = true";
127
+                        break;
128
+                    case 2:
129
+                        approval_filter = " AND approval = false";
130
+                        break;
131
+                }
132
+                var q = "CREATE TEMPORARY TABLE previous ON COMMIT DROP AS SELECT roasted_id, sum(roasted_quantity) AS p FROM roasting_log WHERE time > "+prevStartDate+" AND time < "+prevEndDate+" AND roasted_id IS NOT NULL" + transaction_filter + approval_filter + " GROUP BY roasted_id";
87 133
                 query.exec(q);
88
-                q = "CREATE TEMPORARY TABLE current ON COMMIT DROP AS SELECT roasted_id, sum(roasted_quantity) AS c FROM roasting_log WHERE time > "+curStartDate+" AND time < "+curEndDate+" AND roasted_id IS NOT NULL GROUP BY roasted_id";
134
+                q = "CREATE TEMPORARY TABLE current ON COMMIT DROP AS SELECT roasted_id, sum(roasted_quantity) AS c FROM roasting_log WHERE time > "+curStartDate+" AND time < "+curEndDate+" AND roasted_id IS NOT NULL" + transaction_filter + approval_filter + " GROUP BY roasted_id";
89 135
                 query.exec(q);
90 136
                 query.exec("INSERT INTO previous SELECT roasted_id, 0 FROM current WHERE roasted_id NOT IN (SELECT roasted_id FROM previous)");
91 137
                 query.exec("INSERT INTO current SELECT roasted_id, 0 FROM previous WHERE roasted_id NOT IN (SELECT roasted_id FROM current)");
@@ -95,24 +141,24 @@
95 141
                 {
96 142
                     output.writeStartElement("tr");
97 143
                     output.writeTextElement("td", query.value(0));
98
-					output.writeTextElement("td", (query.value(1) / conversion).toFixed(2));
99
-					output.writeTextElement("td", (query.value(2) / conversion).toFixed(2));
100
-					output.writeTextElement("td", (query.value(3) / conversion).toFixed(2));
144
+                    output.writeTextElement("td", (query.value(1) / conversion).toFixed(2));
145
+                    output.writeTextElement("td", (query.value(2) / conversion).toFixed(2));
146
+                    output.writeTextElement("td", (query.value(3) / conversion).toFixed(2));
101 147
                     output.writeEndElement();
102 148
                 }
103 149
                 output.writeEndElement();
104 150
                 output.writeStartElement("tfoot");
105
-                output.writeTextElement("th", "Totals");
151
+                output.writeTextElement("th", TTR("pytdprodcomp", "Totals"));
106 152
                 query.exec("SELECT sum(p), sum(c), sum(c-p) FROM comp");
107 153
                 query.next();
108
-				output.writeTextElement("td", (query.value(0) / conversion).toFixed(2));
109
-				output.writeTextElement("td", (query.value(1) / conversion).toFixed(2));
110
-				output.writeTextElement("td", (query.value(2) / conversion).toFixed(2));
154
+                output.writeTextElement("td", (query.value(0) / conversion).toFixed(2));
155
+                output.writeTextElement("td", (query.value(1) / conversion).toFixed(2));
156
+                output.writeTextElement("td", (query.value(2) / conversion).toFixed(2));
111 157
                 output.writeEndElement();
112 158
                 query.exec("ABORT");
113 159
                 output.writeEndElement();
114
-				output.writeStartElement("p");
115
-				output.writeAttribute("style", "page-break-inside: avoid");
160
+                output.writeStartElement("p");
161
+                output.writeAttribute("style", "page-break-inside: avoid");
116 162
                 output.writeStartElement("svg");
117 163
                 output.writeAttribute("xmlns", "http://www.w3.org/2000/svg");
118 164
                 output.writeAttribute("width", "7.5in");
@@ -158,21 +204,21 @@
158 204
                     query.exec(q);
159 205
                     query.next();
160 206
                     dates[i] = query.value(0);
161
-                    q = "SELECT sum(roasted_quantity) FROM roasting_log WHERE time > "+curStartDate+" AND time < '"+dates[i]+"'";
207
+                    q = "SELECT sum(roasted_quantity) FROM roasting_log WHERE time > "+curStartDate+" AND time < '"+dates[i]+"'" + transaction_filter + approval_filter;
162 208
                     query.exec(q);
163 209
                     if(query.next())
164 210
                     {
165
-                        curpounds[i] = query.value(0);
211
+                        curpounds[i] = Number(query.value(0));
166 212
                     }
167 213
                     else
168 214
                     {
169 215
                         curpounds[i] = 0;
170 216
                     }
171
-                    q = "SELECT sum(roasted_quantity) FROM roasting_log WHERE time > "+curStartDate+"::date - '1 year'::interval AND time < '"+dates[i]+"'::date - '1 year'::interval";
217
+                    q = "SELECT sum(roasted_quantity) FROM roasting_log WHERE time > "+curStartDate+"::date - '1 year'::interval AND time < '"+dates[i]+"'::date - '1 year'::interval" + transaction_filter + approval_filter;
172 218
                     query.exec(q);
173 219
                     if(query.next())
174 220
                     {
175
-                        prevpounds[i] = query.value(0);
221
+                        prevpounds[i] = Number(query.value(0));
176 222
                     }
177 223
                     else
178 224
                     {
@@ -204,23 +250,23 @@
204 250
                         avgchange[i] = sum / parseInt(avgField.text);
205 251
                     }
206 252
                 }
207
-				// Calculate the domain of the primary y axis.
253
+                // Calculate the domain of the primary y axis.
208 254
                 var maxy1 = 0;
209
-				var increment = 100; // Starting increment is 100 Lb.
210
-				if(unitBox.currentIndex == 0) {
211
-					increment = 110; // Starting increment is 50 Kg if Kg is requested.
212
-				}
255
+                    var increment = 100; // Starting increment is 100 Lb.
256
+                    if(unitBox.currentIndex == 0) {
257
+                        increment = 110; // Starting increment is 50 Kg if Kg is requested.
258
+                    }
213 259
                 while(maxy1 < Math.max(curpounds[days - 1], prevpounds[days - 1]))
214 260
                 {
215
-					maxy1 += increment;
261
+                    maxy1 += increment;
262
+                }
263
+                // Calculate the number of grid lines and loosen spacing if there are too many.
264
+                var divisions = maxy1 / increment;
265
+                while(divisions > 10) {
266
+                    increment *= 2;
267
+                    divisions = maxy1 / increment;
216 268
                 }
217
-				// Calculate the number of grid lines and loosen spacing if there are too many.
218
-				var divisions = maxy1 / increment;
219
-				while(divisions > 10) {
220
-					increment *= 2;
221
-					divisions = maxy1 / increment;
222
-				}
223
-				// Draw y axis grid lines.
269
+                // Draw y axis grid lines.
224 270
                 var pos = 0;
225 271
                 while(pos <= maxy1)
226 272
                 {
@@ -231,7 +277,7 @@
231 277
                     output.writeAttribute("y2", (450/maxy1)*pos);
232 278
                     output.writeAttribute("style", "stroke:rgb(0,0,0);stroke-width:1;");
233 279
                     output.writeEndElement();
234
-					pos += increment;
280
+                    pos += increment;
235 281
                 }
236 282
                 var n = Math.min.apply(Math, change);
237 283
                 var m = Math.max.apply(Math, change);
@@ -334,19 +380,24 @@
334 380
                     output.writeAttribute("x", "0");
335 381
                     output.writeAttribute("y", -((450/maxy1)*i)+5);
336 382
                     output.writeAttribute("font-size", "12");
337
-					switch(unitBox.currentIndex) {
338
-						case 0:
339
-							output.writeCharacters((i / 2.2).toFixed(0));
340
-							break;
341
-						case 1:
342
-						default:
343
-							output.writeCharacters(i);
344
-							break;
345
-					}
383
+                    switch(unitBox.currentIndex) {
384
+                        case 0:
385
+                            output.writeCharacters((i / 2.2).toFixed(0));
386
+                            break;
387
+                        case 1:
388
+                        default:
389
+                            output.writeCharacters(i);
390
+                            break;
391
+                    }
346 392
                     output.writeEndElement();
347 393
                     i += increment;
348 394
                 }
349 395
                 i = miny2;
396
+                var stepSize = 0.1;
397
+                while((maxy2 - miny2) / stepSize > 20)
398
+                {
399
+                    stepSize += 0.05;
400
+                }
350 401
                 while(i <= maxy2)
351 402
                 {
352 403
                     output.writeStartElement("text");
@@ -355,7 +406,7 @@
355 406
                     output.writeAttribute("font-size", "12");
356 407
                     output.writeCharacters(Number(i*100).toFixed(0)+"%");
357 408
                     output.writeEndElement();
358
-                    i += 0.1;
409
+                    i += stepSize;
359 410
                 }
360 411
                 i = 0;
361 412
                 while(i <= days-1)
@@ -373,40 +424,40 @@
373 424
                     switch(parts[1])
374 425
                     {
375 426
                         case '01':
376
-                            ds = "January ";
427
+                            ds = TTR("pytdprodcomp", "January ");
377 428
                             break;
378 429
                         case '02':
379
-                            ds = "February ";
430
+                            ds = TTR("pytdprodcomp", "February ");
380 431
                             break;
381 432
                         case '03':
382
-                            ds = "March ";
433
+                            ds = TTR("pytdprodcomp", "March ");
383 434
                             break;
384 435
                         case '04':
385
-                            ds = "April ";
436
+                            ds = TTR("pytdprodcomp", "April ");
386 437
                             break;
387 438
                         case '05':
388
-                            ds = "May ";
439
+                            ds = TTR("pytdprodcomp", "May ");
389 440
                             break;
390 441
                         case '06':
391
-                            ds = "June ";
442
+                            ds = TTR("pytdprodcomp", "June ");
392 443
                             break;
393 444
                         case '07':
394
-                            ds = "July ";
445
+                            ds = TTR("pytdprodcomp", "July ");
395 446
                             break;
396 447
                         case '08':
397
-                            ds = "August ";
448
+                            ds = TTR("pytdprodcomp", "August ");
398 449
                             break;
399 450
                         case '09':
400
-                            ds = "September ";
451
+                            ds = TTR("pytdprodcomp", "September ");
401 452
                             break;
402 453
                         case '10':
403
-                            ds = "October ";
454
+                            ds = TTR("pytdprodcomp", "October ");
404 455
                             break;
405 456
                         case '11':
406
-                            ds = "November ";
457
+                            ds = TTR("pytdprodcomp", "November ");
407 458
                             break;
408 459
                         case '12':
409
-                            ds = "December ";
460
+                            ds = TTR("pytdprodcomp", "December ");
410 461
                             break;
411 462
                     }
412 463
                     ds = ds + Number(parts[2]);
@@ -422,7 +473,7 @@
422 473
                         i = days - 1;
423 474
                     }
424 475
                 }
425
-				query = query.invalidate();
476
+                query = query.invalidate();
426 477
                 output.writeStartElement("rect");
427 478
                 output.writeAttribute("fill", "rgb(0,0,0)");
428 479
                 output.writeAttribute("x", "45");
@@ -434,14 +485,14 @@
434 485
                 output.writeAttribute("x", "75");
435 486
                 output.writeAttribute("y", "120");
436 487
                 output.writeAttribute("font-size", "12");
437
-				switch(unitBox.currentIndex) {
438
-					case 0:
439
-						output.writeCharacters("Previous Year Kg");
440
-						break;
441
-					case 1:
442
-						output.writeCharacters("Previous Year Lb");
443
-						break;
444
-				}
488
+                switch(unitBox.currentIndex) {
489
+                    case 0:
490
+                        output.writeCharacters(TTR("pytdprodcomp", "Previous Year Kg"));
491
+                        break;
492
+                    case 1:
493
+                        output.writeCharacters(TTR("pytdprodcomp", "Previous Year Lb"));
494
+                        break;
495
+                }
445 496
                 output.writeEndElement();
446 497
                 output.writeStartElement("rect");
447 498
                 output.writeAttribute("fill", "rgb(255,0,0)");
@@ -454,14 +505,14 @@
454 505
                 output.writeAttribute("x", "225");
455 506
                 output.writeAttribute("y", "120");
456 507
                 output.writeAttribute("font-size", "12");
457
-				switch(unitBox.currentIndex) {
458
-					case 0:
459
-						output.writeCharacters("Current Year Kg");
460
-						break;
461
-					case 1:
462
-						output.writeCharacters("Current Year Lb");
463
-						break;
464
-				}
508
+                switch(unitBox.currentIndex) {
509
+                    case 0:
510
+                        output.writeCharacters(TTR("pytdprodcomp", "Current Year Kg"));
511
+                        break;
512
+                    case 1:
513
+                        output.writeCharacters(TTR("pytdprodcomp", "Current Year Lb"));
514
+                        break;
515
+                }
465 516
                 output.writeEndElement();
466 517
                 output.writeStartElement("rect");
467 518
                 output.writeAttribute("fill", "rgb(0,255,0)");
@@ -474,7 +525,7 @@
474 525
                 output.writeAttribute("x", "375");
475 526
                 output.writeAttribute("y", "120");
476 527
                 output.writeAttribute("font-size", "12");
477
-                output.writeCharacters("% Change");
528
+                output.writeCharacters(TTR("pytdprodcomp", "% Change"));
478 529
                 output.writeEndElement();
479 530
                 output.writeStartElement("rect");
480 531
                 output.writeAttribute("fill", "rgb(0,0,255)");
@@ -487,24 +538,24 @@
487 538
                 output.writeAttribute("x", "525");
488 539
                 output.writeAttribute("y", "120");
489 540
                 output.writeAttribute("font-size", "12");
490
-                output.writeCharacters("Average % Change");
541
+                output.writeCharacters(TTR("pytdprodcomp", "Average % Change"));
542
+                output.writeEndElement();
491 543
                 output.writeEndElement();
492 544
                 output.writeEndElement();
493 545
                 output.writeEndElement();
494 546
                 output.writeEndElement();
495 547
                 output.writeEndElement();
496
-				output.writeEndElement();
497 548
                 output.writeEndDocument();
498 549
                 view.setContent(buffer);
499 550
                 buffer.close();
500 551
             }
501 552
             refresh();
502
-			dateSelect.rangeUpdated.connect(function() {
503
-				refresh();
504
-			});
505
-			avgField.editingFinished.connect(function() {
506
-				refresh();
507
-			});
553
+            dateSelect.rangeUpdated.connect(function() {
554
+                refresh();
555
+            });
556
+            avgField.editingFinished.connect(function() {
557
+                refresh();
558
+            });
508 559
         ]]>
509 560
     </program>
510 561
 </window>

+ 164
- 143
config/Reports/cogr.xml View File

@@ -1,151 +1,172 @@
1 1
 <window id="greencost">
2
-	<reporttitle>Production:->Cost of Green Coffee for Roasted Coffee</reporttitle>
3
-	<layout type="vertical">
4
-		<webview id="report" />
5
-	</layout>
6
-	<menu name="File">
7
-		<item id="print" shortcut="Ctrl+P">Print</item>
8
-	</menu>
9
-	<program>
10
-		<![CDATA[
11
-			this.windowTitle = "Typica - Cost of Green Coffee for Roasted Coffee";
12
-			var report = findChildObject(this, 'report');
13
-			var printMenu = findChildObject(this, 'print');
14
-			printMenu.triggered.connect(function() {
15
-				report.print();
16
-			});
17
-			function refresh() {
18
-				var buffer = new QBuffer;
19
-				buffer.open(3);
20
-				var output = new XmlWriter(buffer);
21
-				output.writeStartDocument("1.0");
22
-				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">');
2
+    <reporttitle>Production:->Cost of Green Coffee for Roasted Coffee</reporttitle>	<layout type="vertical">
3
+        <layout type="horizontal">
4
+            <label>Weight Unit:</label>
5
+            <sqldrop id="unit" />
6
+            <stretch />
7
+        </layout>
8
+        <webview id="report" />
9
+    </layout>
10
+    <menu name="File">
11
+        <item id="print" shortcut="Ctrl+P">Print...</item>
12
+    </menu>
13
+    <program>
14
+        <![CDATA[
15
+            this.windowTitle = TTR("greencost", "Typica - Cost of Green Coffee for Roasted Coffee");
16
+            var report = findChildObject(this, 'report');
17
+            var printMenu = findChildObject(this, 'print');
18
+            printMenu.triggered.connect(function() {
19
+                report.print();
20
+            });
21
+            var unitBox = findChildObject(this, 'unit');
22
+            unitBox.addItem(TTR("greencost", "Kg"));
23
+            unitBox.addItem(TTR("greencost", "Lb"));
24
+            unitBox.currentIndex = QSettings.value("script/report_unit", 1);
25
+            unitBox['currentIndexChanged(int)'].connect(function() {
26
+                QSettings.setValue("script/report_unit", unitBox.currentIndex);
27
+                refresh();
28
+            });
29
+            function refresh() {
30
+                var conversion = 1;
31
+                if(unitBox.currentIndex == 0) {
32
+                    conversion = 2.2;
33
+                }
34
+                var buffer = new QBuffer;
35
+                buffer.open(3);
36
+                var output = new XmlWriter(buffer);
37
+                output.writeStartDocument("1.0");
38
+                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">');
23 39
                 output.writeStartElement("html");
24 40
                 output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
25 41
                 output.writeStartElement("head");
26
-                output.writeTextElement("title", "Cost of Green Coffee for Roasted Coffee");
42
+                output.writeTextElement("title", TTR("greencost", "Cost of Green Coffee for Roasted Coffee"));
27 43
                 output.writeEndElement();
28 44
                 output.writeStartElement("body");
29
-                output.writeTextElement("h1", "Cost of Green Coffee for Roasted Coffee");
30
-				var query = new QSqlQuery();
31
-				query.exec("SELECT item, (SELECT name FROM items WHERE id = item) AS name FROM current_items ORDER BY name");
32
-				var roastedItems = new Array();
33
-				var roastedNames = new Array();
34
-				while(query.next()) {
35
-					roastedItems.push(query.value(0));
36
-					roastedNames.push(query.value(1));
37
-				}
38
-				var recipes = new Array();
39
-				var recipeQuantities = new Array();
40
-				query.prepare("SELECT unroasted_id, unroasted_quantity FROM roasting_log WHERE roasted_id = :item AND time = (SELECT max(time) FROM roasting_log WHERE roasted_id = :item2)");
41
-				for(var i = 0; i < roastedItems.length; i++) {
42
-					query.bind("item", roastedItems[i]);
43
-					query.bind("item2", roastedItems[i]);
44
-					query.exec();
45
-					if(query.next()) {
46
-						recipes.push(query.value(0));
47
-						recipeQuantities.push(query.value(1));
48
-					} else {
49
-						recipes.push("{-1}");
50
-						recipeQuantities.push("{-1}");
51
-					}
52
-				}
53
-				query.prepare("SELECT min(unroasted_total_quantity / roasted_quantity), max(unroasted_total_quantity / roasted_quantity), avg(unroasted_total_quantity / roasted_quantity) FROM roasting_log WHERE roasted_id = :ri AND unroasted_id = :gi AND unroasted_total_quantity > 0 AND roasted_quantity > 0 AND approval = true");
54
-				var mins = new Array();
55
-				var maxes = new Array();
56
-				var means = new Array();
57
-				for(var i = 0; i < roastedItems.length; i++) {
58
-					if(recipes[i] == "{-1}") {
59
-						mins.push("undefined");
60
-						maxes.push("undefined");
61
-						means.push("undefined");
62
-					} else {
63
-						query.bind(":ri", Number(roastedItems[i]));
64
-						query.bind(":gi", recipes[i]);
65
-						query.exec();
66
-						if(query.next()) {
67
-							mins.push(query.value(0));
68
-							maxes.push(query.value(1));
69
-							means.push(query.value(2));
70
-						} else {
71
-							print("Error 2");
72
-						}
73
-					}
74
-				}
75
-				var proportionalCosts = new Array();
76
-				query.prepare("SELECT cost * :proportion FROM purchase WHERE item = :id");
77
-				for(var i = 0; i < roastedItems.length; i++) {
78
-					if(recipes[i] == "{-1}") {
79
-						proportionalCosts.push("undefined");
80
-					} else {
81
-						greens = sqlToArray(recipes[i]);
82
-						weights = sqlToArray(recipeQuantities[i]);
83
-						proportions = new Array();
84
-						quantitySum = weights.reduce(function(p, c) {
85
-							return p + c;
86
-						});
87
-						for(var j = 0; j < weights.length; j++) {
88
-							proportions.push(weights[j]/quantitySum);
89
-						}
90
-						partialSum = 0;
91
-						for(var j = 0; j < greens.length; j++) {
92
-							query.bind(":proportion", proportions[j]);
93
-							query.bind(":id", greens[j]);
94
-							query.exec();
95
-							if(query.next()) {
96
-								partialSum += query.value(0);
97
-							} else {
98
-								print("Error 3");
99
-							}
100
-						}
101
-						proportionalCosts.push(partialSum);
102
-					}
103
-				}
104
-				query = query.invalidate();
105
-				var minCosts = new Array();
106
-				var maxCosts = new Array();
107
-				var meanCosts = new Array();
108
-				for(var i = 0; i < roastedItems.length; i++) {
109
-					if(recipes[i] == "{-1}") {
110
-						minCosts.push("undefined");
111
-						maxCosts.push("undefined");
112
-						meanCosts.push("undefined");
113
-					} else {
114
-						minCosts.push(proportionalCosts[i] * mins[i]);
115
-						maxCosts.push(proportionalCosts[i] * maxes[i]);
116
-						meanCosts.push(proportionalCosts[i] * means[i]);
117
-					}
118
-				}
119
-				output.writeStartElement("table");
120
-				output.writeAttribute("rules", "groups");
121
-				output.writeAttribute("cellpadding", "3px");
122
-				output.writeStartElement("thead");
123
-				output.writeStartElement("tr");
124
-				output.writeTextElement("th", "Coffee");
125
-				output.writeTextElement("th", "Minimum Cost");
126
-				output.writeTextElement("th", "Maximum Cost");
127
-				output.writeTextElement("th", "Mean Cost");
128
-				output.writeEndElement();
129
-				output.writeEndElement();
130
-				output.writeStartElement("tbody");
131
-				for(var i = 0; i < roastedItems.length; i++) {
132
-					output.writeStartElement("tr");
133
-					output.writeTextElement("td", roastedNames[i]);
134
-					output.writeTextElement("td", Number(minCosts[i]).toFixed(2));
135
-					output.writeTextElement("td", Number(maxCosts[i]).toFixed(2));
136
-					output.writeTextElement("td", Number(meanCosts[i]).toFixed(2));
137
-					output.writeEndElement();
138
-				}
139
-				output.writeEndElement();
140
-				output.writeEndElement();
141
-				output.writeEndElement();
142
-				output.writeEndElement();
143
-				output.writeEndDocument();
144
-				report.setContent(buffer);
145
-				buffer.close();
146
-			};
147
-			refresh();
148
-		]]>
149
-	</program>
45
+                var unit = (unitBox.currentIndex == 0 ? TTR("greencost", "Kg") : 
46
+                    TTR("greencost", "Lb"));
47
+                output.writeTextElement("h1", TTR("greencost", "Cost of Green Coffee for Roasted Coffee"));
48
+                output.writeTextElement("p", TTR("greencost", "Cost of green coffee per ") +
49
+                    unit + TTR("greencost", " of roasted coffee"));
50
+                var query = new QSqlQuery();
51
+                query.exec("SELECT item, (SELECT name FROM items WHERE id = item) AS name FROM current_items ORDER BY name");
52
+                var roastedItems = new Array();
53
+                var roastedNames = new Array();
54
+                while(query.next()) {
55
+                    roastedItems.push(query.value(0));
56
+                    roastedNames.push(query.value(1));
57
+                }
58
+                var recipes = new Array();
59
+                var recipeQuantities = new Array();
60
+                query.prepare("SELECT unroasted_id, unroasted_quantity FROM roasting_log WHERE roasted_id = :item AND time = (SELECT max(time) FROM roasting_log WHERE roasted_id = :item2)");
61
+                for(var i = 0; i < roastedItems.length; i++) {
62
+                    query.bind("item", roastedItems[i]);
63
+                    query.bind("item2", roastedItems[i]);
64
+                    query.exec();
65
+                    if(query.next()) {
66
+                        recipes.push(query.value(0));
67
+                        recipeQuantities.push(query.value(1));
68
+                    } else {
69
+                        recipes.push("{-1}");
70
+                        recipeQuantities.push("{-1}");
71
+                    }
72
+                }
73
+                query.prepare("SELECT min(unroasted_total_quantity / roasted_quantity), max(unroasted_total_quantity / roasted_quantity), avg(unroasted_total_quantity / roasted_quantity) FROM roasting_log WHERE roasted_id = :ri AND unroasted_id = :gi AND unroasted_total_quantity > 0 AND roasted_quantity > 0 AND approval = true");
74
+                var mins = new Array();
75
+                var maxes = new Array();
76
+                var means = new Array();
77
+                for(var i = 0; i < roastedItems.length; i++) {
78
+                    if(recipes[i] == "{-1}") {
79
+                        mins.push("undefined");
80
+                        maxes.push("undefined");
81
+                        means.push("undefined");
82
+                    } else {
83
+                        query.bind(":ri", Number(roastedItems[i]));
84
+                        query.bind(":gi", recipes[i]);
85
+                        query.exec();
86
+                        if(query.next()) {
87
+                            mins.push(query.value(0));
88
+                            maxes.push(query.value(1));
89
+                            means.push(query.value(2));
90
+                        } else {
91
+                            print("Error 2");
92
+                        }
93
+                    }
94
+                }
95
+                var proportionalCosts = new Array();
96
+                query.prepare("SELECT cost * :proportion * :conversion FROM purchase WHERE item = :id");
97
+                for(var i = 0; i < roastedItems.length; i++) {
98
+                    if(recipes[i] == "{-1}") {
99
+                        proportionalCosts.push("undefined");
100
+                    } else {
101
+                        greens = sqlToArray(recipes[i]);
102
+                        weights = sqlToArray(recipeQuantities[i]);
103
+                        proportions = new Array();
104
+                        quantitySum = weights.reduce(function(p, c) {
105
+                            return Number(p) + Number(c);
106
+                        });
107
+                        for(var j = 0; j < weights.length; j++) {
108
+                            proportions.push(weights[j]/quantitySum);
109
+                        }
110
+                        partialSum = 0;
111
+                        for(var j = 0; j < greens.length; j++) {
112
+                            query.bind(":proportion", proportions[j]);
113
+                            query.bind(":id", greens[j]);
114
+                            query.bind(":conversion", conversion);
115
+                            query.exec();
116
+                            if(query.next()) {
117
+                                partialSum += Number(query.value(0));
118
+                            } else {
119
+                                print("Error 3");
120
+                            }
121
+                        }
122
+                        proportionalCosts.push(partialSum);
123
+                    }
124
+                }
125
+                query = query.invalidate();
126
+                var minCosts = new Array();
127
+                var maxCosts = new Array();
128
+                var meanCosts = new Array();
129
+                for(var i = 0; i < roastedItems.length; i++) {
130
+                    if(recipes[i] == "{-1}") {
131
+                        minCosts.push("undefined");
132
+                        maxCosts.push("undefined");
133
+                        meanCosts.push("undefined");
134
+                    } else {
135
+                        minCosts.push(proportionalCosts[i] * mins[i]);
136
+                        maxCosts.push(proportionalCosts[i] * maxes[i]);
137
+                        meanCosts.push(proportionalCosts[i] * means[i]);
138
+                    }
139
+                }
140
+                output.writeStartElement("table");
141
+                output.writeAttribute("rules", "groups");
142
+                output.writeAttribute("cellpadding", "3px");
143
+                output.writeStartElement("thead");
144
+                output.writeStartElement("tr");
145
+                output.writeTextElement("th", TTR("greencost", "Coffee"));
146
+                output.writeTextElement("th", TTR("greencost", "Minimum Cost"));
147
+                output.writeTextElement("th", TTR("greencost", "Maximum Cost"));
148
+                output.writeTextElement("th", TTR("greencost", "Mean Cost"));
149
+                output.writeEndElement();
150
+                output.writeEndElement();
151
+                output.writeStartElement("tbody");
152
+                for(var i = 0; i < roastedItems.length; i++) {
153
+                    output.writeStartElement("tr");
154
+                    output.writeTextElement("td", roastedNames[i]);
155
+                    output.writeTextElement("td", Number(minCosts[i]).toFixed(2));
156
+                    output.writeTextElement("td", Number(maxCosts[i]).toFixed(2));
157
+                    output.writeTextElement("td", Number(meanCosts[i]).toFixed(2));
158
+                    output.writeEndElement();
159
+                }
160
+                output.writeEndElement();
161
+                output.writeEndElement();
162
+                output.writeEndElement();
163
+                output.writeEndElement();
164
+                output.writeEndDocument();
165
+                report.setContent(buffer);
166
+                buffer.close();
167
+            };
168
+            refresh();
169
+        ]]>
170
+    </program>
150 171
 </window>
151 172
 

+ 83
- 28
config/Reports/dailyproductiondetail.xml View File

@@ -49,14 +49,14 @@
49 49
 				output.writeStartElement("html");
50 50
 				output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
51 51
 				output.writeStartElement("head");
52
-				output.writeTextElement("title", "Daily Production Report");
52
+				output.writeTextElement("title", TTR("dailyproduction", "Daily Production Report"));
53 53
 				output.writeEndElement();
54 54
 				output.writeStartElement("body");
55 55
 				var dateString = "" + dateField.year() + "-" + dateField.month() + "-" + dateField.day();
56
-				output.writeTextElement("h1", "Daily Production Report: " + dateString);
57
-				output.writeTextElement("h2", "Batches Roasted");
56
+				output.writeTextElement("h1", TTR("dailyproduction", "Daily Production Report: ") + dateString);
57
+				output.writeTextElement("h2", TTR("dailyproduction", "Batches Roasted"));
58 58
 				var query = new QSqlQuery();
59
-				var q = "SELECT time, machine, (SELECT name FROM machine WHERE id = machine), (SELECT name FROM items WHERE id = roasted_id), unroasted_id, unroasted_quantity, unroasted_total_quantity, roasted_id, roasted_quantity, annotation, duration, files FROM roasting_log WHERE time > '" + dateString + "' AND time < ('" + dateString + "'::date + integer '1') ORDER BY time";
59
+				var q = "SELECT time, machine, (SELECT name FROM machine WHERE id = machine), (SELECT name FROM items WHERE id = roasted_id), unroasted_id, unroasted_quantity, unroasted_total_quantity, roasted_id, roasted_quantity, annotation, duration, files, (SELECT loss FROM roasting_specification WHERE item = roasted_id AND time = (SELECT max(time) FROM roasting_specification WHERE time <= roasting_log.time AND item = roasted_id)), (SELECT tolerance FROM roasting_specification WHERE item = roasted_id AND time = (SELECT max(time) FROM roasting_specification WHERE time <= roasting_log.time AND item = roasted_id)), (SELECT notes FROM roasting_specification WHERE item = roasted_id AND time = (SELECT max(time) FROM roasting_specification WHERE time <= roasting_log.time AND item = roasted_id)), approval FROM roasting_log WHERE time > '" + dateString + "' AND time < ('" + dateString + "'::date + integer '1') ORDER BY time";
60 60
 				query.exec(q);
61 61
 				var times = new Array();
62 62
 				var machines = new Array();
@@ -65,34 +65,36 @@
65 65
 				output.writeAttribute("cellpadding", "3px");
66 66
 				output.writeStartElement("thead");
67 67
 				output.writeStartElement("tr");
68
-				output.writeTextElement("th", "Time");
69
-				output.writeTextElement("th", "Machine");
70
-				output.writeTextElement("th", "Batch ID");
68
+                                output.writeAttribute("valign", "bottom");
69
+				output.writeTextElement("th", TTR("dailyproduction", "Time"));
70
+				output.writeTextElement("th", TTR("dailyproduction", "Machine"));
71
+				output.writeTextElement("th", TTR("dailyproduction", "Batch ID"));
71 72
 				switch(unitBox.currentIndex)
72 73
 				{
73 74
 					case 0:
74
-						output.writeTextElement("th", "Green Weights (Kg)");
75
+						output.writeTextElement("th", TTR("dailyproduction", "Green Weights (Kg)"));
75 76
 						break;
76 77
 					case 1:
77
-						output.writeTextElement("th", "Green Weights (Lb)");
78
+						output.writeTextElement("th", TTR("dailyproduction", "Green Weights (Lb)"));
78 79
 						break;
79 80
 				}
80 81
 				output.writeTextElement("th", "Green Coffees");
81 82
 				switch(unitBox.currentIndex)
82 83
 				{
83 84
 					case 0:
84
-						output.writeTextElement("th", "Roasted Weight (Kg)");
85
+						output.writeTextElement("th", TTR("dailyproduction", "Roasted Weight (Kg)"));
85 86
 						break;
86 87
 					case 1:
87
-						output.writeTextElement("th", "Roasted Weight (Lb)");
88
+						output.writeTextElement("th", TTR("dailyproduction", "Roasted Weight (Lb)"));
88 89
 						break;
89 90
 				}
90
-				output.writeTextElement("th", "Roasted Coffee");
91
-				output.writeTextElement("th", "% Weight Loss");
92
-				output.writeTextElement("th", "Duration");
91
+				output.writeTextElement("th", TTR("dailyproduction", "Roasted Coffee"));
92
+				output.writeTextElement("th", TTR("dailyproduction", "% Weight Loss"));
93
+				output.writeTextElement("th", TTR("dailyproduction", "Duration"));
93 94
 				output.writeEndElement();
94 95
 				output.writeEndElement();
95 96
 				output.writeStartElement("tbody");
97
+                                output.writeAttribute("valign", "top");
96 98
 				while(query.next())
97 99
 				{
98 100
 					times.push(query.value(0));
@@ -164,11 +166,45 @@
164 166
 							output.writeCDATA(query.value(8));
165 167
 							break;
166 168
 					}
167
-					output.writeTextElement("td", query.value(3) + " (" + query.value(7) + ")");
169
+                                        output.writeStartElement("td");
170
+                                        output.writeStartElement("span");
171
+                                        if(query.value(15) == "false") {
172
+                                            output.writeAttribute("style", "color:#FF0000");
173
+                                        }
174
+                                        output.writeCharacters(query.value(3) + " (" + query.value(7) + ")");
175
+                                        output.writeEndElement();
176
+                                        output.writeEndElement();
168 177
 					if(Number(query.value(6)) > 0) {
169 178
 						var loss = (Number(query.value(6)) - Number(query.value(8)))/Number(query.value(6));
179
+                                                var lossMin;
180
+                                                var lossMax;
181
+                                                var lossColor;
182
+                                                var useLoss;
183
+                                                if(query.value(12) > 0) {
184
+                                                    useLoss = true;
185
+                                                    lossMin = Number(query.value(12)) - Number(query.value(13));
186
+                                                    lossMax = Number(query.value(12)) + Number(query.value(13));
187
+                                                    if(loss >= lossMin && loss <= lossMax) {
188
+                                                        lossColor = '#00FF00';
189
+                                                    } else {
190
+                                                        lossColor = '#FF0000';
191
+                                                    }
192
+                                                } else {
193
+                                                    lossColor = '#000000';
194
+                                                    useLoss = false;
195
+                                                }
170 196
 						loss *= 100;
171
-						output.writeTextElement("td", loss.toFixed(2)+"%");
197
+                                                var lossSpec = Number(query.value(12)) * 100;
198
+                                                var lossTol = Number(query.value(13)) * 100;
199
+                                                output.writeStartElement("td");
200
+                                                output.writeStartElement("span");
201
+                                                output.writeAttribute("style", "color:"+lossColor);
202
+                                                if(useLoss) {
203
+                                                    output.writeAttribute("title", lossSpec.toFixed(2) + "+/-" + lossTol.toFixed(2) + "%");
204
+                                                }
205
+                                                output.writeCharacters(loss.toFixed(2) + "%");
206
+                                                output.writeEndElement();
207
+                                                output.writeEndElement();
172 208
 					} else {
173 209
 						output.writeTextElement("td", "Undefined");
174 210
 					}
@@ -180,7 +216,13 @@
180 216
 						output.writeEmptyElement("td");
181 217
 						output.writeStartElement("td");
182 218
 						output.writeAttribute("colspan", "8");
183
-						output.writeTextElement("em", query.value(9));
219
+                                                var noteArray = query.value(9).split("\n");
220
+                                                for(var i = 0; i < noteArray.length; i++) {
221
+                                                    output.writeStartElement("p");
222
+                                                    output.writeAttribute("style", "margin-top: 0; margin-bottom: 0");
223
+                                                    output.writeCharacters(noteArray[i]);
224
+                                                    output.writeEndElement();
225
+                                                }
184 226
 						output.writeEndElement();
185 227
 						output.writeEndElement();
186 228
 					}
@@ -188,8 +230,8 @@
188 230
 					var annotations = annotationFromRecord(files[0]);
189 231
 					output.writeStartElement("tr");
190 232
 					output.writeStartElement("td");
191
-					output.writeAttribute("colspan", "9");
192
-					output.writeTextElement("strong", "Profile Summary");
233
+					output.writeAttribute("colspan", "5");
234
+					output.writeTextElement("strong", TTR("dailyproduction", "Profile Summary"));
193 235
 					var buffer2 = new QBuffer("<points>"+annotations+"</points>");
194 236
 					buffer2.open(1);
195 237
 					var colQuery = new XQuery;
@@ -198,12 +240,12 @@
198 240
 					var result = colQuery.exec();
199 241
 					buffer2.close();
200 242
 					var seriesHeaders = new Array();
201
-					seriesHeaders.push("Time");
243
+					seriesHeaders.push(TTR("dailyproduction", "Time"));
202 244
 					var records = result.split(";");
203 245
 					for(var i = 0; i < records.length - 1; i++) {
204 246
 						seriesHeaders.push(records[i].replace(/^\s+|\s+$/g,""));
205 247
 					}
206
-					seriesHeaders.push("Note");
248
+					seriesHeaders.push(TTR("dailyproduction", "Note"));
207 249
 					output.writeStartElement("table");
208 250
 					output.writeStartElement("thead");
209 251
 					output.writeStartElement("tr");
@@ -265,6 +307,19 @@
265 307
 					output.writeEndElement();
266 308
 					output.writeEndElement();	
267 309
 					output.writeEndElement();
310
+                                        output.writeStartElement("td");
311
+                                        output.writeAttribute("colspan", "4");
312
+                                        if(query.value(14)) {
313
+                                            output.writeTextElement("strong", "Roast Specification Notes");
314
+                                            var specArray = query.value(14).split("\n");
315
+                                            for(var i = 0; i < noteArray.length; i++) {
316
+                                                output.writeStartElement("p");
317
+                                                output.writeAttribute("style", "margin-top: 0; margin-bottom: 0");
318
+                                                output.writeCharacters(specArray[i]);
319
+                                                output.writeEndElement();
320
+                                            }                                            
321
+                                        }
322
+                                        output.writeEndElement();
268 323
 					output.writeEndElement();
269 324
 				}
270 325
 				output.writeEndElement();
@@ -273,7 +328,7 @@
273 328
 				output.writeEmptyElement("td");
274 329
 				output.writeEmptyElement("td");
275 330
 				output.writeStartElement("td");
276
-				output.writeTextElement("strong", "Totals:");
331
+				output.writeTextElement("strong", TTR("dailyproduction", "Totals:"));
277 332
 				output.writeEndElement();
278 333
 				q = "SELECT sum(unroasted_total_quantity), sum(roasted_quantity), sum(duration) FROM roasting_log WHERE time > '" + dateString + "' AND time < ('" + dateString + "'::date + integer '1')";
279 334
 				query.exec(q);
@@ -303,17 +358,17 @@
303 358
 				output.writeEndElement();
304 359
 				output.writeEndElement(); //tfoot
305 360
 				output.writeEndElement(); //table
306
-				output.writeTextElement("h2", "Inventory");
361
+				output.writeTextElement("h2", TTR("dailyproduction", "Inventory"));
307 362
 				output.writeStartElement("table");
308 363
 				output.writeAttribute("rules", "groups");
309 364
 				output.writeAttribute("cellpadding", "3px");
310 365
 				output.writeStartElement("thead");
311 366
 				output.writeStartElement("tr");
312
-				output.writeTextElement("th", "Green Coffee");
313
-				output.writeTextElement("th", "Starting Inventory");
314
-				output.writeTextElement("th", "Ending Inventory");
315
-				output.writeTextElement("th", "Change");
316
-				output.writeTextElement("th", "Availability");
367
+				output.writeTextElement("th", TTR("dailyproduction", "Green Coffee"));
368
+				output.writeTextElement("th", TTR("dailyproduction", "Starting Inventory"));
369
+				output.writeTextElement("th", TTR("dailyproduction", "Ending Inventory"));
370
+				output.writeTextElement("th", TTR("dailyproduction", "Change"));
371
+				output.writeTextElement("th", TTR("dailyproduction", "Availability"));
317 372
 				output.writeEndElement();
318 373
 				output.writeEndElement();
319 374
 				output.writeStartElement("tbody");

+ 25
- 13
config/Reports/fypurchase.xml View File

@@ -14,11 +14,11 @@
14 14
 	</menu>
15 15
 	<program>
16 16
 		<![CDATA[
17
-			this.windowTitle = "Typica - Coffee Purchase Previous Years Comparison";
17
+			this.windowTitle = TTR("fypurchase", "Typica - Coffee Purchase Previous Years Comparison");
18 18
 			/* Set Lifetime range. */
19 19
 			var dateSelect = findChildObject(this, 'dates');
20 20
 			var query = new QSqlQuery();
21
-			query.exec("SELECT concat(EXTRACT(YEAR FROM time::date), '-01-01') FROM purchase WHERE time = (SELECT min(time) FROM purchase) UNION SELECT concat(EXTRACT(YEAR FROM 'now'::date), '-12-31')");
21
+			query.exec("SELECT concat(EXTRACT(YEAR FROM time::date), '-01-01') FROM purchase WHERE time = (SELECT min(time) FROM purchase) UNION SELECT concat(EXTRACT(YEAR FROM 'now'::date), '-12-31') ORDER BY concat ASC");
22 22
 			query.next();
23 23
 			var lifetimeStartDate = query.value(0);
24 24
 			query.next();
@@ -33,8 +33,8 @@
33 33
 			});
34 34
 			/* Add units to unit selector and enable functionality */
35 35
 			var unitBox = findChildObject(this, 'unit');
36
-			unitBox.addItem("Kg");
37
-			unitBox.addItem("Lb");
36
+			unitBox.addItem(TTR("fypurchase", "Kg"));
37
+			unitBox.addItem(TTR("fypurchase", "Lb"));
38 38
 			unitBox.currentIndex = QSettings.value("script/report_unit", 1);
39 39
 			unitBox['currentIndexChanged(int)'].connect(function() {
40 40
 				QSettings.setValue("script/report_unit", unitBox.currentIndex);
@@ -50,10 +50,10 @@
50 50
                 output.writeStartElement("html");
51 51
                 output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
52 52
                 output.writeStartElement("head");
53
-				output.writeTextElement("title", "Coffee Purchase Previous Years Comparison");
53
+				output.writeTextElement("title", TTR("fypurchase", "Coffee Purchase Previous Years Comparison"));
54 54
 				output.writeEndElement();
55 55
 				output.writeStartElement("body");
56
-				output.writeTextElement("h1", "Coffee Purchase Previous Years Comparison");
56
+				output.writeTextElement("h1", TTR("fypurchase", "Coffee Purchase Previous Years Comparison"));
57 57
 				output.writeStartElement("table");
58 58
 				output.writeAttribute("style", "page-break-after: auto; text-align: left");
59 59
 				output.writeAttribute("rules", "groups");
@@ -61,16 +61,24 @@
61 61
 				output.writeStartElement("thead");
62 62
 				output.writeStartElement("tr");
63 63
 				output.writeEmptyElement("th");
64
-				output.writeTextElement("th", "Sacks Purchased");
64
+				output.writeTextElement("th", TTR("fypurchase", "Sacks Purchased"));
65 65
 				switch(unitBox.currentIndex) {
66 66
 					case 0:
67
-						output.writeTextElement("th", "Kilos Purchased");
67
+						output.writeTextElement("th", TTR("fypurchase", "Kg Purchased"));
68 68
 						break;
69 69
 					case 1:
70
-						output.writeTextElement("th", "Pounds Purchased");
70
+						output.writeTextElement("th", TTR("fypurchase", "Lb Purchased"));
71 71
 						break;
72 72
 				}
73
-				output.writeTextElement("th", "Cost");
73
+				output.writeTextElement("th", TTR("fypurchase", "Cost"));
74
+                                switch(unitBox.currentIndex) {
75
+                                    case 0:
76
+                                        output.writeTextElement("th", TTR("fypurchase", "Cost per Kg"));
77
+                                        break;
78
+                                    case 1:
79
+                                        output.writeTextElement("th", TTR("fypurchase", "Cost per Lb"));
80
+                                        break;
81
+                                }
74 82
 				output.writeEndElement(); //tr
75 83
 				output.writeEndElement(); //thead
76 84
 				output.writeStartElement("tbody");
@@ -102,13 +110,14 @@
102 110
 					sacktotal += Number(query.value(0));
103 111
 					unittotal += Number(query.value(1));
104 112
 					costtotal += Number(query.value(2));
113
+                                        output.writeTextElement("td", (Number(query.value(2))/Number(query.value(1))).toFixed(2));
105 114
 					output.writeEndElement(); //tr
106 115
 				}
107 116
 				query = query.invalidate();
108 117
 				output.writeEndElement(); //tbody
109 118
 				output.writeStartElement("tfoot");
110
-				output.writeTextElement("th", "Totals:");
111
-				output.writeTextElement("td", sacktotal);
119
+				output.writeTextElement("th", TTR("fypurchase", "Totals:"));
120
+				output.writeTextElement("td", sacktotal.toFixed(2));
112 121
 				output.writeTextElement("td", unittotal.toFixed(2));
113 122
 				output.writeTextElement("td", costtotal.toFixed(2));
114 123
 				output.writeEndElement(); //tfoot
@@ -131,7 +140,10 @@
131 140
 					expandedYears.push(url);
132 141
 					var element = new WebElement(view.findFirstElement("#" + url));
133 142
 					var year = url.slice(1,url.length);
134
-					var details = '<tr><td /><td colspan="3"><table><tr><th>Id</th><th>Invoice</th><th>Vendor</th><th>Cost</th></tr>';
143
+					var details = '<tr><td /><td colspan="3"><table><tr><th>Id</th><th>' + TTR("fypurchase", "Invoice") +
144
+                                        '</th><th>' + TTR("fypurchase", "Vendor") +
145
+                                        '</th><th>' + TTR("fypurchase", "Cost") +
146
+                                        '</th></tr>';
135 147
 					q = "SELECT id, invoice, vendor, (SELECT sum(cost) FROM invoice_items WHERE invoice_id = id) FROM invoices WHERE time >= '" + year + "-01-01' AND time <= '" + year + "-12-31'";
136 148
 					var query = new QSqlQuery();
137 149
 					query.exec(q);

+ 28
- 13
config/Reports/greensales.xml View File

@@ -14,7 +14,7 @@
14 14
 	</menu>
15 15
 	<program>
16 16
 		<![CDATA[
17
-			this.windowTitle = "Typica - Green Coffee Sales";
17
+			this.windowTitle = TTR("greensales", "Typica - Green Coffee Sales");
18 18
 			var dateSelect = findChildObject(this, 'dates');
19 19
 			var dateQuery = new QSqlQuery();
20 20
 			dateQuery.exec("SELECT time::date FROM sale WHERE time = (SELECT min(time) FROM sale) OR time = (SELECT max(time) FROM sale) ORDER BY time ASC");
@@ -29,8 +29,8 @@
29 29
 			dateSelect.setLifetimeRange(lifetimeStartDate, lifetimeEndDate);
30 30
 			dateQuery = dateQuery.invalidate();
31 31
 			var unitBox = findChildObject(this, 'unit');
32
-			unitBox.addItem("Kg");
33
-			unitBox.addItem("Lb");
32
+			unitBox.addItem(TTR("greensales", "Kg"));
33
+			unitBox.addItem(TTR("greensales", "Lb"));
34 34
 			unitBox.currentIndex = QSettings.value("script/report_unit", 1);
35 35
 			unitBox['currentIndexChanged(int)'].connect(function() {
36 36
 				QSettings.setValue("script/report_unit", unitBox.currentIndex);
@@ -46,24 +46,23 @@
46 46
 				var buffer = new QBuffer;
47 47
 				buffer.open(3);
48 48
 				var output = new XmlWriter(buffer);
49
-				var output = new XmlWriter(buffer);
50 49
 				output.writeStartDocument("1.0");
51 50
 				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">');
52 51
 				output.writeStartElement("html");
53 52
 				output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
54 53
 				output.writeStartElement("head");
55
-				output.writeTextElement("title", "Green Coffee Sales");
54
+				output.writeTextElement("title", TTR("greensales", "Green Coffee Sales"));
56 55
 				output.writeEndElement();
57 56
 				output.writeStartElement("body");
58 57
 				var dateRange = dateSelect.currentRange();
59 58
 				var startDate = dateRange[0];
60 59
 				var endDate = dateRange[dateRange.length - 1];
61
-				output.writeTextElement("h1", "Green Coffee Sales: " + startDate + " - " + endDate);
60
+				output.writeTextElement("h1", TTR("greensales", "Green Coffee Sales: ") + startDate + " - " + endDate);
62 61
 				var conversion = 1;
63
-				var unitText = 'Lb';
62
+				var unitText = TTR("greensales", "Lb");
64 63
 				if(unitBox.currentIndex == 0) {
65 64
 					conversion = 2.2;
66
-					unitText = 'Kg';
65
+					unitText = TTR("greensales", "Kg");
67 66
 				}
68 67
 				var query = new QSqlQuery();
69 68
 				query.prepare("SELECT item, (SELECT name FROM coffees WHERE id = item) AS name, (SELECT origin FROM coffees WHERE id = item) AS origin, (SELECT reference FROM coffees WHERE id = item) AS reference, (SUM(quantity)/:conversion)::numeric(12,3) FROM sale WHERE time < :ed ::date + interval '1 day' AND time >= :sd GROUP BY item ORDER BY name ASC");
@@ -76,11 +75,11 @@
76 75
 				output.writeAttribute("cellpadding", "3px");
77 76
 				output.writeStartElement("thead");
78 77
 				output.writeStartElement("tr");
79
-				output.writeTextElement("th", "ID"); // 0
80
-				output.writeTextElement("th", "Coffee"); // 1
81
-				output.writeTextElement("th", "Origin"); // 2
82
-				output.writeTextElement("th", "Reference"); // 3
83
-				output.writeTextElement("th", "Quantity"); // 4
78
+				output.writeTextElement("th", TTR("greensales", "ID")); // 0
79
+				output.writeTextElement("th", TTR("greensales", "Coffee")); // 1
80
+				output.writeTextElement("th", TTR("greensales", "Origin")); // 2
81
+				output.writeTextElement("th", TTR("greensales", "Reference")); // 3
82
+				output.writeTextElement("th", TTR("greensales", "Quantity")); // 4
84 83
 				output.writeEndElement();
85 84
 				output.writeEndElement();
86 85
 				output.writeStartElement("tbody");
@@ -96,6 +95,22 @@
96 95
 					output.writeEndElement();
97 96
 				}
98 97
 				output.writeEndElement();
98
+                                output.writeStartElement("tfoot");
99
+                                query.prepare("SELECT (sum(quantity)/:conversion)::numeric(12,3) FROM sale WHERE time < :ed ::date + interval '1 day' AND time >= :sd");
100
+                                query.bind(":conversion", conversion);
101
+                                query.bind(":ed", endDate);
102
+                                query.bind(":sd", startDate);
103
+                                query.exec();
104
+                                if(query.next()) {
105
+                                    output.writeStartElement("tr");
106
+                                    output.writeEmptyElement("td");
107
+                                    output.writeEmptyElement("td");
108
+                                    output.writeEmptyElement("td");
109
+                                    output.writeTextElement("th", TTR("greensales", "Total:"));
110
+                                    output.writeTextElement("td", query.value(0));
111
+                                    output.writeEndElement();
112
+                                }
113
+                                output.writeEndElement();
99 114
 				output.writeEndElement();
100 115
 				output.writeEndElement();
101 116
 				output.writeEndElement();

+ 320
- 0
config/Reports/historyreport.xml View File

@@ -0,0 +1,320 @@
1
+<window id="batchreport">
2
+    <reporttitle>Production:->Batch Log</reporttitle>
3
+    <layout type="vertical">
4
+        <layout type="horizontal">
5
+            <daterange id="dates" initial="6" /><!-- Last 7 Days -->
6
+            <label>Batch Type: </label>
7
+            <sqldrop id="batchtype" />
8
+            <label>Approval: </label>
9
+            <sqldrop id="approval" />
10
+            <label>Search: </label>
11
+            <line id="search" />
12
+            <label>Weight Unit:</label>
13
+            <sqldrop id="unit" />
14
+            <stretch />
15
+        </layout>
16
+        <webview id="report" />
17
+    </layout>
18
+    <menu name="File">
19
+        <item id="print" shortcut="Ctrl+P">Print...</item>
20
+    </menu>
21
+    <program>
22
+        <![CDATA[
23
+            this.setWindowTitle(TTR("batchreport", "Typica - Batch Log"));
24
+            var dateSelect = findChildObject(this, 'dates');
25
+            var dateQuery = new QSqlQuery();
26
+            dateQuery.exec("SELECT time::date FROM roasting_log WHERE time = (SELECT min(time) FROM roasting_log) OR time = (SELECT max(time) FROM roasting_log) ORDER BY time ASC");
27
+            dateQuery.next();
28
+            var lifetimeStartDate = dateQuery.value(0);
29
+            var lifetimeEndDate;
30
+            if(dateQuery.next()) {
31
+                lifetimeEndDate = dateQuery.value(0);
32
+            } else {
33
+                lifetimeEndDate = lifetimeStartDate;
34
+            }
35
+            dateSelect.setLifetimeRange(lifetimeStartDate, lifetimeEndDate);
36
+            dateQuery = dateQuery.invalidate();
37
+            dateSelect.currentIndex = QSettings.value("script/history/dateIndex", 6);
38
+            dateSelect.rangeUpdated.connect(function() {
39
+                if(dateSelect.currentIndex != 24) {
40
+                    QSettings.setValue("script/history/dateIndex", dateSelect.currentIndex);
41
+                }
42
+                refresh();
43
+            });
44
+            var unitBox = findChildObject(this, 'unit');
45
+            unitBox.addItem(TTR("batchreport", "Kg"));
46
+            unitBox.addItem(TTR("batchreport", "Lb"));
47
+            unitBox.currentIndex = QSettings.value("script/history_unit", 1);
48
+            unitBox['currentIndexChanged(int)'].connect(function() {
49
+                QSettings.setValue("script/history_unit", unitBox.currentIndex);
50
+                refresh();
51
+            });
52
+            var batchType = findChildObject(this, 'batchtype');
53
+            batchType.addItem(TTR("batchreport", "Any"));
54
+            batchType.addItem(TTR("batchreport", "Production Roasts"));
55
+            batchType.addItem(TTR("batchreport", "Sample Roasts"));
56
+            batchType.currentIndex = QSettings.value("script/history/batchtypefilter", 1);
57
+            batchType['currentIndexChanged(int)'].connect(function() {
58
+                QSettings.setValue("script/history/batchtypefilter", batchType.currentIndex);
59
+                refresh();
60
+            });
61
+            var approval = findChildObject(this, 'approval');
62
+            approval.addItem(TTR("batchreport", "Any"));
63
+            approval.addItem(TTR("batchreport", "Approved"));
64
+            approval.addItem(TTR("batchreport", "Not Approved"));
65
+            approval.currentIndex = QSettings.value("script/history/approvalfilter", 1);
66
+            approval['currentIndexChanged(int)'].connect(function() {
67
+                QSettings.setValue("script/history/approvalfilter", approval.currentIndex);
68
+                refresh();
69
+            });
70
+            var search = findChildObject(this, 'search');
71
+            search.editingFinished.connect(function() {
72
+                refresh();
73
+            });
74
+            var view = findChildObject(this, 'report');
75
+            var printMenu = findChildObject(this, 'print');
76
+            printMenu.triggered.connect(function() {
77
+                view.print();
78
+            });
79
+            view.scriptLinkClicked.connect(function(url) {
80
+                var arg = decodeURI(url.slice(2, url.length));
81
+                var key = arg.split("@");
82
+                var details = createWindow("batchDetails");
83
+                var fakeTable = new Object;
84
+                fakeTable.holding = new Array(7);
85
+                fakeTable.data = function(r, c) {
86
+                    return this.holding[c];
87
+                };
88
+                var conversion = 1;
89
+                if(unitBox.currentIndex == 0) {
90
+                    conversion = 2.2;
91
+                }
92
+                var query = new QSqlQuery();
93
+                var q = "SELECT time, machine, (SELECT name FROM items WHERE id = roasted_id) AS name, unroasted_total_quantity / " + conversion + " AS green, roasted_quantity  / " + conversion + " AS roasted, CASE WHEN unroasted_total_quantity = 0 THEN NULL ELSE ((unroasted_total_quantity - roasted_quantity) / unroasted_total_quantity * 100::numeric)::numeric(12,2) END AS weight_loss, duration, annotation FROM roasting_log WHERE machine = :machine AND time = :time";
94
+                query.prepare(q);
95
+                query.bind(":machine", key[0]);
96
+                query.bind(":time", key[1]);
97
+                query.exec();
98
+                query.next();
99
+                for(var i = 0; i < 8; i++) {
100
+                    fakeTable.holding[i] = query.value(i);
101
+                }
102
+                fakeTable.holding[0] = key[1];
103
+                query = query.invalidate();
104
+                details.loadData(fakeTable, 0);
105
+            });
106
+            var refresh = function() {
107
+                var dateRange = dateSelect.currentRange();
108
+                var startDate = dateRange[0];
109
+                var endDate = dateRange[dateRange.length - 1];
110
+                var conversion = 1;
111
+                if(unitBox.currentIndex == 0) {
112
+                    conversion = 2.2;
113
+                }
114
+                var approvalClause = "";
115
+                switch(approval.currentIndex) {
116
+                    case 1:
117
+                        approvalClause = " AND approval = true";
118
+                        break;
119
+                    case 2:
120
+                        approvalClause = " AND approval = false";
121
+                        break;
122
+                }
123
+                var batchClause = "";
124
+                switch(batchType.currentIndex) {
125
+                    case 1:
126
+                        batchClause = " AND transaction_type = 'ROAST'";
127
+                        break;
128
+                    case 2:
129
+                        batchClause = " AND transaction_type = 'SAMPLEROAST'";
130
+                        break;
131
+                }
132
+                var searchClause = "";
133
+                if(search.text.length > 0) {
134
+                    searchClause = " WHERE (person ~* :p1 OR rname ~* :p2 OR mname ~* :p3 OR greens ~* :p4 OR annotation ~* :p5 OR array_to_string(files, ',') ~* :p6)";
135
+                }
136
+                var q = "WITH qq AS (SELECT roasting_log.time, array_to_string(files, ','), person, (SELECT name || ' (' || id || ')' FROM items WHERE id = roasted_id) AS rname, duration, (SELECT name FROM machine WHERE id = machine) AS mname, array_to_string(ARRAY(SELECT name || ' (' || id || ')' FROM items WHERE id IN (SELECT unnest(unroasted_id))), ',') AS greens, (unroasted_total_quantity/:c1)::numeric(12,2), (roasted_quantity/:c2)::numeric(12,2), loss, annotation, approval, spec_loss::numeric(12,2) || '±' || spec_tolerance::numeric(12,2) AS lspec, notes, loss_match, machine || '@' || roasting_log.time AS link, files FROM roasting_log, LATERAL (SELECT CASE WHEN (unroasted_total_quantity = 0) THEN NULL ELSE (((unroasted_total_quantity - roasted_quantity)/unroasted_total_quantity)*100)::numeric(12,2) END AS loss) lc, LATERAL (WITH q AS (SELECT (SELECT min(time) - interval '10 years' FROM roasting_log) AS time, NULL::numeric AS loss, NULL::numeric AS tolerance, NULL::text AS notes) SELECT time, (loss*100)::numeric(12,2) AS spec_loss, (tolerance*100)::numeric(12,2) AS spec_tolerance, notes FROM roasting_specification WHERE item = roasted_id AND time = (SELECT max(time) FROM roasting_specification WHERE time <= roasting_log.time AND item = roasted_id) UNION (SELECT * FROM q) ORDER BY time DESC LIMIT 1) spec, LATERAL (SELECT loss >= spec_loss - spec_tolerance AND loss <= spec_loss + spec_tolerance AS loss_match) m WHERE roasting_log.time >= :sd AND roasting_log.time < :ed::date + interval '1 day'" + approvalClause + batchClause + ") SELECT * FROM qq" + searchClause + " ORDER BY time DESC";
137
+                var query = new QSqlQuery();
138
+                query.prepare(q);
139
+                query.bind(":c1", conversion);
140
+                query.bind(":c2", conversion);
141
+                query.bind(":sd", startDate);
142
+                query.bind(":ed", endDate);
143
+                if(searchClause.length > 0) {
144
+                    var pattern = ".*" + search.text + ".*";
145
+                    query.bind(":p1", pattern);
146
+                    query.bind(":p2", pattern);
147
+                    query.bind(":p3", pattern);
148
+                    query.bind(":p4", pattern);
149
+                    query.bind(":p5", pattern);
150
+                    query.bind(":p6", pattern);
151
+                }
152
+                query.exec();
153
+                var buffer = new QBuffer;
154
+                buffer.open(3);
155
+                var output = new XmlWriter(buffer);
156
+                output.writeStartDocument("1.0");
157
+                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">');
158
+                output.writeStartElement("html");
159
+                output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
160
+                output.writeStartElement("head");
161
+                output.writeTextElement("title", TTR("batchreport", "Batch Log"));
162
+                output.writeEndElement();
163
+                output.writeStartElement("body");
164
+                output.writeStartElement("table");
165
+                output.writeAttribute("style", "page-break-after: auto; text-align: left");
166
+                output.writeAttribute("rules", "groups");
167
+                output.writeAttribute("cellpadding", "3px");
168
+                output.writeStartElement("thead");
169
+                output.writeStartElement("tr");
170
+                output.writeAttribute("valign", "bottom");
171
+                output.writeTextElement("th", TTR("batchreport", "Time"));
172
+                output.writeTextElement("th", TTR("batchreport", "File Numbers"));
173
+                output.writeTextElement("th", TTR("batchreport", "Operator"));
174
+                output.writeTextElement("th", TTR("batchreport", "Roasted Coffee"));
175
+                output.writeEndElement();
176
+                output.writeStartElement("tr");
177
+                output.writeAttribute("valign", "bottom");
178
+                output.writeEmptyElement("td");
179
+                output.writeTextElement("th", TTR("batchreport", "Duration"));
180
+                output.writeTextElement("th", TTR("batchreport", "Machine"));
181
+                output.writeTextElement("th", TTR("batchreport", "Green Coffees"));
182
+                output.writeEndElement();
183
+                output.writeStartElement("tr");
184
+                output.writeAttribute("valign", "bottom");
185
+                output.writeEmptyElement("td");
186
+                output.writeTextElement("th", TTR("batchreport", "Green Weight"));
187
+                output.writeTextElement("th", TTR("batchreport", "Roasted Weight"));
188
+                output.writeTextElement("th", TTR("batchreport", "% Weight Loss"));
189
+                output.writeEndElement();
190
+                output.writeStartElement("tr");
191
+                output.writeAttribute("valign", "bottom");
192
+                output.writeEmptyElement("td");
193
+                output.writeStartElement("th");
194
+                output.writeAttribute("colspan", "2");
195
+                output.writeCharacters(TTR("batchreport", "Batch Notes"));
196
+                output.writeEndElement();
197
+                output.writeTextElement("th", TTR("batchreport", "Specification Notes"));
198
+                output.writeEndElement();
199
+                output.writeEndElement();
200
+                output.writeStartElement("tbody");
201
+                while(query.next()) {
202
+                    output.writeStartElement("tr");
203
+                    output.writeAttribute("valign", "top");
204
+                    output.writeStartElement("td");
205
+                    output.writeStartElement("a");
206
+                    output.writeAttribute("href", "typica://script/b/" + query.value(15));
207
+                    output.writeCharacters(query.value(0).replace("T", " "));
208
+                    output.writeEndElement();
209
+                    output.writeEndElement();
210
+                    output.writeStartElement("td");
211
+                    output.writeStartElement("a");
212
+                    output.writeAttribute("href", "typica://script/b/" + query.value(15));
213
+                    output.writeCharacters(query.value(1));
214
+                    output.writeEndElement();
215
+                    output.writeEndElement();
216
+                    output.writeTextElement("td", query.value(2));
217
+                    output.writeStartElement("td");
218
+                    output.writeStartElement("span");
219
+                    if(query.value(11) == "false") {
220
+                        output.writeAttribute("style", "color:#FF0000");
221
+                    }
222
+                    output.writeCharacters(query.value(3));
223
+                    output.writeEndElement();
224
+                    output.writeEndElement();
225
+                    output.writeEndElement();
226
+                    output.writeStartElement("tr");
227
+                    output.writeAttribute("valign", "top");
228
+                    output.writeEmptyElement("td");
229
+                    output.writeTextElement("td", query.value(4));
230
+                    output.writeTextElement("td", query.value(5));
231
+                    output.writeTextElement("td", query.value(6));
232
+                    output.writeEndElement();
233
+                    output.writeStartElement("tr");
234
+                    output.writeAttribute("valign", "top");
235
+                    output.writeEmptyElement("td");
236
+                    output.writeTextElement("td", query.value(7));
237
+                    output.writeTextElement("td", query.value(8));
238
+                    output.writeStartElement("td");
239
+                    output.writeStartElement("span");
240
+                    if(query.value(14) == "false" && query.value(12).length > 0) {
241
+                        output.writeAttribute("style", "color:#FF0000");
242
+                    } else if (query.value(14) === "true") {
243
+                        output.writeAttribute("style", "color:#00FF00");
244
+                    }
245
+                    output.writeAttribute("title", query.value(12));
246
+                    output.writeCharacters(query.value(9));
247
+                    output.writeEndElement();
248
+                    output.writeEndElement();
249
+                    output.writeEndElement();
250
+                    output.writeStartElement("tr");
251
+                    output.writeAttribute("valign", "top");
252
+                    output.writeEmptyElement("td");
253
+                    output.writeStartElement("td");
254
+                    output.writeAttribute("colspan", "2");
255
+                    output.writeAttribute("style", "max-width: 400px");
256
+                    var noteArray = query.value(10).split("\n");
257
+                    for(var i = 0; i < noteArray.length; i++) {
258
+                        output.writeStartElement("p");
259
+                        output.writeAttribute("style", "margin-top: 0; margin-bottom: 0");
260
+                        output.writeCharacters(noteArray[i]);
261
+                        output.writeEndElement();
262
+                    }
263
+                    output.writeEndElement();
264
+                    output.writeStartElement("td");
265
+                    output.writeAttribute("style", "max-width: 400px");
266
+                    var specArray = query.value(13).split("\n");
267
+                    for(var i = 0; i < specArray.length; i++) {
268
+                        output.writeStartElement("p", specArray[i]);
269
+                        output.writeAttribute("style", "margin-top: 0; margin-bottom: 0");
270
+                        output.writeCharacters(specArray[i]);
271
+                        output.writeEndElement();
272
+                    }
273
+                    output.writeEndElement();
274
+                    output.writeEndElement();
275
+                }
276
+                output.writeEndElement();
277
+                output.writeStartElement("tfoot");
278
+                output.writeStartElement("tr");
279
+                output.writeAttribute("valign", "bottom");
280
+                output.writeTextElement("th", TTR("batchreport", "Total Batches"));
281
+                output.writeTextElement("th", TTR("batchreport", "Total Duration"));
282
+                output.writeTextElement("th", TTR("batchreport", "Total Green Weight"));
283
+                output.writeTextElement("th", TTR("batchreport", "Total Roasted Weight"));
284
+                output.writeEndElement();
285
+                output.writeStartElement("tr");
286
+                output.writeAttribute("valign", "top");
287
+                q = "WITH qq AS (SELECT roasting_log.time, array_to_string(files, ','), person, (SELECT name || ' (' || id || ')' FROM items WHERE id = roasted_id) AS rname, duration, (SELECT name FROM machine WHERE id = machine) AS mname, array_to_string(ARRAY(SELECT name || ' (' || id || ')' FROM items WHERE id IN (SELECT unnest(unroasted_id))), ',') AS greens, (unroasted_total_quantity/:c1)::numeric(12,2) AS uq, (roasted_quantity/:c2)::numeric(12,2) AS rq, annotation, approval, files FROM roasting_log WHERE roasting_log.time >= :sd AND roasting_log.time < :ed::date + interval '1 day'" + approvalClause + batchClause + ") SELECT count(1), sum(duration), sum(uq), sum(rq) FROM qq" + searchClause;
288
+                var query = new QSqlQuery();
289
+                query.prepare(q);
290
+                query.bind(":c1", conversion);
291
+                query.bind(":c2", conversion);
292
+                query.bind(":sd", startDate);
293
+                query.bind(":ed", endDate);
294
+                if(searchClause.length > 0) {
295
+                    var pattern = ".*" + search.text + ".*";
296
+                    query.bind(":p1", pattern);
297
+                    query.bind(":p2", pattern);
298
+                    query.bind(":p3", pattern);
299
+                    query.bind(":p4", pattern);
300
+                    query.bind(":p5", pattern);
301
+                    query.bind(":p6", pattern);
302
+                }
303
+                query.exec();
304
+                query.next();
305
+                for(var i = 0; i < 4; i++) {
306
+                    output.writeTextElement("td", query.value(i));
307
+                }
308
+                output.writeEndElement();
309
+                output.writeEndElement();
310
+                query = query.invalidate();
311
+                output.writeEndElement();
312
+                output.writeEndElement();
313
+                output.writeEndDocument();
314
+                view.setContent(buffer);
315
+                buffer.close();
316
+            };
317
+            refresh();
318
+        ]]>
319
+    </program>
320
+</window>

+ 24
- 25
config/Reports/invchange.xml View File

@@ -14,7 +14,7 @@
14 14
 	</menu>
15 15
 	<program>
16 16
 		<![CDATA[
17
-			this.windowTitle = "Typica - Inventory Change Summary";
17
+			this.windowTitle = TTR("invchange", "Typica - Inventory Change Summary");
18 18
 			var dateSelect = findChildObject(this, 'dates');
19 19
 			var dateQuery = new QSqlQuery();
20 20
 			dateQuery.exec("SELECT time::date FROM transactions WHERE time = (SELECT min(time) FROM transactions) OR time = (SELECT max(time) FROM transactions) ORDER BY time ASC");
@@ -29,8 +29,8 @@
29 29
 			dateSelect.setLifetimeRange(lifetimeStartDate, lifetimeEndDate);
30 30
 			dateQuery = dateQuery.invalidate();
31 31
 			var unitBox = findChildObject(this, 'unit');
32
-			unitBox.addItem("Kg");
33
-			unitBox.addItem("Lb");
32
+			unitBox.addItem(TTR("invchange", "Kg"));
33
+			unitBox.addItem(TTR("invchange", "Lb"));
34 34
 			unitBox.currentIndex = QSettings.value("script/report_unit", 1);
35 35
 			unitBox['currentIndexChanged(int)'].connect(function() {
36 36
 				QSettings.setValue("script/report_unit", unitBox.currentIndex);
@@ -50,23 +50,23 @@
50 50
 				output.writeStartElement("html");
51 51
 				output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
52 52
 				output.writeStartElement("head");
53
-				output.writeTextElement("title", "Inventory Change Summary");
53
+				output.writeTextElement("title", TTR("invchange", "Inventory Change Summary"));
54 54
 				output.writeEndElement();
55 55
 				output.writeStartElement("body");
56 56
 				var dateRange = dateSelect.currentRange();
57 57
 				var startDate = dateRange[0];
58 58
 				var endDate = dateRange[dateRange.length - 1];
59
-				output.writeTextElement("h1", "Inventory Change Summary: " + startDate + " – " + endDate);
59
+				output.writeTextElement("h1", TTR("invchange", "Inventory Change Summary: ") + startDate + " – " + endDate);
60 60
 				var conversion = 1;
61 61
 				if(unitBox.currentIndex == 0) {
62 62
 					conversion = 2.2;
63 63
 				}
64
-				var unitText = "Lb";
64
+				var unitText = TTR("invchange", "Lb");
65 65
 				if(unitBox.currentIndex == 0) {
66
-					unitText = "Kg";
66
+					unitText = TTR("invchange", "Kg");
67 67
 				}
68 68
 				var query = new QSqlQuery();
69
-					var q = "WITH q AS (SELECT id, name, reference, COALESCE((SELECT balance FROM item_history(id) WHERE time = (SELECT max(time) FROM item_history(id) WHERE time < :sd1)), 0)/:c1 AS starting_balance, COALESCE((SELECT sum(quantity) FROM purchase WHERE item = id AND time >= :sd2 AND time < :ed1 ::date + interval '1 day'), 0)/:c2 AS purchase, COALESCE((SELECT sum(quantity) FROM use WHERE item = id AND time >= :sd3 AND time < :ed2 ::date + interval '1 day'), 0)/:c3 AS use, COALESCE((SELECT sum(quantity) FROM sale WHERE item = id AND time >= :sd4 AND time < :ed3 ::date + interval '1 day'), 0)/:c4 AS sale, (SElECT balance FROM item_history(id) WHERE time = (SELECT max(time) FROM item_history(id) WHERE time < :ed4 ::date + interval '1 day'))/:c5 AS quantity, (SELECT sum(cost * quantity) / sum(quantity) FROM purchase WHERE item = id) AS unit_cost FROM coffees WHERE id IN (SELECT item FROM purchase WHERE time >= :sd6 AND time < :ed5 ::date + interval '1 day') OR id IN (SELECT id FROM items WHERE (SELECT balance FROM item_history(id) WHERE time = (SELECT max(time) FROM item_history(id) WHERE time < :ed6 ::date + interval '1 day')) > 0) OR id IN (SELECT DISTINCT item FROM all_transactions WHERE time > :sd7 AND time < :ed7 ::date + interval '1 day')) SELECT *, (starting_balance + purchase - use - sale - quantity)/:c7 AS adjustment, starting_balance * unit_cost * :c8 AS starting_cost, purchase * unit_cost * :c9 AS purchase_cost, use * unit_cost * :c10 AS use_cost, sale * unit_cost * :c11 AS sale_cost, quantity * unit_cost * :c12 AS quantity_cost, (starting_balance + purchase - use - sale - quantity) * unit_cost * :c13 AS adjustment_cost, (SELECT sum(quantity)/:c6 FROM purchase WHERE item = id) AS total_purchase FROM q ORDER BY name";
69
+					var q = "WITH q AS (SELECT id, name, reference, COALESCE((SELECT balance FROM item_history(id) WHERE time = (SELECT max(time) FROM item_history(id) WHERE time < :sd1)), 0)/:c1 AS starting_balance, COALESCE((SELECT sum(quantity) FROM purchase WHERE item = id AND time >= :sd2 AND time < :ed1 ::date + interval '1 day'), 0)/:c2 AS purchase, COALESCE((SELECT sum(quantity) FROM use WHERE item = id AND time >= :sd3 AND time < :ed2 ::date + interval '1 day'), 0)/:c3 AS use, COALESCE((SELECT sum(quantity) FROM sale WHERE item = id AND time >= :sd4 AND time < :ed3 ::date + interval '1 day'), 0)/:c4 AS sale, (SElECT balance FROM item_history(id) WHERE time = (SELECT max(time) FROM item_history(id) WHERE time < :ed4 ::date + interval '1 day'))/:c5 AS quantity, (SELECT sum(cost * quantity) / sum(quantity) FROM purchase WHERE item = id) AS unit_cost FROM coffees WHERE id IN (SELECT item FROM purchase WHERE time >= :sd6 AND time < :ed5 ::date + interval '1 day') OR id IN (SELECT id FROM items WHERE (SELECT balance FROM item_history(id) WHERE time = (SELECT max(time) FROM item_history(id) WHERE time < :ed6 ::date + interval '1 day')) > 0) OR id IN (SELECT DISTINCT item FROM all_transactions WHERE time > :sd7 AND time < :ed7 ::date + interval '1 day')) SELECT *, (starting_balance + purchase - use - sale - quantity) AS adjustment, starting_balance * unit_cost * :c8 AS starting_cost, purchase * unit_cost * :c9 AS purchase_cost, use * unit_cost * :c10 AS use_cost, sale * unit_cost * :c11 AS sale_cost, quantity * unit_cost * :c12 AS quantity_cost, (starting_balance + purchase - use - sale - quantity) * unit_cost * :c13 AS adjustment_cost, (SELECT sum(quantity)/:c6 FROM purchase WHERE item = id) AS total_purchase FROM q ORDER BY name";
70 70
 				query.prepare(q);
71 71
 				query.bind(":sd1", startDate);
72 72
 				query.bind(":sd2", startDate);
@@ -87,7 +87,6 @@
87 87
 				query.bind(":c4", conversion);
88 88
 				query.bind(":c5", conversion);
89 89
 				query.bind(":c6", conversion);
90
-				query.bind(":c7", conversion);
91 90
 				query.bind(":c8", conversion);
92 91
 				query.bind(":c9", conversion);
93 92
 				query.bind(":c10", conversion);
@@ -100,21 +99,21 @@
100 99
 				output.writeAttribute("cellpadding", "3px");
101 100
 				output.writeStartElement("thead");
102 101
 				output.writeStartElement("tr");
103
-				output.writeTextElement("th", "ID"); // 0
104
-				output.writeTextElement("th", "Coffee"); // 1
105
-				output.writeTextElement("th", "Reference"); // 2
106
-				output.writeTextElement("th", "Starting (" + unitText + ")"); // 3
107
-				output.writeTextElement("th", "Cost"); // 10
108
-				output.writeTextElement("th", "Purchase (" + unitText + ")"); // 4
109
-				output.writeTextElement("th", "Cost"); // 11
110
-				output.writeTextElement("th", "Use (" + unitText + ")"); // 5
111
-				output.writeTextElement("th", "Cost"); // 12
112
-				output.writeTextElement("th", "Sale (" + unitText + ")"); // 6
113
-				output.writeTextElement("th", "Cost"); // 13
114
-				output.writeTextElement("th", "Adjustment (" + unitText + ")"); // 9
115
-				output.writeTextElement("th", "Cost"); // 15
116
-				output.writeTextElement("th", "Ending (" + unitText + ")"); // 7
117
-				output.writeTextElement("th", "Cost"); // 14
102
+				output.writeTextElement("th", TTR("invchange", "ID")); // 0
103
+				output.writeTextElement("th", TTR("invchange", "Coffee")); // 1
104
+				output.writeTextElement("th", TTR("invchange", "Reference")); // 2
105
+				output.writeTextElement("th", TTR("invchange", "Starting (") + unitText + ")"); // 3
106
+				output.writeTextElement("th", TTR("invchange", "Cost")); // 10
107
+				output.writeTextElement("th", TTR("invchange", "Purchase (") + unitText + ")"); // 4
108
+				output.writeTextElement("th", TTR("invchange", "Cost")); // 11
109
+				output.writeTextElement("th", TTR("invchange", "Use (") + unitText + ")"); // 5
110
+				output.writeTextElement("th", TTR("invchange", "Cost")); // 12
111
+				output.writeTextElement("th", TTR("invchange", "Sale (") + unitText + ")"); // 6
112
+				output.writeTextElement("th", TTR("invchange", "Cost")); // 13
113
+				output.writeTextElement("th", TTR("invchange", "Adjustment (") + unitText + ")"); // 9
114
+				output.writeTextElement("th", TTR("invchange", "Cost")); // 15
115
+				output.writeTextElement("th", TTR("invchange", "Ending (") + unitText + ")"); // 7
116
+				output.writeTextElement("th", TTR("invchange", "Cost")); // 14
118 117
 				output.writeEndElement();
119 118
 				output.writeEndElement();
120 119
 				output.writeStartElement("tbody");
@@ -190,7 +189,7 @@
190 189
 				output.writeStartElement("tr");
191 190
 				output.writeTextElement("td", "");
192 191
 				output.writeTextElement("td", "");
193
-				output.writeTextElement("th", "Total:");
192
+				output.writeTextElement("th", TTR("invchange", "Total:"));
194 193
 				output.writeTextElement("td", sum3.toFixed(2));
195 194
 				output.writeTextElement("td", sum10.toFixed(2));
196 195
 				output.writeTextElement("td", sum4.toFixed(2));

+ 30
- 30
config/Reports/inventory.xml View File

@@ -15,31 +15,31 @@
15 15
     </menu>
16 16
     <program>
17 17
         <![CDATA[
18
-            this.windowTitle = "Typica - Current Inventory and Availability Projection";
18
+            this.windowTitle = TTR("inventoryreport", "Typica - Current Inventory and Availability Projection");
19 19
             var report = findChildObject(this, 'report');
20 20
             var printMenu = findChildObject(this, 'print');
21 21
             printMenu.triggered.connect(function() {
22 22
                 report.print();
23 23
             });
24 24
             var sortBox = findChildObject(this, 'sort');
25
-            sortBox.addItem("Coffee A-Z");
26
-            sortBox.addItem("Coffee Z-A");
27
-            sortBox.addItem("Stock Ascending");
28
-            sortBox.addItem("Stock Descending");
29
-            sortBox.addItem("Sacks Ascending");
30
-            sortBox.addItem("Sacks Descending");
31
-            sortBox.addItem("Unit Cost Ascending");
32
-            sortBox.addItem("Unit Cost Descending");
33
-            sortBox.addItem("Stock Cost Ascending");
34
-            sortBox.addItem("Stock Cost Descending");
35
-            sortBox.addItem("Use Rate Ascending");
36
-            sortBox.addItem("Use Rate Descending");
37
-            sortBox.addItem("Availability Shortest-Longest");
38
-            sortBox.addItem("Availability Longest-Shortest");
25
+            sortBox.addItem(TTR("inventoryreport", "Coffee A-Z"));
26
+            sortBox.addItem(TTR("inventoryreport", "Coffee Z-A"));
27
+            sortBox.addItem(TTR("inventoryreport", "Stock Ascending"));
28
+            sortBox.addItem(TTR("inventoryreport", "Stock Descending"));
29
+            sortBox.addItem(TTR("inventoryreport", "Sacks Ascending"));
30
+            sortBox.addItem(TTR("inventoryreport", "Sacks Descending"));
31
+            sortBox.addItem(TTR("inventoryreport", "Unit Cost Ascending"));
32
+            sortBox.addItem(TTR("inventoryreport", "Unit Cost Descending"));
33
+            sortBox.addItem(TTR("inventoryreport", "Stock Cost Ascending"));
34
+            sortBox.addItem(TTR("inventoryreport", "Stock Cost Descending"));
35
+            sortBox.addItem(TTR("inventoryreport", "Use Rate Ascending"));
36
+            sortBox.addItem(TTR("inventoryreport", "Use Rate Descending"));
37
+            sortBox.addItem(TTR("inventoryreport", "Availability Shortest-Longest"));
38
+            sortBox.addItem(TTR("inventoryreport", "Availability Longest-Shortest"));
39 39
             sortBox.currentIndex = QSettings.value("inventory_sort", 0);
40 40
 			var unitBox = findChildObject(this, 'unit');
41
-			unitBox.addItem("Kg");
42
-			unitBox.addItem("Lb");
41
+			unitBox.addItem(TTR("inventoryreport", "Kg"));
42
+			unitBox.addItem(TTR("inventoryreport", "Lb"));
43 43
 			unitBox.currentIndex = QSettings.value("script/report_unit", 1);
44 44
             function refresh() {
45 45
                 var buffer = new QBuffer;
@@ -50,31 +50,31 @@
50 50
                 output.writeStartElement("html");
51 51
                 output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
52 52
                 output.writeStartElement("head");
53
-                output.writeTextElement("title", "Current Inventory and Availability Projection");
53
+                output.writeTextElement("title", TTR("inventoryreport", "Current Inventory and Availability Projection"));
54 54
                 output.writeEndElement();
55 55
                 output.writeStartElement("body");
56
-                output.writeTextElement("h1", "Current Inventory and Availability Projection");
57
-                output.writeTextElement("p", "This is a report showing how much of each coffee is available, the cost of that coffee, the daily rate of use for that coffee, and the date the coffee will be gone if use continues at the current rate.");
56
+                output.writeTextElement("h1", TTR("inventoryreport", "Current Inventory and Availability Projection"));
57
+                output.writeTextElement("p", TTR("inventoryreport", "This is a report showing how much of each coffee is available, the cost of that coffee, the daily rate of use for that coffee, and the date the coffee will be gone if use continues at the current rate."));
58 58
                 output.writeStartElement("table");
59 59
                 output.writeAttribute("rules", "groups");
60 60
                 output.writeAttribute("cellpadding", "3px");
61 61
                 output.writeStartElement("thead");
62 62
                 output.writeStartElement("tr");
63
-                output.writeTextElement("th", "Coffee");
63
+                output.writeTextElement("th", TTR("inventoryreport", "Coffee"));
64 64
 				switch(unitBox.currentIndex)
65 65
 				{
66 66
 					case 0:
67
-						output.writeTextElement("th", "Stock (Kg)");
67
+						output.writeTextElement("th", TTR("inventoryreport", "Stock (Kg)"));
68 68
 						break;
69 69
 					case 1:
70
-						output.writeTextElement("th", "Stock (Lb)");
70
+						output.writeTextElement("th", TTR("inventoryreport", "Stock (Lb)"));
71 71
 						break;
72 72
 				}
73
-                output.writeTextElement("th", "Sacks");
74
-                output.writeTextElement("th", "Unit Cost");
75
-                output.writeTextElement("th", "Stock Cost");
76
-                output.writeTextElement("th", "Use Rate");
77
-                output.writeTextElement("th", "Availability");
73
+                output.writeTextElement("th", TTR("inventoryreport", "Sacks"));
74
+                output.writeTextElement("th", TTR("inventoryreport", "Unit Cost"));
75
+                output.writeTextElement("th", TTR("inventoryreport", "Stock Cost"));
76
+                output.writeTextElement("th", TTR("inventoryreport", "Use Rate"));
77
+                output.writeTextElement("th", TTR("inventoryreport", "Availability"));
78 78
                 output.writeEndElement();
79 79
                 output.writeEndElement();
80 80
                 output.writeStartElement("tbody");
@@ -155,7 +155,7 @@
155 155
                 output.writeEndElement();
156 156
                 output.writeStartElement("tfoot");
157 157
                 output.writeStartElement("tr");
158
-                output.writeTextElement("th", "Totals");
158
+                output.writeTextElement("th", TTR("inventoryreport", "Totals"));
159 159
                 query.prepare("SELECT (sum(quantity) / :conversion)::numeric(12,2), sum(quantity * (SELECT cost FROM purchase WHERE item = id))::numeric(12,2) FROM items WHERE quantity > 0");
160 160
 				switch(unitBox.currentIndex)
161 161
 				{
@@ -175,7 +175,7 @@
175 175
                 output.writeTextElement("td", "");
176 176
                 output.writeTextElement("td", "");
177 177
                 query = query.invalidate();
178
-				output.writeEndElement();
178
+                output.writeEndElement();
179 179
                 output.writeEndElement();
180 180
                 output.writeEndElement();
181 181
                 output.writeEndElement();

+ 156
- 0
config/Reports/invoices.xml View File

@@ -0,0 +1,156 @@
1
+<window id="invoicereport">
2
+    <reporttitle>Purchase:->Invoices</reporttitle>
3
+    <layout type="vertical">
4
+        <layout type="horizontal">
5
+            <daterange id="dates" initial="23" /><!-- Lifetime -->
6
+            <label>Vendor: </label>
7
+            <sqldrop id="vendor" />
8
+            <label>Search: </label>
9
+            <line id="search" />
10
+            <stretch />
11
+        </layout>
12
+        <webview id="report" />
13
+    </layout>
14
+    <menu name="File">
15
+        <item id="print" shortcut="Ctrl+P">Print...</item>
16
+    </menu>
17
+    <program>
18
+        <![CDATA[
19
+            var vendor = findChildObject(this, "vendor");
20
+            vendor.addItem(TTR("invoicereport", "Any"));
21
+            var query = new QSqlQuery();
22
+            query.exec("SELECT DISTINCT vendor FROM invoices");
23
+            while(query.next()) {
24
+                vendor.addItem(query.value(0));
25
+            }
26
+            vendor['currentIndexChanged(int)'].connect(refresh);
27
+            var dateSelect = findChildObject(this, 'dates');
28
+            query.exec("SELECT time::date FROM invoices WHERE time = (SELECT min(time) FROM invoices) OR time = (SELECT max(time) FROM invoices) ORDER BY time ASC");
29
+            query.next();
30
+            var lifetimeStartDate = query.value(0);
31
+            var lifetimeEndDate;
32
+            if(query.next()) {
33
+                lifetimeEndDate = query.value(0);
34
+            } else {
35
+                lifetimeEndDate = lifetimeStartDate;
36
+            }
37
+            dateSelect.setLifetimeRange(lifetimeStartDate, lifetimeEndDate);
38
+            dateSelect.rangeUpdated.connect(refresh);
39
+            query = query.invalidate();
40
+            var search = findChildObject(this, "search");
41
+            search.editingFinished.connect(refresh);
42
+            var view = findChildObject(this, "report");
43
+            view.scriptLinkClicked.connect(function(url) {
44
+                var info = createWindow("invoiceinfo");
45
+                info.setInvoiceID(url);
46
+                var invquery = new QSqlQuery();
47
+                invquery.exec("SELECT time, invoice, vendor FROM invoices WHERE id = " + url);
48
+                invquery.next();
49
+                var timefield = findChildObject(info, 'date');
50
+                timefield.text = invquery.value(0);
51
+                var vendorfield = findChildObject(info, 'vendor');
52
+                vendorfield.text = invquery.value(2);
53
+                var invoicefield = findChildObject(info, 'invoice');
54
+                invoicefield.text = invquery.value(1);
55
+                var itemtable = findChildObject(info, 'itemtable');
56
+                itemtable.setQuery("SELECT record_type, item_id, description, (SELECT reference FROM items WHERE id = item_id) AS reference, (SELECT cost FROM purchase WHERE item = item_id) AS unit_cost, (SELECT quantity FROM purchase WHERE item = item_id) AS quantity, ((SELECT quantity FROM purchase WHERE item = item_id)/(SELECT conversion FROM lb_bag_conversion WHERE item = item_id))::numeric(12,2) AS sacks, cost FROM invoice_items WHERE invoice_id = " + url + " AND record_type = 'PURCHASE' UNION SELECT record_type, NULL, description, NULL, NULL, NULL, NULL, cost FROM invoice_items WHERE invoice_id = " + url + " AND record_type = 'FEE' ORDER BY item_id");
57
+                invquery = invquery.invalidate();
58
+            });
59
+            function refresh() {
60
+                var dateRange = dateSelect.currentRange();
61
+                var startDate = dateRange[0];
62
+                var endDate = dateRange[dateRange.length - 1];
63
+                var buffer = new QBuffer;
64
+                buffer.open(3);
65
+                var output = new XmlWriter(buffer);
66
+                output.writeStartDocument("1.0");
67
+                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">');
68
+                output.writeStartElement("html");
69
+                output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
70
+                output.writeStartElement("head");
71
+                output.writeTextElement("title", TTR("invoicereport", "Invoices"));
72
+                output.writeEndElement();
73
+                output.writeStartElement("body");
74
+                output.writeTextElement("h1", TTR("invoicereport", "Invoices ") + startDate + " - " + endDate);
75
+                output.writeStartElement("table");
76
+                output.writeAttribute("style", "page-break-after: auto; text-align: left");
77
+                output.writeAttribute("rules", "groups");
78
+                output.writeAttribute("cellpadding", "3px");
79
+                output.writeStartElement("thead");
80
+                output.writeStartElement("tr");
81
+                output.writeTextElement("th", TTR("invoicereport", "Date"));
82
+                output.writeTextElement("th", TTR("invoicereport", "Vendor"));
83
+                output.writeTextElement("th", TTR("invoicereport", "Invoice"));
84
+                output.writeTextElement("th", TTR("invoicereport", "Cost"));
85
+                output.writeEndElement();
86
+                output.writeEndElement();
87
+                output.writeStartElement("tbody");
88
+                var query = new QSqlQuery();
89
+                var vendorclause = "";
90
+                if(vendor.currentIndex > 0) {
91
+                    vendorclause = " AND vendor = :vendor";
92
+                }
93
+                var searchclause = "";
94
+                if(search.text.length > 0)
95
+                {
96
+                    searchclause = " AND id IN (SELECT invoice_id FROM invoice_items WHERE item_id IN (SELECT item FROM certifications WHERE certification ~* :p1 UNION SELECT id FROM coffees WHERE origin ~* :p2 UNION SELECT id FROM items WHERE name ~* :p3 UNION SELECT id FROM coffees WHERE reference ~* :p4 UNION SELECT id FROM coffees WHERE region ~* :p5 UNION SELECT id FROM coffees WHERE producer ~* :p6 UNION SELECT id FROM coffees WHERE grade ~* :p7 UNION SELECT id FROM coffees WHERE milling ~* :p8 UNION SELECT id FROM coffees WHERE drying ~* :p9 UNION SELECT id FROM decaf_coffees WHERE decaf_method ~* :p10) OR description ~* :p11 UNION SELECT id FROM invoices WHERE invoice ~* :p12 UNION SELECT id FROM invoices WHERE vendor ~* :p13)";
97
+                }
98
+                query.prepare("SELECT id, time::date, vendor, invoice, (SELECT sum(cost) FROM invoice_items WHERE invoice_id = id)::numeric(12,2) AS cost FROM invoices WHERE time >= :sd AND time < :ed::date + interval '1 day'" + vendorclause + searchclause + " ORDER BY time DESC");
99
+                query.bind(":sd", startDate);
100
+                query.bind(":ed", endDate);
101
+                if(vendorclause.length > 0) {
102
+                    query.bind(":vendor", vendor.currentText);
103
+                }
104
+                if(searchclause.length > 0)
105
+                {
106
+                    var pattern = ".*" + search.text + ".*";
107
+                    query.bind(":p1", pattern);
108
+                    query.bind(":p2", pattern);
109
+                    query.bind(":p3", pattern);
110
+                    query.bind(":p4", pattern);
111
+                    query.bind(":p5", pattern);
112
+                    query.bind(":p6", pattern);
113
+                    query.bind(":p7", pattern);
114
+                    query.bind(":p8", pattern);
115
+                    query.bind(":p9", pattern);
116
+                    query.bind(":p10", pattern);
117
+                    query.bind(":p11", pattern);
118
+                    query.bind(":p12", pattern);
119
+                    query.bind(":p13", pattern);
120
+                }
121
+                query.exec();
122
+                var cost_sum = 0;
123
+                while(query.next()) {
124
+                    output.writeStartElement("tr");
125
+                    output.writeStartElement("td");
126
+                    output.writeStartElement("a");
127
+                    output.writeAttribute("href", "typica://script/" + query.value(0));
128
+                    output.writeCharacters(query.value(1));
129
+                    output.writeEndElement();
130
+                    output.writeEndElement();
131
+                    for(var i = 2; i <= 4; i++) {
132
+                        output.writeTextElement("td", query.value(i));
133
+                    }
134
+                    output.writeEndElement();
135
+                    cost_sum += Number(query.value(4));
136
+                }
137
+                query = query.invalidate();
138
+                output.writeEndElement();
139
+                output.writeStartElement("tfoot");
140
+                output.writeStartElement("tr");
141
+                output.writeEmptyElement("td");
142
+                output.writeEmptyElement("td");
143
+                output.writeTextElement("th", TTR("invoicereport", "Total:"));
144
+                output.writeTextElement("td", Number(cost_sum).toFixed(2));
145
+                output.writeEndElement();
146
+                output.writeEndElement();
147
+                output.writeEndElement();
148
+                output.writeEndElement();
149
+                output.writeEndElement();
150
+                output.writeEndDocument();
151
+                view.setContent(buffer);
152
+                buffer.close();
153
+            }
154
+            refresh();
155
+        ]]>
156
+    </program>

+ 42
- 36
config/Reports/itemtransactions.xml View File

@@ -18,11 +18,11 @@
18 18
 	</menu>
19 19
 	<program>
20 20
 		<![CDATA[
21
-			this.windowTitle = "Typica - Item Transactions";
21
+			this.windowTitle = TTR("item_transactions", "Typica - Item Transactions");
22 22
 			var itemBox = findChildObject(this, 'item');
23 23
 			var unitBox = findChildObject(this, 'unit');
24
-			unitBox.addItem("Kg");
25
-			unitBox.addItem("Lb");
24
+			unitBox.addItem(TTR("item_transactions", "Kg"));
25
+			unitBox.addItem(TTR("item_transactions", "Lb"));
26 26
 			unitBox.currentIndex = QSettings.value("script/report_unit", 1);
27 27
 			unitBox['currentIndexChanged(int)'].connect(function() {
28 28
 				QSettings.setValue("script/report_unit", unitBox.currentIndex);
@@ -45,7 +45,7 @@
45 45
 				output.writeStartElement("html");
46 46
 				output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
47 47
 				output.writeStartElement("head");
48
-				output.writeTextElement("title", "Item Transactions");
48
+				output.writeTextElement("title", TTR("item_transactions", "Item Transactions"));
49 49
 				output.writeStartElement("script");
50 50
 				var scriptFile = new QFile(QSettings.value("config") + "/Scripts/d3.min.js");
51 51
 				scriptFile.open(1);
@@ -64,7 +64,7 @@
64 64
 				
65 65
 				output.writeEndElement();
66 66
 				output.writeStartElement("body");
67
-				output.writeTextElement("h1", "Item Transactions:");
67
+				output.writeTextElement("h1", TTR("item_transactions", "Item Transactions:"));
68 68
 				output.writeStartElement("table");
69 69
 				output.writeStartElement("tr");
70 70
 				output.writeStartElement("td");
@@ -77,11 +77,11 @@
77 77
 				query.exec();
78 78
 				if(query.next()) {
79 79
 					output.writeStartElement("td");
80
-					output.writeTextElement("strong", "Reference: ");
80
+					output.writeTextElement("strong", TTR("item_transactions", "Reference: "));
81 81
 					output.writeTextElement("span", query.value(0));
82 82
 					output.writeEndElement(); // td
83 83
 					output.writeStartElement("td");
84
-					output.writeTextElement("strong", "Category: ");
84
+					output.writeTextElement("strong", TTR("item_transactions", "Category: "));
85 85
 					output.writeTextElement("span", query.value(1));
86 86
 					output.writeEndElement(); //td
87 87
 					output.writeEndElement(); //tr
@@ -91,29 +91,29 @@
91 91
 					if(query.next()) {
92 92
 						output.writeStartElement("tr");
93 93
 						output.writeStartElement("td");
94
-						output.writeTextElement("strong", "Origin: ");
94
+						output.writeTextElement("strong", TTR("item_transactions", "Origin: "));
95 95
 						output.writeTextElement("span", query.value(0));
96 96
 						output.writeEndElement(); // td
97 97
 						output.writeStartElement("td");
98
-						output.writeTextElement("strong", "Region: ");
98
+						output.writeTextElement("strong", TTR("item_transactions", "Region: "));
99 99
 						output.writeTextElement("span", query.value(1));
100 100
 						output.writeEndElement(); // td
101 101
 						output.writeStartElement("td");
102
-						output.writeTextElement("strong", "Producer: ");
102
+						output.writeTextElement("strong", TTR("item_transactions", "Producer: "));
103 103
 						output.writeTextElement("span", query.value(2));
104 104
 						output.writeEndElement(); // td
105 105
 						output.writeEndElement(); // tr
106 106
 						output.writeStartElement("tr");
107 107
 						output.writeStartElement("td");
108
-						output.writeTextElement("strong", "Grade: ");
108
+						output.writeTextElement("strong", TTR("item_transactions", "Grade: "));
109 109
 						output.writeTextElement("span", query.value(3));
110 110
 						output.writeEndElement(); // td
111 111
 						output.writeStartElement("td");
112
-						output.writeTextElement("strong", "Milling: ");
112
+						output.writeTextElement("strong", TTR("item_transactions", "Milling: "));
113 113
 						output.writeTextElement("span", query.value(4));
114 114
 						output.writeEndElement(); // td
115 115
 						output.writeStartElement("td");
116
-						output.writeTextElement("strong", "Drying: ");
116
+						output.writeTextElement("strong", TTR("item_transactions", "Drying: "));
117 117
 						output.writeTextElement("span", query.value(5));
118 118
 						output.writeEndElement(); // td
119 119
 						output.writeEndElement(); // tr
@@ -124,7 +124,7 @@
124 124
 							output.writeStartElement("tr");
125 125
 							output.writeStartElement("td");
126 126
 							output.writeAttribute("colspan", "3");
127
-							output.writeTextElement("strong", "Decaffeination Method: ");
127
+							output.writeTextElement("strong", TTR("item_transactions", "Decaffeination Method: "));
128 128
 							output.writeTextElement("span", query.value(0));
129 129
 							output.writeEndElement(); // td
130 130
 							output.writeEndElement(); // tr
@@ -176,7 +176,7 @@
176 176
 					scriptFile.close();
177 177
 					output.writeEndElement();
178 178
 					
179
-					query.prepare("SELECT time::date, type, quantity / :c1, balance / :c2, (SELECT files FROM roasting_log WHERE roasting_log.time = item_history.time AND item = ANY(unroasted_id)), (SELECT invoice_id FROM invoice_items WHERE item = item_id AND item_history.type = 'PURCHASE'), (SELECT vendor || ' ' || invoice FROM invoices WHERE id = (SELECT invoice_id FROM invoice_items WHERE item = item_id AND item_history.type = 'PURCHASE')), (SELECT name FROM items WHERE id = (SELECT roasted_id FROM roasting_log WHERE roasting_log.time = item_history.time AND item = ANY(unroasted_id))) FROM item_history(:item)");
179
+					query.prepare("SELECT time::date, type, quantity / :c1, balance / :c2, (SELECT files FROM roasting_log WHERE roasting_log.time = item_history.time AND item = ANY(unroasted_id)), (SELECT invoice_id FROM invoice_items WHERE item = item_id AND item_history.type = 'PURCHASE'), (SELECT vendor || ' ' || invoice FROM invoices WHERE id = (SELECT invoice_id FROM invoice_items WHERE item = item_id AND item_history.type = 'PURCHASE')), (SELECT name FROM items WHERE id = (SELECT roasted_id FROM roasting_log WHERE roasting_log.time = item_history.time AND item = ANY(unroasted_id))), customer, reason FROM item_history(:item)");
180 180
 					switch(unitBox.currentIndex)
181 181
 					{
182 182
 						case 0:
@@ -192,39 +192,41 @@
192 192
 					query.exec();
193 193
 					output.writeStartElement("table");
194 194
 					output.writeStartElement("tr");
195
-					output.writeTextElement("th", "Date");
196
-					output.writeTextElement("th", "Type");
197
-					output.writeTextElement("th", "Quantity");
198
-					output.writeTextElement("th", "Balance");
199
-					output.writeTextElement("th", "Record");
195
+					output.writeTextElement("th", TTR("item_transactions", "Date"));
196
+					output.writeTextElement("th", TTR("item_transactions", "Type"));
197
+					output.writeTextElement("th", TTR("item_transactions", "Quantity"));
198
+					output.writeTextElement("th", TTR("item_transactions", "Balance"));
199
+					output.writeTextElement("th", TTR("item_transactions", "Record"));
200 200
 					output.writeEndElement(); // tr
201 201
 					var prev_balance = "0";
202 202
 					var prev_prec = 0;
203 203
 					var cur_prec = 0;
204
+                                        var max_prec = 3;
204 205
 					while(query.next()) {
205 206
 						output.writeStartElement("tr");
206 207
 						output.writeAttribute("class", query.value(1));
207 208
 						output.writeTextElement("td", query.value(0));
208 209
 						output.writeTextElement("td", query.value(1));
210
+                                                var split = prev_balance.split('.');
211
+                                                if(split.length > 1) {
212
+                                                    prev_prec = split[1].length;
213
+                                                } else {
214
+                                                    prev_prec = 0;
215
+                                                }
216
+                                                split = query.value(2).split('.');
217
+                                                if(split.length > 1) {
218
+                                                    cur_prec = split[1].length;
219
+                                                } else {
220
+                                                    cur_prec = 0;
221
+                                                }
222
+                                                var prec = prev_prec > cur_prec ? prev_prec : cur_prec;
223
+                                                var prec = (prec > max_prec ? max_prec : prec);
209 224
 						if(query.value(1) == "INVENTORY") {
210
-							var split = prev_balance.split('.');
211
-							if(split.length > 1) {
212
-								prev_prec = split[1].length;
213
-							} else {
214
-								prev_prec = 0;
215
-							}
216
-							split = query.value(2).split('.');
217
-							if(split.length > 1) {
218
-								cur_prec = split[1].length;
219
-							} else {
220
-								cur_prec = 0;
221
-							}
222
-							var prec = prev_prec > cur_prec ? prev_prec : cur_prec;
223
-							output.writeTextElement("td", (Number(query.value(2)) - Number(prev_balance)).toFixed(prec));
225
+                                                    output.writeTextElement("td", (Number(query.value(2)) - Number(prev_balance)).toFixed(prec));
224 226
 						} else {
225
-							output.writeTextElement("td", query.value(2));
227
+                                                    output.writeTextElement("td", (Number(query.value(2)).toFixed(prec)));
226 228
 						}
227
-						output.writeTextElement("td", query.value(3));
229
+						output.writeTextElement("td", (Number(query.value(3)).toFixed(prec)));
228 230
 						prev_balance = query.value(3);
229 231
 						if(query.value(1) == "PURCHASE") {
230 232
 							output.writeStartElement("td");
@@ -240,6 +242,10 @@
240 242
 							output.writeCDATA(query.value(7) + " " + query.value(4));
241 243
 							output.writeEndElement();
242 244
 							output.writeEndElement();
245
+                                                } else if(query.value(1) == "LOSS") {
246
+                                                    output.writeTextElement("td", query.value(9));
247
+                                                } else if(query.value(1) == "SALE") {
248
+                                                    output.writeTextElement("td", query.value(8));
243 249
 						} else {
244 250
 							output.writeTextElement("td", "");
245 251
 						}

+ 117
- 106
config/Reports/monthcompare.xml View File

@@ -13,20 +13,20 @@
13 13
     </menu>
14 14
     <program>
15 15
         <![CDATA[
16
-            this.windowTitle = "Typica - Previous Year Production Comparison By Month";
16
+            this.windowTitle = TTR("pytdprodcomp", "Typica - Previous Year Production Comparison By Month");
17 17
             var view = findChildObject(this, 'report');
18 18
             var printMenu = findChildObject(this, 'print');
19 19
             printMenu.triggered.connect(function() {
20 20
                 view.print();
21 21
             });
22
-			var unitBox = findChildObject(this, 'unit');
23
-			unitBox.addItem("Kg");
24
-			unitBox.addItem("Lb");
25
-			unitBox.currentIndex = QSettings.value("script/report_unit", 1);
26
-			unitBox['currentIndexChanged(int)'].connect(function() {
27
-				QSettings.setValue("script/report_unit", unitBox.currentIndex);
28
-				refresh();
29
-			});
22
+            var unitBox = findChildObject(this, 'unit');
23
+            unitBox.addItem(TTR("pytdprodcomp", "Kg"));
24
+            unitBox.addItem(TTR("pytdprodcomp", "Lb"));
25
+            unitBox.currentIndex = QSettings.value("script/report_unit", 1);
26
+            unitBox['currentIndexChanged(int)'].connect(function() {
27
+                QSettings.setValue("script/report_unit", unitBox.currentIndex);
28
+                refresh();
29
+            });
30 30
             function refresh() {
31 31
                 var buffer = new QBuffer;
32 32
                 buffer.open(3);
@@ -36,120 +36,131 @@
36 36
                 output.writeStartElement("html");
37 37
                 output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
38 38
                 output.writeStartElement("head");
39
-                output.writeTextElement("title", "Previous Year Production Comparison By Month");
39
+                output.writeTextElement("title", TTR("pytdprodcomp", "Previous Year Production Comparison By Month"));
40 40
                 output.writeEndElement();
41 41
                 output.writeStartElement("body");
42
-                output.writeTextElement("h1", "Previous Year Production Comparison By Month");
43
-				switch(unitBox.currentIndex)
44
-				{
45
-					case 0:
46
-						output.writeTextElement("p", "This report compares total roasted coffee production in kilograms with the previous year on a monthly basis.");
47
-						break;
48
-					case 1:
49
-						output.writeTextElement("p", "This report compares total roasted coffee production in pounds with the previous year on a monthly basis.");
50
-						break;
51
-				}
42
+                output.writeTextElement("h1", TTR("pytdprodcomp", "Previous Year Production Comparison By Month"));
43
+                switch(unitBox.currentIndex)
44
+                {
45
+                    case 0:
46
+                        output.writeTextElement("p", TTR("pytdprodcomp", "This report compares total roasted coffee production in kilograms with the previous year on a monthly basis."));
47
+                        break;
48
+                    case 1:
49
+                        output.writeTextElement("p", TTR("pytdprodcomp", "This report compares total roasted coffee production in pounds with the previous year on a monthly basis."));
50
+                        break;
51
+                }
52 52
                 output.writeStartElement("table");
53 53
                 output.writeAttribute("style", "page-break-after:auto;");
54 54
                 output.writeAttribute("rules", "groups");
55 55
                 output.writeAttribute("cellpadding", "3px");
56 56
                 output.writeStartElement("thead");
57 57
                 output.writeStartElement("tr");
58
-                output.writeTextElement("th", "Month");
59
-				var query = new QSqlQuery();
60
-				query.exec("SELECT EXTRACT(YEAR FROM 'now'::date) AS current_year");
61
-				query.next();
62
-				var current_year = query.value(0);
58
+                output.writeTextElement("th", TTR("pytdprodcomp", "Month"));
59
+                var query = new QSqlQuery();
60
+                query.exec("SELECT EXTRACT(YEAR FROM 'now'::date) AS current_year");
61
+                query.next();
62
+                var current_year = query.value(0);
63 63
                 output.writeTextElement("th", current_year-1);
64 64
                 output.writeTextElement("th", current_year);
65 65
                 output.writeTextElement("th", "Change");
66 66
                 output.writeEndElement();
67 67
                 output.writeEndElement();
68 68
                 output.writeStartElement("tbody");
69
-				var month_names = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
70
-				var current_data = new Array();
71
-				var previous_data = new Array();
72
-				for(var i = 0; i < 12; i++) {
73
-					var q = "SELECT SUM(roasted_quantity) FROM roasting_log WHERE roasted_id IS NOT NULL AND time > '"
74
-					q += current_year;
75
-					q += "-";
76
-					q += 1+i;
77
-					q += "-01' AND time < '";
78
-					if(i == 11) {
79
-						q += 1+current_year;
80
-					} else {
81
-						q += current_year;
82
-					}
83
-					q += "-";
84
-					if(i == 11) {
85
-						q += "01"
86
-					} else {
87
-						q += 2+i;
88
-					}
89
-					q += "-01'";
90
-					query.exec(q);
91
-					query.next();
92
-					current_data.push(query.value(0));
93
-				}
94
-				for(var i = 0; i < 12; i++) {
95
-					var q = "SELECT SUM(roasted_quantity) FROM roasting_log WHERE roasted_id IS NOT NULL AND time > '"
96
-					q += current_year-1;
97
-					q += "-";
98
-					q += 1+i;
99
-					q += "-01' AND time < '";
100
-					if(i == 11) {
101
-						q += current_year;
102
-					} else {
103
-						q += current_year-1;
104
-					}
105
-					q += "-";
106
-					if(i == 11) {
107
-						q += "01"
108
-					} else {
109
-						q += 2+i;
110
-					}
111
-					q += "-01'";
112
-					query.exec(q);
113
-					query.next();
114
-					previous_data.push(query.value(0));
115
-				}
116
-				query = query.invalidate();
117
-				for(var i = 0; i < 12; i++) {
118
-					output.writeStartElement("tr");
119
-					output.writeTextElement("td", month_names[i]);
120
-					switch(unitBox.currentIndex)
121
-					{
122
-						case 0:
123
-							output.writeTextElement("td", Number(previous_data[i] / 2.2).toFixed(2));
124
-							output.writeTextElement("td", Number(current_data[i] / 2.2).toFixed(2));
125
-							output.writeTextElement("td", Number((current_data[i] - previous_data[i]) / 2.2).toFixed(2));
126
-							break;
127
-						case 1:
128
-							output.writeTextElement("td", Number(previous_data[i]).toFixed(2));
129
-							output.writeTextElement("td", Number(current_data[i]).toFixed(2));
130
-							output.writeTextElement("td", Number(current_data[i]-previous_data[i]).toFixed(2));
131
-							break;
132
-					}
133
-					output.writeEndElement();
134
-				}
69
+                var month_names = [TTR("pytdprodcomp", "January"),
70
+                    TTR("pytdprodcomp", "February"),
71
+                    TTR("pytdprodcomp", "March"),
72
+                    TTR("pytdprodcomp", "April"),
73
+                    TTR("pytdprodcomp", "May"),
74
+                    TTR("pytdprodcomp", "June"),
75
+                    TTR("pytdprodcomp", "July"),
76
+                    TTR("pytdprodcomp", "August"),
77
+                    TTR("pytdprodcomp", "September"),
78
+                    TTR("pytdprodcomp", "October"),
79
+                    TTR("pytdprodcomp", "November"),
80
+                    TTR("pytdprodcomp", "December")];
81
+                var current_data = new Array();
82
+                var previous_data = new Array();
83
+                for(var i = 0; i < 12; i++) {
84
+                    var q = "SELECT SUM(roasted_quantity) FROM roasting_log WHERE roasted_id IS NOT NULL AND time > '"
85
+                    q += current_year;
86
+                    q += "-";
87
+                    q += 1+i;
88
+                    q += "-01' AND time < '";
89
+                    if(i == 11) {
90
+                        q += 1+current_year;
91
+                    } else {
92
+                        q += current_year;
93
+                    }
94
+                    q += "-";
95
+                    if(i == 11) {
96
+                        q += "01"
97
+                    } else {
98
+                        q += 2+i;
99
+                    }
100
+                    q += "-01'";
101
+                    query.exec(q);
102
+                    query.next();
103
+                    current_data.push(query.value(0));
104
+                }
105
+                for(var i = 0; i < 12; i++) {
106
+                    var q = "SELECT SUM(roasted_quantity) FROM roasting_log WHERE roasted_id IS NOT NULL AND time > '"
107
+                    q += current_year-1;
108
+                    q += "-";
109
+                    q += 1+i;
110
+                    q += "-01' AND time < '";
111
+                    if(i == 11) {
112
+                        q += current_year;
113
+                    } else {
114
+                        q += current_year-1;
115
+                    }
116
+                    q += "-";
117
+                    if(i == 11) {
118
+                        q += "01"
119
+                    } else {
120
+                        q += 2+i;
121
+                    }
122
+                    q += "-01'";
123
+                    query.exec(q);
124
+                    query.next();
125
+                    previous_data.push(query.value(0));
126
+                }
127
+                query = query.invalidate();
128
+                for(var i = 0; i < 12; i++) {
129
+                    output.writeStartElement("tr");
130
+                    output.writeTextElement("td", month_names[i]);
131
+                    switch(unitBox.currentIndex)
132
+                    {
133
+                        case 0:
134
+                            output.writeTextElement("td", Number(previous_data[i] / 2.2).toFixed(2));
135
+                            output.writeTextElement("td", Number(current_data[i] / 2.2).toFixed(2));
136
+                            output.writeTextElement("td", Number((current_data[i] - previous_data[i]) / 2.2).toFixed(2));
137
+                            break;
138
+                        case 1:
139
+                            output.writeTextElement("td", Number(previous_data[i]).toFixed(2));
140
+                            output.writeTextElement("td", Number(current_data[i]).toFixed(2));
141
+                            output.writeTextElement("td", Number(current_data[i]-previous_data[i]).toFixed(2));
142
+                            break;
143
+                    }
144
+                    output.writeEndElement();
145
+                }
135 146
                 output.writeEndElement();
136 147
                 output.writeStartElement("tfoot");
137
-                output.writeTextElement("th", "Totals");
138
-				var current_total = current_data.reduce(function(a,b) {return Number(a) + Number(b);}, 0);
139
-				var previous_total = previous_data.reduce(function(a,b) {return Number(a) + Number(b);}, 0);
140
-				switch(unitBox.currentIndex)
141
-				{
142
-					case 0:
143
-						output.writeTextElement("td", (previous_total/2.2).toFixed(2));
148
+                output.writeTextElement("th", TTR("pytdprodcomp", "Totals"));
149
+                var current_total = current_data.reduce(function(a,b) {return Number(a) + Number(b);}, 0);
150
+                var previous_total = previous_data.reduce(function(a,b) {return Number(a) + Number(b);}, 0);
151
+                switch(unitBox.currentIndex)
152
+                {
153
+                    case 0:
154
+                        output.writeTextElement("td", (previous_total/2.2).toFixed(2));
144 155
 						output.writeTextElement("td", (current_total/2.2).toFixed(2));
145
-						output.writeTextElement("td", ((current_total-previous_total)/2.2).toFixed(2));
146
-						break;
147
-					case 1:
148
-						output.writeTextElement("td", previous_total.toFixed(2));
149
-						output.writeTextElement("td", current_total.toFixed(2));
150
-						output.writeTextElement("td", (current_total-previous_total).toFixed(2));
151
-						break;
152
-				}
156
+                        output.writeTextElement("td", ((current_total-previous_total)/2.2).toFixed(2));
157
+                        break;
158
+                    case 1:
159
+                        output.writeTextElement("td", previous_total.toFixed(2));
160
+                        output.writeTextElement("td", current_total.toFixed(2));
161
+                        output.writeTextElement("td", (current_total-previous_total).toFixed(2));
162
+                        break;
163
+                }
153 164
                 output.writeEndElement();
154 165
                 output.writeEndElement();
155 166
                 output.writeEndElement();

+ 186
- 0
config/Reports/productionsummary.xml View File

@@ -0,0 +1,186 @@
1
+<window id="dailyproduction">
2
+    <reporttitle>Production:->Production Summary</reporttitle>
3
+    <layout type="vertical">
4
+        <layout type="horizontal">
5
+            <label>Batch Type: </label>
6
+            <sqldrop id="batchtype" />
7
+            <label>Approval: </label>
8
+            <sqldrop id="approval" />
9
+            <daterange id="dates" initial="9" /><!-- Current Month to Date-->
10
+            <label>Weight Unit:</label>
11
+            <sqldrop id="unit" />
12
+            <stretch />
13
+        </layout>
14
+        <webview id="report" />
15
+    </layout>
16
+    <menu name="File">
17
+        <item id="print" shortcut="Ctrl+P">Print</item>
18
+    </menu>
19
+    <program>
20
+        <![CDATA[
21
+            this.windowTitle = TTR("dailyproduction", "Typica - Production Summary");
22
+            var dateSelect = findChildObject(this, 'dates');
23
+            var dateQuery = new QSqlQuery();
24
+            dateQuery.exec("SELECT time::date FROM roasting_log WHERE time = (SELECT min(time) FROM roasting_log) OR time = (SELECT max(time) FROM roasting_log) ORDER BY time ASC");
25
+            dateQuery.next();
26
+            var lifetimeStartDate = dateQuery.value(0);
27
+            var lifetimeEndDate;
28
+            if(dateQuery.next()) {
29
+                lifetimeEndDate = dateQuery.value(0);
30
+            } else {
31
+                lifetimeEndDate = lifetimeStartDate;
32
+            }
33
+            dateSelect.setLifetimeRange(lifetimeStartDate, lifetimeEndDate);
34
+            dateQuery = dateQuery.invalidate();
35
+            var unitBox = findChildObject(this, 'unit');
36
+            unitBox.addItem(TTR("dailyproduction", "Kg"));
37
+            unitBox.addItem(TTR("dailyproduction", "Lb"));
38
+            unitBox.currentIndex = QSettings.value("script/report_unit", 1);
39
+            unitBox['currentIndexChanged(int)'].connect(function() {
40
+                QSettings.setValue("script/report_unit", unitBox.currentIndex);
41
+                refresh();
42
+            });
43
+            var batchType = findChildObject(this, 'batchtype');
44
+            batchType.addItem(TTR("dailyproduction", "Any"));
45
+            batchType.addItem(TTR("dailyproduction", "Production Roasts"));
46
+            batchType.addItem(TTR("dailyproduction", "Sample Roasts"));
47
+            batchType.currentIndex = QSettings.value("script/batchtypefilter", 1);
48
+            batchType['currentIndexChanged(int)'].connect(function() {
49
+                QSettings.setValue("script/batchtypefilter", batchType.currentIndex);
50
+                refresh();
51
+            });
52
+            var approval = findChildObject(this, 'approval');
53
+            approval.addItem(TTR("dailyproduction", "Any"));
54
+            approval.addItem(TTR("dailyproduction", "Approved"));
55
+            approval.addItem(TTR("dailyproduction", "Not Approved"));
56
+            approval.currentIndex = QSettings.value("script/approvalfilter", 1);
57
+            approval['currentIndexChanged(int)'].connect(function() {
58
+                QSettings.setValue("script/approvalfilter", approval.currentIndex);
59
+                refresh();
60
+            });
61
+            var view = findChildObject(this, 'report');
62
+            var printMenu = findChildObject(this, 'print');
63
+            printMenu.triggered.connect(function() {
64
+                view.print();
65
+            });
66
+            function refresh() {
67
+                var buffer = new QBuffer;
68
+                buffer.open(3);
69
+                var output = new XmlWriter(buffer);
70
+                output.writeStartDocument("1.0");
71
+                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">');
72
+                output.writeStartElement("html");
73
+                output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
74
+                output.writeStartElement("head");
75
+                output.writeTextElement("title", TTR("dailyproduction", "Production Summary"));
76
+                output.writeEndElement();
77
+                output.writeStartElement("body");
78
+                var dateRange = dateSelect.currentRange();
79
+                var startDate = dateRange[0];
80
+                var endDate = dateRange[dateRange.length - 1];
81
+                output.writeTextElement("h1", TTR("dailyproduction", "Production Summary: ") + startDate + " - " + endDate);
82
+                var conversion = 1;
83
+                var unitText = TTR("dailyproduction", "Lb");
84
+                if(unitBox.currentIndex == 0) {
85
+                    conversion = 2.2;
86
+                    unitText = TTR("dailyproduction", "Kg");
87
+                }
88
+                var transaction_filter;
89
+                var approval_filter;
90
+                switch(batchType.currentIndex) {
91
+                    case 0:
92
+                        transaction_filter = "";
93
+                        break;
94
+                    case 1:
95
+                        transaction_filter = " AND transaction_type = 'ROAST'";
96
+                        break;
97
+                    case 2:
98
+                        transaction_filter = " AND transaction_type = 'SAMPLEROAST'";
99
+                        break;
100
+                }
101
+                switch(approval.currentIndex) {
102
+                    case 0:
103
+                        approval_filter = "";
104
+                        break;
105
+                    case 1:
106
+                        approval_filter = " AND approval = true";
107
+                        break;
108
+                    case 2:
109
+                        approval_filter = " AND approval = false";
110
+                        break;
111
+                }
112
+                var query = new QSqlQuery();
113
+                query.prepare("SELECT count(1), sum(unroasted_total_quantity) / :c1, sum(roasted_quantity) / :c2 FROM roasting_log WHERE time >= :sd AND time < :ed ::date + interval '1 day'" + transaction_filter + approval_filter);
114
+                query.bind(":c1", conversion);
115
+                query.bind(":c2", conversion);
116
+                query.bind(":sd", startDate);
117
+                query.bind(":ed", endDate);
118
+                query.exec();
119
+                query.next();
120
+                var batchesRoasted = query.value(0);
121
+                var unroastedSum = query.value(1);
122
+                var roastedSum = query.value(2);
123
+                output.writeTextElement("p", "" + roastedSum + " " + unitText + TTR("dailyproduction", " roasted from ") +
124
+                unroastedSum + " " + unitText + TTR("dailyproduction", " green in ") +
125
+                batchesRoasted + TTR("dailyproduction", " batches."));
126
+                query.prepare("SELECT time::date AS date, count(1), sum(unroasted_total_quantity) / :c1, sum(roasted_quantity) / :c2 FROM roasting_log WHERE time >= :sd AND time < :ed ::date + interval '1 day'" + transaction_filter + approval_filter + " GROUP BY date ORDER BY date ASC");
127
+                query.bind(":c1", conversion);
128
+                query.bind(":c2", conversion);
129
+                query.bind(":sd", startDate);
130
+                query.bind(":ed", endDate);
131
+                query.exec();
132
+                output.writeStartElement("table");
133
+                output.writeAttribute("rules", "groups");
134
+                output.writeAttribute("cellpadding", "3px");
135
+                output.writeStartElement("thead");
136
+                output.writeStartElement("tr");
137
+                output.writeTextElement("th", TTR("dailyproduction", "Date"));
138
+                output.writeTextElement("th", TTR("dailyproduction", "Batches"));
139
+                output.writeTextElement("th", TTR("dailyproduction", "Unroasted (") + unitText + ")");
140
+                output.writeTextElement("th", TTR("dailyproduction", "Roasted (") + unitText + ")");
141
+                output.writeEndElement();
142
+                output.writeEndElement();
143
+                output.writeStartElement("tbody");
144
+                while(query.next()) {
145
+                    output.writeStartElement("tr");
146
+                    output.writeStartElement("td");
147
+                    output.writeStartElement("a");
148
+                    output.writeAttribute("href", "typica://script/d" + query.value(0));
149
+                    output.writeCDATA(query.value(0));
150
+                    output.writeEndElement();
151
+                    output.writeEndElement();
152
+                    output.writeTextElement("td", query.value(1));
153
+                    output.writeTextElement("td", query.value(2));
154
+                    output.writeTextElement("td", query.value(3));
155
+                    output.writeEndElement();
156
+                }
157
+                output.writeEndElement();
158
+                output.writeStartElement("tfoot");
159
+                output.writeStartElement("tr");
160
+                output.writeStartElement("td");
161
+                output.writeTextElement("strong", TTR("dailyproduction", "Totals:"));
162
+                output.writeEndElement();
163
+                output.writeTextElement("td", batchesRoasted);
164
+                output.writeTextElement("td", unroastedSum);
165
+                output.writeTextElement("td", roastedSum);
166
+                output.writeEndElement();
167
+                output.writeEndElement();
168
+                output.writeEndElement();
169
+                output.writeEndElement();
170
+                output.writeEndElement();
171
+                output.writeEndDocument();
172
+                view.setContent(buffer);
173
+                buffer.close();
174
+                query = query.invalidate();
175
+            }
176
+            refresh();
177
+            dateSelect.rangeUpdated.connect(refresh);
178
+            view.scriptLinkClicked.connect(function(url) {
179
+                var arg = url.slice(1, url.length).split("-");
180
+                var details = createReport("dailyproductiondetail.xml");
181
+                var selector = findChildObject(details, "reportdate");
182
+                selector.setDate(arg[0], arg[1], arg[2]);
183
+            });
184
+        ]]>
185
+    </program>
186
+</window>

+ 186
- 0
config/Reports/reminders.xml View File

@@ -0,0 +1,186 @@
1
+<window id="remindersreport">
2
+	<reporttitle>Production:->Reminders</reporttitle>
3
+    <layout type="vertical">
4
+        <webview id="report" />
5
+    </layout>
6
+    <menu name="File">
7
+        <item id="print" shortcut="Ctrl+P">Print</item>
8
+    </menu>
9
+    <program>
10
+        <![CDATA[
11
+            var window = this;
12
+            this.windowTitle = TTR("remindersreport", "Typica - Reminders");
13
+            var report = findChildObject(this, 'report');
14
+            var printMenu = findChildObject(this, 'print');
15
+            printMenu.triggered.connect(function() {
16
+                report.print();
17
+            });
18
+            var e = new Array();
19
+            function populateReminderData() {
20
+                e = new Array();
21
+                var query = new QSqlQuery;
22
+                query.exec("SELECT id, reminder FROM reminders");
23
+                while(query.next())
24
+                {
25
+                    var reminder = JSON.parse(query.value(1));
26
+                    reminder.dbid = query.value(0);
27
+                    var start_time = "" + reminder.start_year + "-" + reminder.start_month + "-" + reminder.start_day + " " + reminder.start_time;
28
+                    if(reminder.condition == "PRODUCTIONWEIGHT")
29
+                    {
30
+                        var convert = 1;
31
+                        var unittext = TTR("remindersreport", " Lb");
32
+                        if(reminder.unit == "KG")
33
+                        {
34
+                            convert = 2.2;
35
+                            unittext = TTR("remindersreport", " Kg");
36
+                        }
37
+                        var dq = new QSqlQuery;
38
+                        dq.prepare("SELECT sum(roasted_quantity)/:conversion FROM roasting_log WHERE time > :since");
39
+                        dq.bind(":conversion", convert);
40
+                        dq.bind(":since", start_time);
41
+                        dq.exec();
42
+                        dq.next();
43
+                        var proportion;
44
+                        var remain;
45
+                        if(reminder.value == 0 || (reminder.value < Number(dq.value(0))))
46
+                        {
47
+                            proportion = 1;
48
+                        }
49
+                        else
50
+                        {
51
+                            proportion = Number(dq.value(0)) / reminder.value;
52
+                        }
53
+                        remain = (reminder.value - Number(dq.value(0))).toFixed(0);
54
+                        reminder.completion = proportion;
55
+                        reminder.detail = remain + unittext;
56
+                        dq = dq.invalidate();
57
+                    }
58
+                    else if(reminder.condition == "DAYS")
59
+                    {
60
+                        var dq = new QSqlQuery;
61
+                        dq.prepare("SELECT 'now'::date - :since::date");
62
+                        dq.bind(":since", start_time);
63
+                        dq.exec();
64
+                        dq.next();
65
+                        var proportion;
66
+                        var remain;
67
+                        if(reminder.value == 0 || (reminder.value < Number(dq.value(0))))
68
+                        {
69
+                            proportion = 1;
70
+                        }
71
+                        else
72
+                        {
73
+                            proportion = Number(dq.value(0)) / reminder.value;
74
+                        }
75
+                        remain = reminder.value - Number(dq.value(0));
76
+                        reminder.completion = proportion;
77
+                        reminder.detail = remain + TTR("remindersreport", " Days");
78
+                        dq = dq.invalidate();
79
+                    }
80
+                    else if(reminder.condition == "PRODUCTIONBATCHES")
81
+                    {
82
+                        var dq = new QSqlQuery;
83
+                        dq.prepare("SELECT count(1) FROM roasting_log WHERE time > :since");
84
+                        dq.bind(":since", start_time);
85
+                        dq.exec();
86
+                        dq.next();
87
+                        var proportion;
88
+                        var remain;
89
+                        if(reminder.value == 0 || (reminder.value < Number(dq.value(0))))
90
+                        {
91
+                            proportion = 1;
92
+                        }
93
+                        else
94
+                        {
95
+                            proportion = Number(dq.value(0)) / reminder.value;
96
+                        }
97
+                        remain = reminder.value - Number(dq.value(0));
98
+                        reminder.completion = proportion;
99
+                        reminder.detail = remain + TTR("remindersreport", " Batches");
100
+                        dq = dq.invalidate();
101
+                    }
102
+                    else if(reminder.condition == "PRODUCTIONHOURS")
103
+                    {
104
+                        var dq = new QSqlQuery;
105
+                        dq.prepare("SELECT extract(epoch FROM (SELECT sum(duration) FROM roasting_log WHERE time > :since) / 3600)");
106
+                        dq.bind(":since", start_time);
107
+                        dq.exec();
108
+                        dq.next();
109
+                        var proportion;
110
+                        var remain;
111
+                        if(reminder.value == 0 || (reminder.value < Number(dq.value(0))))
112
+                        {
113
+                            proportion = 1;
114
+                        }
115
+                        else
116
+                        {
117
+                            proportion = Number(dq.value(0)) / reminder.value;
118
+                        }
119
+                        remain = reminder.value - Number(dq.value(0));
120
+                        reminder.completion = proportion;
121
+                        reminder.detail = remain.toFixed(1) + TTR("remindersreport", " Hours");
122
+                        dq = dq.invalidate();
123
+                    }
124
+                    e[reminder.dbid] = reminder;
125
+                }
126
+                query = query.invalidate();
127
+            }
128
+            function refresh() {
129
+                populateReminderData();
130
+                var passedData = e.filter(function(n){return n.hasOwnProperty("completion")}).sort(function(a,b){return b.completion-a.completion});
131
+                var buffer = new QBuffer;
132
+                buffer.open(3);
133
+                var output = new XmlWriter(buffer);
134
+                output.writeStartDocument("1.0");
135
+                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">');
136
+                output.writeStartElement("html");
137
+                output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
138
+                output.writeStartElement("head");
139
+                output.writeTextElement("title", TTR("remindersreport", "Reminders"));
140
+                output.writeEndElement();
141
+                output.writeStartElement("body");
142
+                output.writeTextElement("h1", TTR("remindersreport", "Reminders"));
143
+                output.writeStartElement("a");
144
+                output.writeAttribute("href", "typica://script/0");
145
+                output.writeStartElement("div");
146
+                output.writeAttribute("class", "reminder");
147
+                output.writeStartElement("div");
148
+                output.writeAttribute("class", "nocolor");
149
+                output.writeEndElement();
150
+                output.writeStartElement("span");
151
+                output.writeAttribute("class", "title");
152
+                output.writeCDATA(TTR("remindersreport", "New Reminder"));
153
+                output.writeEndElement();
154
+                output.writeEndElement();
155
+                output.writeEndElement();
156
+                output.writeTextElement("script", "e = " + JSON.stringify(passedData));
157
+                var styleFile = new QFile(QSettings.value("config") + "/Scripts/reminders.css");
158
+                styleFile.open(1);
159
+                output.writeTextElement("style", styleFile.readToString());
160
+                styleFile.close();
161
+                output.writeStartElement("script");
162
+                scriptFile = new QFile(QSettings.value("config") + "/Scripts/reminders.js");
163
+                scriptFile.open(1);
164
+                output.writeCDATA(scriptFile.readToString());
165
+                scriptFile.close();
166
+                output.writeEndElement();
167
+                output.writeEndElement();
168
+                output.writeEndDocument();
169
+                report.setContent(buffer);
170
+                buffer.close();
171
+            }
172
+            refresh();
173
+            var doRefresh = function() {
174
+                refresh();
175
+            }
176
+            report.scriptLinkClicked.connect(function(url) {
177
+                var reminder = createWindow("editreminder");
178
+                if(url != "0")
179
+                {
180
+                    reminder.loadData(e[url]);
181
+                }
182
+                reminder.setRefreshFunction(doRefresh);
183
+            });
184
+        ]]>
185
+    </program>
186
+</window>

+ 110
- 61
config/Reports/rwacp.xml View File

@@ -1,11 +1,17 @@
1 1
 <window id="productionreport">
2
-	<reporttitle>Production:->Recent Average Weekly Coffee Production</reporttitle>
2
+	<reporttitle>Production:->Recent Average Coffee Production</reporttitle>
3 3
     <layout type="vertical">
4 4
         <layout type="horizontal">
5 5
             <label>Sort Order:</label>
6 6
             <sqldrop id="sort" />
7
-			<label>Weight Unit:</label>
8
-			<sqldrop id="unit" />
7
+            <label>Weight Unit:</label>
8
+            <sqldrop id="unit" />
9
+            <label>Batch Type: </label>
10
+            <sqldrop id="batchtype" />
11
+            <label>Days to Average: </label>
12
+            <line id="days" />
13
+            <label>Days to Scale: </label>
14
+            <line id="scale" />
9 15
             <stretch />
10 16
         </layout>
11 17
         <webview id="report" />
@@ -15,22 +21,50 @@
15 21
     </menu>
16 22
     <program>
17 23
         <![CDATA[
18
-            this.windowTitle = "Typica - Recent Average Weekly Coffee Production";
24
+            this.windowTitle = TTR("productionreport", "Typica - Recent Average Coffee Production");
19 25
             var report = findChildObject(this, 'report');
20 26
             var printMenu = findChildObject(this, 'print');
21 27
             printMenu.triggered.connect(function() {
22 28
                 report.print();
23 29
             });
24 30
             var sortBox = findChildObject(this, 'sort');
25
-            sortBox.addItem("Roasted Coffee A-Z");
26
-            sortBox.addItem("Roasted Coffee Z-A");
27
-            sortBox.addItem("Weekly Use Ascending");
28
-            sortBox.addItem("Weekly Use Descending");
31
+            sortBox.addItem(TTR("productionreport", "Roasted Coffee A-Z"));
32
+            sortBox.addItem(TTR("productionreport", "Roasted Coffee Z-A"));
33
+            sortBox.addItem(TTR("productionreport", "Weekly Use Ascending"));
34
+            sortBox.addItem(TTR("productionreport", "Weekly Use Descending"));
29 35
             sortBox.currentIndex = QSettings.value("rwacp_sort", 3);
30
-			var unitBox = findChildObject(this, 'unit');
31
-			unitBox.addItem("Kg");
32
-			unitBox.addItem("Lb");
33
-			unitBox.currentIndex = QSettings.value("script/report_unit", 1);
36
+            var unitBox = findChildObject(this, 'unit');
37
+            unitBox.addItem(TTR("productionreport", "Kg"));
38
+            unitBox.addItem(TTR("productionreport", "Lb"));
39
+            unitBox.currentIndex = QSettings.value("script/report_unit", 1);
40
+            var batchType = findChildObject(this, 'batchtype');
41
+            batchType.addItem(TTR("productionreport", "Any"));
42
+            batchType.addItem(TTR("productionreport", "Production Roasts"));
43
+            batchType.addItem(TTR("productionreport", "Sample Roasts"));
44
+            batchType.currentIndex = QSettings.value("script/racpreport/batchtypefilter", 1);
45
+            batchType['currentIndexChanged(int)'].connect(function() {
46
+                QSettings.setValue("script/racpreport/batchtypefilter",
47
+                                   batchType.currentIndex);
48
+                refresh();
49
+            });
50
+            var daysBox = findChildObject(this, 'days');
51
+            daysBox.text = QSettings.value("script/racpreport/days", "28");
52
+            daysBox.editingFinished.connect(function() {
53
+                if(Number(daysBox.text) < 1) {
54
+                    daysBox.text = QSettings.value("script/racpreport/days");
55
+                }
56
+                QSettings.setValue("script/racpreport/days", daysBox.text);
57
+                refresh();
58
+            });
59
+            var scaleBox = findChildObject(this, 'scale');
60
+            scaleBox.text = QSettings.value("script/racpreport/scale", "7");
61
+            scaleBox.editingFinished.connect(function() {
62
+                if(Number(scaleBox.text) < 1) {
63
+                    scaleBox.text = QSettings.value("script/racpreport/scale");
64
+                }
65
+                QSettings.setValue("script/racpreport/scale", scaleBox.text);
66
+                refresh();
67
+            });
34 68
             function refresh() {
35 69
                 var buffer = new QBuffer;
36 70
                 buffer.open(3);
@@ -40,56 +74,70 @@
40 74
                 output.writeStartElement("html");
41 75
                 output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
42 76
                 output.writeStartElement("head");
43
-                output.writeTextElement("title", "Recent Average Weekly Coffee Production");
77
+                output.writeTextElement("title", TTR("productionreport", "Recent Average Coffee Production"));
44 78
                 output.writeEndElement();
45 79
                 output.writeStartElement("body");
46
-                output.writeTextElement("h1", "Recent Average Weekly Coffee Production");
47
-				switch(unitBox.currentIndex)
48
-				{
49
-					case 0:
50
-						output.writeTextElement("p", "This is a report of average weekly coffee production in kilograms over the past 28 days.");
51
-						break;
52
-					case 1:
53
-						output.writeTextElement("p", "This is a report of average weekly coffee production in pounds over the past 28 days.");
54
-						break;
55
-				}
80
+                output.writeTextElement("h1", TTR("productionreport", "Recent Average Coffee Production"));
81
+                switch(unitBox.currentIndex)
82
+                {
83
+                    case 0:
84
+                        output.writeTextElement("p", TTR("productionreport", "This is a report of average coffee production per ") + scaleBox.text + 
85
+                        TTR("productionreport", " days in kilograms over the past ") + daysBox.text +
86
+                        TTR("productionreport", " days."));
87
+                        break;
88
+                    case 1:
89
+                        output.writeTextElement("p", TTR("productionreport", "This is a report of average coffee production per ") + scaleBox.text +
90
+                        TTR("productionreport", " days in pounds over the past ") + daysBox.text +
91
+                        TTR("productionreport", " days."));
92
+                        break;
93
+                }
56 94
                 output.writeStartElement("table");
57 95
                 output.writeAttribute("rules", "groups");
58 96
                 output.writeAttribute("cellpadding", "3px");
59 97
                 output.writeStartElement("thead");
60 98
                 output.writeStartElement("tr");
61
-                output.writeTextElement("th", "Roasted Coffee");
62
-                output.writeTextElement("th", "Weekly Use");
99
+                output.writeTextElement("th", TTR("productionreport", "Roasted Coffee"));
100
+                output.writeTextElement("th", TTR("productionreport", "Weekly Use"));
63 101
                 output.writeEndElement();
64 102
                 output.writeEndElement();
65 103
                 output.writeStartElement("tbody");
66
-				var q = "SELECT (SELECT name FROM items WHERE id = roasted_id) AS name, ((sum(roasted_quantity) / 4) / :conversion)::numeric(18,2) AS weekly FROM roasting_log WHERE time > current_date - integer '28' AND roasted_quantity > 0 GROUP BY roasted_id ORDER BY "
104
+                var batchClause = "";
105
+                switch(batchType.currentIndex) {
106
+                    case 1:
107
+                        batchClause = " AND transaction_type = 'ROAST'";
108
+                        break;
109
+                    case 2:
110
+                        batchClause = " AND transaction_type = 'SAMPLEROAST'";
111
+                        break;
112
+                }
113
+                var q = "SELECT (SELECT name FROM items WHERE id = roasted_id) AS name, ((sum(roasted_quantity) / :scale) / :conversion)::numeric(18,2) AS weekly FROM roasting_log WHERE time > current_date - integer '" + daysBox.text + "' AND roasted_quantity > 0 " + batchClause + " GROUP BY roasted_id ORDER BY "
67 114
                 switch(sortBox.currentIndex)
68 115
                 {
69 116
                     case 0:
70
-						q += "name ASC";
117
+                        q += "name ASC";
71 118
                         break;
72 119
                     case 1:
73
-						q += "name DESC";
74
-						break;
120
+                        q += "name DESC";
121
+                        break;
75 122
                     case 2:
76
-						q += "weekly ASC";
77
-						break;
123
+                        q += "weekly ASC";
124
+                        break;
78 125
                     case 3:
79
-						q += "weekly DESC";
80
-						break;
126
+                        q += "weekly DESC";
127
+                        break;
128
+                }
129
+                var query = new QSqlQuery();
130
+                query.prepare(q);
131
+                switch(unitBox.currentIndex)
132
+                {
133
+                    case 0:
134
+                        query.bind(":conversion", 2.2);
135
+                        break;
136
+                    case 1:
137
+                        query.bind(":conversion", 1);
138
+                        break;
81 139
                 }
82
-				var query = new QSqlQuery();
83
-				query.prepare(q);
84
-				switch(unitBox.currentIndex)
85
-				{
86
-					case 0:
87
-						query.bind(":conversion", 2.2);
88
-						break;
89
-					case 1:
90
-						query.bind(":conversion", 1);
91
-						break;
92
-				}
140
+                query.bind(":scale", Number(daysBox.text)/Number(scaleBox.text));
93 141
                 query.exec();
94 142
                 while(query.next())
95 143
                 {
@@ -100,22 +148,23 @@
100 148
                 }
101 149
                 output.writeEndElement();
102 150
                 output.writeStartElement("tfoot")
103
-                output.writeTextElement("th", "Total");
104
-                query.prepare("SELECT (sum(roasted_quantity) / 4 / :conversion)::numeric(18,2) FROM roasting_log WHERE time > current_date - integer '28' AND roasted_quantity > 0");
151
+                output.writeTextElement("th", TTR("productionreport", "Total:"));
152
+                query.prepare("SELECT ((sum(roasted_quantity) / :scale) / :conversion)::numeric(18,2) FROM roasting_log WHERE time > current_date - integer '" + daysBox.text + "' AND roasted_quantity > 0 " + batchClause);
105 153
                 switch(unitBox.currentIndex)
106
-				{
107
-					case 0:
108
-						query.bind(":conversion", 2.2);
109
-						break;
110
-					case 1:
111
-						query.bind(":conversion", 1);
112
-						break;
113
-				}
114
-				query.exec();
115
-				query.next();
154
+                {
155
+                    case 0:
156
+                        query.bind(":conversion", 2.2);
157
+                        break;
158
+                    case 1:
159
+                        query.bind(":conversion", 1);
160
+                        break;
161
+                }
162
+                query.bind(":scale", Number(daysBox.text)/Number(scaleBox.text));
163
+                query.exec();
164
+                query.next();
116 165
                 output.writeTextElement("td", query.value(0));
117 166
                 query = query.invalidate();
118
-				output.writeEndElement();
167
+                output.writeEndElement();
119 168
                 output.writeEndElement();
120 169
                 output.writeEndElement();
121 170
                 output.writeEndElement();
@@ -128,10 +177,10 @@
128 177
                 QSettings.setValue("rwacp_sort", sortBox.currentIndex);
129 178
                 refresh();
130 179
             });
131
-			unitBox['currentIndexChanged(int)'].connect(function() {
132
-				QSettings.setValue("script/report_unit", unitBox.currentIndex);
133
-				refresh();
134
-			});
180
+            unitBox['currentIndexChanged(int)'].connect(function() {
181
+                QSettings.setValue("script/report_unit", unitBox.currentIndex);
182
+                refresh();
183
+            });
135 184
         ]]>
136 185
     </program>
137 186
 </window>

+ 68
- 0
config/Scripts/reminders.css View File

@@ -0,0 +1,68 @@
1
+body{
2
+    background-color: #EDE3C0;
3
+}
4
+.reminder{
5
+    background-color: #EEEEEE;
6
+    display: block;
7
+    margin-left: 10px;
8
+    margin-right: 10px;
9
+    margin-top: 10px;
10
+    max-width: 300px;
11
+    padding-right: 5px;
12
+    padding-bottom: 5px;
13
+}
14
+.green{
15
+    width: 150px;
16
+    background-color: #00FF00;
17
+    height: 10px;
18
+    display: block;
19
+}
20
+.yellow{
21
+    width: 150px;
22
+    background-color: #FFFF00;
23
+    height: 10px;
24
+    display: block;
25
+}
26
+.orange{
27
+    width: 150px;
28
+    background-color: #FFA500;
29
+    height: 10px;
30
+    display: block;
31
+}
32
+.red{
33
+    width: 150px;
34
+    background-color: #FF0000;
35
+    height: 10px;
36
+    display: block;
37
+}
38
+.nocolor{
39
+    height: 10px;
40
+    display: block;
41
+}
42
+a{
43
+    text-decoration: none;
44
+    color: #000000;
45
+    display: block;
46
+    max-width: 300px;
47
+}
48
+.reminder span{
49
+    margin-left: 5px;
50
+    display: block;
51
+}
52
+.progress{
53
+    float: left;
54
+    color: #708090;
55
+    font-size: 80%
56
+}
57
+.detail{
58
+    float: right;
59
+    color: #708090;
60
+    font-size: 80%
61
+}
62
+.clearfix{
63
+    clear: both;
64
+}
65
+.title{
66
+    margin-bottom: 5px;
67
+    margin-top: 3px;
68
+}

+ 23
- 0
config/Scripts/reminders.js View File

@@ -0,0 +1,23 @@
1
+e.forEach(function(item) {
2
+    var element = document.createElement("a");
3
+    element.href="typica://script/" + item.dbid
4
+    var colordiv;
5
+    if(item.completion >= 1)
6
+    {
7
+        colordiv = '<div class="red"></div>';
8
+    }
9
+    else
10
+    {
11
+        colordiv = '<div class="orange"></div>';
12
+    }
13
+    if(item.completion < 0.6)
14
+    {
15
+        colordiv = '<div class="yellow"></div>';
16
+    }
17
+    if(item.completion < 0.3)
18
+    {
19
+        colordiv = '<div class="green"></div>';
20
+    }
21
+    element.innerHTML = '<div class="reminder">' + colordiv + '<span class="title">' + item.title + '</span><span class="progress">' + Math.floor(item.completion * 100) + '%</span><span class="detail">' + item.detail + '</span><div class="clearfix"></div></div>';
22
+    document.body.appendChild(element);
23
+})

BIN
config/Translations/config.de.qm View File


+ 2810
- 0
config/Translations/config.de.ts
File diff suppressed because it is too large
View File


+ 2309
- 0
config/Translations/config.ts
File diff suppressed because it is too large
View File


+ 0
- 178
config/Windows/batchdetails.xml View File

@@ -1,178 +0,0 @@
1
-<window id="batchDetails">
2
-    <layout type="vertical">
3
-        <layout type="horizontal">
4
-            <label>Time:</label>
5
-            <line editable="false" id="time" />
6
-        </layout>
7
-        <layout type="horizontal">
8
-            <label>Name:</label>
9
-            <line editable="false" id="name" />
10
-        </layout>
11
-        <layout type="horizontal">
12
-            <label>Green:</label>
13
-            <line editable="false" id="green" />
14
-        </layout>
15
-        <layout type="horizontal">
16
-            <label>Roasted:</label>
17
-            <line editable="false" id="roasted" />
18
-        </layout>
19
-        <layout type="horizontal">
20
-            <label>Duration:</label>
21
-            <line editable="false" id="duration" />
22
-        </layout>
23
-        <layout type="horizontal">
24
-            <label>Approval:</label>
25
-            <line editable="false" id="approval" />
26
-        </layout>
27
-        <layout type="horizontal">
28
-            <label>Files:</label>
29
-			<line id="files" />
30
-		</layout>
31
-        <layout type="vertical">
32
-            <button type="push" id="target" name="Load profile as target" />
33
-            <button type="push" id="view" name="View profile" />
34
-            <button type="push" id="compare" name="Compare profile" />
35
-        </layout>
36
-    </layout>
37
-    <program>
38
-        <![CDATA[
39
-			var widget = this;
40
-			var target = findChildObject(this, 'target');
41
-			var batchTime = findChildObject(this, 'time');
42
-			var filesfield = findChildObject(this, 'files');
43
-            var compare = findChildObject(this, 'compare');
44
-			if(typeof(navigationwindow.loggingWindow) == "undefined")
45
-			{
46
-				compare.enabled = false;
47
-				target.enabled = false;
48
-			}
49
-            compare.clicked.connect(function() {
50
-                files = filesfield.text;
51
-                files = files.replace("{", "(");
52
-                files = files.replace("}", ")");
53
-                var q = "SELECT file, name FROM files WHERE id IN ";
54
-                q = q + files;
55
-                q = q + " AND type = 'profile'";
56
-                query = new QSqlQuery();
57
-                query.exec(q);
58
-                query.next();
59
-                var buffer = new QBuffer(query.value(0));
60
-                var pname = query.value(1);
61
-                query = query.invalidate();
62
-                var startSeries = Number(QSettings.value('cseries', 3));
63
-                var nextSeries = startSeries + 2;
64
-                QSettings.setValue('cseries', nextSeries);
65
-                var input = new XMLInput(buffer, startSeries);
66
-                var graph = findChildObject(navigationwindow.loggingWindow, 'graph');
67
-                input.measure.connect(graph.newMeasurement);
68
-                input.input();
69
-            });
70
-			target.clicked.connect(function() {
71
-				files = filesfield.text;
72
-				files = files.replace("{", "(");
73
-				files = files.replace("}", ")");
74
-				var q = "SELECT file, name FROM files WHERE id IN ";
75
-				q = q + files;
76
-				q = q + " AND type = 'profile'";
77
-				query = new QSqlQuery();
78
-				query.exec(q);
79
-				query.next();
80
-				var targetseries = -1;
81
-				var buffer = new QBuffer(query.value(0));
82
-				var pname = query.value(1);
83
-                query = query.invalidate();
84
-				var input = new XMLInput(buffer, 1);
85
-				var graph = findChildObject(navigationwindow.loggingWindow, 'graph');
86
-				var log = findChildObject(navigationwindow.loggingWindow, 'log');
87
-				log.clear();
88
-				graph.clear();
89
-				input.newTemperatureColumn.connect(log.setHeaderData);
90
-				input.newTemperatureColumn.connect(function(col, text) {
91
-					if(text == navigationwindow.loggingWindow.targetcolumnname)
92
-					{
93
-						targetseries = col;
94
-					}
95
-				});
96
-				input.newAnnotationColumn.connect(log.setHeaderData);
97
-				input.measure.connect(graph.newMeasurement);
98
-				input.measure.connect(log.newMeasurement);
99
-				input.measure.connect(function(data, series) {
100
-					if(series == targetseries)
101
-					{
102
-						targetDetector.newMeasurement(data);
103
-					}
104
-				});
105
-				var lc;
106
-				input.lastColumn.connect(function(c) {
107
-					lc = c;
108
-					QSettings.setValue("liveColumn", c + 1);
109
-					navigationwindow.loggingWindow.postLoadColumnSetup(c);
110
-				});
111
-				input.annotation.connect(log.newAnnotation);
112
-				input.annotation.connect(function(note, tcol, ncol) {
113
-					for(var i = tcol; i < ncol; i++) {
114
-						log.newAnnotation(note, i, ncol);
115
-					}
116
-				});
117
-				navigationwindow.loggingWindow.windowTitle = "Typica - " + pname;
118
-				navigationwindow.loggingWindow.raise();
119
-				navigationwindow.loggingWindow.activateWindow();
120
-				input.input();
121
-				log.newAnnotation("End", 1, lc);
122
-			});
123
-			var viewbutton = findChildObject(this, 'view');
124
-			viewbutton.clicked.connect(function() {
125
-				files = filesfield.text;
126
-				files = files.replace("{", "(");
127
-				files = files.replace("}", ")");
128
-				var q = "SELECT file, name FROM files WHERE id IN ";
129
-				q = q + files;
130
-				q = q + " AND type = 'profile'";
131
-				query = new QSqlQuery();
132
-				query.exec(q);
133
-				if(query.next()) {
134
-					var viewer = createWindow('offline');
135
-					var buffer = new QBuffer(query.value(0));
136
-					var pname = query.value(1);
137
-					query = query.invalidate();
138
-					var input = new XMLInput(buffer, 1);
139
-					var graph = findChildObject(viewer, 'graph');
140
-					var log = findChildObject(viewer, 'log');
141
-					input.newTemperatureColumn.connect(log.setHeaderData);
142
-					input.newTemperatureColumn.connect(function(column) {
143
-						viewer.saveTemperatureColumns.push(column);
144
-					});
145
-					input.newAnnotationColumn.connect(log.setHeaderData);
146
-					input.newAnnotationColumn.connect(function(column) {
147
-						viewer.saveAnnotationColumns.push(column);
148
-					});
149
-					input.measure.connect(graph.newMeasurement);
150
-					input.measure.connect(log.newMeasurement);
151
-					input.annotation.connect(log.newAnnotation);
152
-					var lc;
153
-					input.lastColumn.connect(function(c) {
154
-						lc = c;
155
-						if(c < 3)
156
-						{
157
-							log.setHeaderData(3, "");
158
-						}
159
-					});
160
-					input.annotation.connect(function(note, tcol, ncol) {
161
-						for(var i = tcol; i < ncol; i++) {
162
-							log.newAnnotation(note, i, ncol);
163
-						}
164
-					});
165
-					viewer.windowTitle = "Typica - " + pname;
166
-					viewer.raise();
167
-					viewer.activateWindow();
168
-					input.input();
169
-					log.newAnnotation("End", 1, lc);
170
-				}
171
-				else {
172
-					print("Query returned no results");
173
-				}
174
-				query = query.invalidate();
175
-            });
176
-        ]]>
177
-    </program>
178
-</window>

+ 336
- 325
config/Windows/batchdetailsnew.xml View File

@@ -2,45 +2,59 @@
2 2
     <layout type="vertical">
3 3
         <layout type="horizontal">
4 4
             <button type="push" id="target" name="Load profile as target" />
5
-			<button type="push" id="viewprofile" name="View profile" />
6
-			<button type="push" id="compare" name="Compare profile" />
7
-			<button type="push" id="edit" name="Edit" />
5
+            <button type="push" id="viewprofile" name="View profile" />
6
+            <button type="push" id="compare" name="Compare profile" />
7
+            <button type="push" id="edit" name="Edit" />
8 8
         </layout>
9 9
         <webview id="view" />
10 10
     </layout>
11 11
 	<menu name="File">
12
-		<item id="save" shortcut="Ctrl+S">Save…</item>
12
+            <item id="save" shortcut="Ctrl+S">Save...</item>
13
+            <item id="print" shortcut="Ctrl+P">Print...</item>
13 14
 	</menu>
14 15
     <program>
15 16
         <![CDATA[
16
-			var window = this;
17
-			dataView = findChildObject(this, 'view');
18
-			var fileID;
19
-			var target = findChildObject(this, 'target');
20
-			var compare = findChildObject(this, 'compare');
21
-			var edit = findChildObject(this, 'edit');
22
-			edit.enabled = false;
23
-			if(typeof(Windows.loggingWindow) == "undefined") {
24
-				compare.enabled = false;
25
-				target.enabled = false;
26
-			}
27
-			var tableReference;
28
-			var rowReference;
29
-			var batchTime;
30
-			var machine;
31
-			var approval;
32
-			var annotation;
33
-			edit.clicked.connect(function() {
34
-				var editWindow = createWindow("editBatchDetails");
35
-				editWindow.loadData(window, tableReference, rowReference, batchTime, machine, approval, annotation);
36
-			});
37
-			compare.clicked.connect(function() {
38
-				var query = new QSqlQuery;
39
-				query.prepare("SELECT file, name FROM files WHERE id = :id");
40
-				query.bind(":id", Number(fileID));
41
-				query.exec();
42
-				query.next();
43
-				var buffer = new QBuffer(query.value(0));
17
+            var window = this;
18
+            var unit = QSettings.value("script/history_unit", 1);
19
+            var conversion = 1;
20
+            if(unit == 0)
21
+            {
22
+                conversion = 2.2;
23
+            }
24
+            var unitText = (unit == 0 ? TTR("batchDetails", "Kg") :
25
+                           TTR("batchDetails", "Lb"));
26
+            dataView = findChildObject(this, 'view');
27
+            var printMenu = findChildObject(this, 'print');
28
+            printMenu.triggered.connect(function() {
29
+                dataView.print();
30
+            });
31
+            var fileID;
32
+            var target = findChildObject(this, 'target');
33
+            var compare = findChildObject(this, 'compare');
34
+            var edit = findChildObject(this, 'edit');
35
+            edit.enabled = false;
36
+            if(typeof(Windows.loggingWindow) == "undefined") {
37
+                compare.enabled = false;
38
+                target.enabled = false;
39
+            }
40
+            var tableReference;
41
+            var rowReference;
42
+            var batchTime;
43
+            var machine;
44
+            var approval;
45
+            var annotation;
46
+            var roastWeight;
47
+            edit.clicked.connect(function() {
48
+                var editWindow = createWindow("editBatchDetails");
49
+                editWindow.loadData(window, tableReference, rowReference, batchTime, machine, approval, annotation, roastWeight, unit);
50
+            });
51
+            compare.clicked.connect(function() {
52
+                var query = new QSqlQuery;
53
+                query.prepare("SELECT file, name FROM files WHERE id = :id");
54
+                query.bind(":id", Number(fileID));
55
+                query.exec();
56
+                query.next();
57
+                var buffer = new QBuffer(query.value(0));
44 58
                 var pname = query.value(1);
45 59
                 query = query.invalidate();
46 60
                 var startSeries = Number(QSettings.value('cseries', 3));
@@ -50,300 +64,297 @@
50 64
                 var graph = findChildObject(Windows.loggingWindow, 'graph');
51 65
                 input.measure.connect(graph.newMeasurement);
52 66
                 input.input();
53
-				query = query.invalidate();
54
-			});
55
-			target.clicked.connect(function() {
56
-				var query = new QSqlQuery;
57
-				query.prepare("SELECT file, name FROM files WHERE id = :id");
58
-				query.bind(":id", Number(fileID));
59
-				query.exec();
60
-				query.next();
61
-				var buffer = new QBuffer(query.value(0));
62
-				var pname = query.value(1);
63 67
                 query = query.invalidate();
64
-				var input = new XMLInput(buffer, 1);
65
-				var graph = findChildObject(Windows.loggingWindow, 'graph');
66
-				var log = findChildObject(Windows.loggingWindow, 'log');
67
-				log.clear();
68
-				graph.clear();
69
-				input.newTemperatureColumn.connect(log.setHeaderData);
70
-				input.newTemperatureColumn.connect(function(col, text) {
71
-					if(text == Windows.loggingWindow.targetcolumnname)
72
-					{
73
-						targetseries = col;
74
-					}
75
-				});
76
-				input.newAnnotationColumn.connect(log.setHeaderData);
77
-				input.measure.connect(graph.newMeasurement);
78
-				input.measure.connect(log.newMeasurement);
79
-				input.measure.connect(function(data, series) {
80
-					if(series == targetseries)
81
-					{
82
-						targetDetector.newMeasurement(data);
83
-					}
84
-				});
85
-				var lc;
86
-				input.lastColumn.connect(function(c) {
87
-					lc = c;
88
-					QSettings.setValue("liveColumn", c + 1);
89
-					Windows.loggingWindow.postLoadColumnSetup(c);
90
-				});
91
-				input.annotation.connect(log.newAnnotation);
92
-				input.annotation.connect(function(note, tcol, ncol) {
93
-					for(var i = tcol; i < ncol; i++) {
94
-						log.newAnnotation(note, i, ncol);
95
-					}
96
-				});
97
-				Windows.loggingWindow.windowTitle = "Typica - " + pname;
98
-				Windows.loggingWindow.raise();
99
-				Windows.loggingWindow.activateWindow();
100
-				input.input();
101
-				log.newAnnotation("End", 1, lc);
102
-				query = query.invalidate();
103
-			});
104
-			var viewbutton = findChildObject(this, 'viewprofile');
105
-			viewbutton.clicked.connect(function() {
106
-				var query = new QSqlQuery;
107
-				query.prepare("SELECT file, name FROM files WHERE id = :id");
108
-				query.bind(":id", Number(fileID));
109
-				query.exec();
110
-				if(query.next()) {
111
-					var viewer = createWindow('offline');
112
-					var buffer = new QBuffer(query.value(0));
113
-					var pname = query.value(1);
114
-					query = query.invalidate();
115
-					var input = new XMLInput(buffer, 1);
116
-					var graph = findChildObject(viewer, 'graph');
117
-					var log = findChildObject(viewer, 'log');
118
-					input.newTemperatureColumn.connect(log.setHeaderData);
119
-					input.newTemperatureColumn.connect(function(column) {
120
-						viewer.saveTemperatureColumns.push(column);
121
-					});
122
-					input.newAnnotationColumn.connect(log.setHeaderData);
123
-					input.newAnnotationColumn.connect(function(column) {
124
-						viewer.saveAnnotationColumns.push(column);
125
-					});
126
-					input.measure.connect(graph.newMeasurement);
127
-					input.measure.connect(log.newMeasurement);
128
-					input.annotation.connect(log.newAnnotation);
129
-					var lc;
130
-					input.lastColumn.connect(function(c) {
131
-						lc = c;
132
-						if(c < 3)
133
-						{
134
-							log.setHeaderData(3, "");
135
-						}
136
-					});
137
-					input.annotation.connect(function(note, tcol, ncol) {
138
-						for(var i = tcol; i < ncol; i++) {
139
-							log.newAnnotation(note, i, ncol);
140
-						}
141
-					});
142
-					viewer.windowTitle = "Typica - " + pname;
143
-					viewer.raise();
144
-					viewer.activateWindow();
145
-					graph.updatesEnabled = false;
146
-					log.updatesEnabled = false;
147
-					input.input();
148
-					log.updatesEnabled = true;
149
-					graph.updatesEnabled = true;
150
-					log.newAnnotation("End", 1, lc);
151
-				}
152
-				else {
153
-					print("Query returned no results");
154
-				}
155
-				query = query.invalidate();
156
-			});
157
-			window.loadData = function(table, row) {
158
-				tableReference = table;
159
-				rowReference = row;
160
-				var buffer = new QBuffer;
161
-				buffer.open(3);
162
-				var output = new XmlWriter(buffer);
163
-				output.writeStartDocument("1.0");
164
-				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">');
165
-				output.writeStartElement("html");
166
-				output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
167
-				output.writeStartElement("head");
168
-				output.writeTextElement("title", "Batch Details");
169
-				output.writeEndElement();
170
-				output.writeStartElement("body");
171
-				output.writeStartElement("div");
172
-				output.writeAttribute("style", "float: left; padding-right: 10px");
173
-				output.writeStartElement("p");
174
-				output.writeTextElement("strong", "Roasted Coffee: ");
175
-				output.writeTextElement("span", table.data(row, 2));
176
-				output.writeEndElement();
177
-				output.writeEndElement();
178
-				output.writeStartElement("div");
179
-				output.writeAttribute("style", "float: left");
180
-				output.writeStartElement("p");
181
-				output.writeTextElement("strong", "Roasted On: ");
182
-				batchTime = table.data(row, 0);
183
-				output.writeTextElement("span", batchTime);
184
-				output.writeEndElement();
185
-				output.writeStartElement("p");
186
-				output.writeTextElement("strong", "Batch Duration: ");
187
-				output.writeTextElement("span", table.data(row, 6));
188
-				output.writeEndElement();
189
-				output.writeEndElement();
190
-				output.writeStartElement("div");
191
-				output.writeAttribute("style", "clear: both");
192
-				output.writeEndElement();
193
-				output.writeStartElement("table");
194
-				output.writeStartElement("thead");
195
-				output.writeStartElement("tr");
196
-				output.writeTextElement("th", "Green Coffee");
197
-				output.writeTextElement("th", "Weight (lb)");
198
-				output.writeEndElement();
199
-				output.writeEndElement();
200
-				output.writeStartElement("tbody");
201
-				var query = new QSqlQuery();
202
-				query.prepare("SELECT unroasted_id, unroasted_quantity, approval, files, annotation FROM roasting_log WHERE time = :time AND machine = :machine");
203
-				query.bind(":time", batchTime);
204
-				machine = table.data(row, 1);
205
-				query.bind(":machine", machine);
206
-				query.exec();
207
-				query.next();
208
-				approval = query.value(2);
209
-				annotation = query.value(4);
210
-				var items = sqlToArray(query.value(0));
211
-				var quantities = sqlToArray(query.value(1));
212
-				var nameQuery = new QSqlQuery();
213
-				nameQuery.prepare("SELECT name FROM items WHERE id = :id");
214
-				for(var i = 0; i < items.length; i++) {
215
-					output.writeStartElement("tr");
216
-					nameQuery.bind(":id", items[i]);
217
-					nameQuery.exec();
218
-					nameQuery.next();
219
-					output.writeTextElement("td", nameQuery.value(0) + " (" + items[i] + ")");
220
-					output.writeStartElement("td");
221
-					output.writeAttribute("align", "center");
222
-					output.writeCharacters(quantities[i]);
223
-					output.writeEndElement();
224
-					output.writeEndElement();
225
-				}
226
-				nameQuery = nameQuery.invalidate();
227
-				output.writeEndElement();
228
-				output.writeStartElement("tfoot");
229
-				output.writeStartElement("tr");
230
-				output.writeStartElement("td");
231
-				output.writeAttribute("align", "right");
232
-				output.writeTextElement("strong", "Green Total:");
233
-				output.writeEndElement();
234
-				output.writeStartElement("td");
235
-				output.writeAttribute("align", "center");
236
-				output.writeCharacters(table.data(row, 3));
237
-				output.writeEndElement();
238
-				output.writeEndElement();
239
-				output.writeStartElement("tr");
240
-				output.writeStartElement("td");
241
-				output.writeAttribute("align", "right");
242
-				output.writeTextElement("strong", "Roasted Weight:");
243
-				output.writeEndElement();
244
-				output.writeStartElement("td");
245
-				output.writeAttribute("align", "center");
246
-				output.writeCharacters(table.data(row, 4));
247
-				output.writeEndElement();
248
-				output.writeEndElement();
249
-				output.writeStartElement("tr");
250
-				output.writeStartElement("td");
251
-				output.writeAttribute("align", "right");
252
-				output.writeTextElement("strong", "Weight Loss:");
253
-				output.writeEndElement();
254
-				output.writeStartElement("td");
255
-				output.writeAttribute("align", "center");
256
-				output.writeCharacters(table.data(row, 5));
257
-				output.writeCharacters("%");
258
-				output.writeEndElement();
259
-				output.writeEndElement();
260
-				output.writeEndElement();
261
-				output.writeEndElement();
262
-				output.writeStartElement("p");
263
-				output.writeTextElement("strong", "Approved: ");
264
-				output.writeCharacters(approval);
265
-				output.writeEndElement();
266
-				output.writeStartElement("p");
267
-				output.writeTextElement("strong", "Files: ");
268
-				output.writeCharacters(query.value(3));
269
-				output.writeEndElement();
270
-				output.writeStartElement("p");
271
-				output.writeTextElement("strong", "Annotations:");
272
-				var files = sqlToArray(query.value(3));
273
-				var annotations = annotationFromRecord(files[0]);
274
-				fileID = files[0];
275
-				var buffer2 = new QBuffer("<points>"+annotations+"</points>");
276
-				buffer2.open(1);
277
-				var colQuery = new XQuery;
278
-				colQuery.bind("profile", buffer2);
279
-				colQuery.setQuery('for $i in doc($profile)//tuple[1]/temperature/@series return (string($i), ";")');
280
-				var result = colQuery.exec();
281
-				buffer2.close();
282
-				var seriesHeaders = new Array();
283
-				seriesHeaders.push("Time");
284
-				var records = result.split(";");
285
-				for(var i = 0; i < records.length - 1; i++) {
286
-					seriesHeaders.push(records[i].replace(/^\s+|\s+$/g,""));
287
-				}
288
-				seriesHeaders.push("Note");
289
-				output.writeStartElement("table");
290
-				output.writeStartElement("thead");
291
-				output.writeStartElement("tr");
292
-				for(var i = 0; i < seriesHeaders.length; i++) {
293
-					output.writeTextElement("th", seriesHeaders[i]);
294
-				}
295
-				output.writeEndElement();
296
-				output.writeEndElement();
297
-				buffer2.open(1);
298
-				var rq = 'for $t in doc($profile) //tuple return (string($t/time), ";", ';
299
-				for(var i = 0; i < seriesHeaders.length - 2; i++) {
300
-					rq = rq + 'string($t/temperature[' + Number(i+1) + ']), ";", ';
301
-				}
302
-				rq = rq + 'string($t/annotation), "~")';
303
-				colQuery.setQuery(rq);
304
-				var annotationData = colQuery.exec();
305
-				colQuery = colQuery.invalidate();
306
-				buffer2.close();
307
-				output.writeStartElement("tbody");
308
-				var annotationRecords = annotationData.split("~");
309
-				for(var i = 0; i < annotationRecords.length - 1; i++) {
310
-					output.writeStartElement("tr");
311
-					var annotationRow = annotationRecords[i].split(";");
312
-					for(var j = 0; j < annotationRow.length; j++) {
313
-						output.writeStartElement("td");
314
-						output.writeAttribute("style", "border-left: 1px solid #000000");
315
-						if(j > 0) {
316
-							output.writeAttribute("align", "center");
317
-						}
318
-						if(j > 0 && j < annotationRow.length - 1) {
319
-							output.writeCharacters(Number(annotationRow[j].replace(/^\s+|\s+$/g,"")).toFixed(2));
320
-						}
321
-						else {
322
-							output.writeCharacters(annotationRow[j].replace(/^\s+|\s+$/g,""));
323
-						}
324
-						output.writeEndElement();
325
-					}
326
-					output.writeEndElement();
327
-				}
328
-				output.writeEndElement();
329
-				output.writeEndElement();
330
-				output.writeCharacters(annotation);
331
-				output.writeEndElement();
332
-				output.writeEndElement();
333
-				output.writeEndElement();
334
-				output.writeEndDocument();
335
-				dataView.setContent(buffer);
336
-				buffer.close();
337
-				query = query.invalidate();
338
-				edit.enabled = true;
339
-			};
340
-			var saveMenu = findChildObject(this, 'save');
341
-			saveMenu.triggered.connect(function() {
342
-				var filename = QFileDialog.getSaveFileName(window, "Save Log As…", QSettings.value("script/lastDir", "") + "/");
343
-				if(filename != "") {
344
-					saveFileFromDatabase(fileID, filename);
345
-				}
346
-			});
68
+            });
69
+            target.clicked.connect(function() {
70
+                var query = new QSqlQuery;
71
+                query.prepare("SELECT file, name FROM files WHERE id = :id");
72
+                query.bind(":id", Number(fileID));
73
+                query.exec();
74
+                query.next();
75
+                var buffer = new QBuffer(query.value(0));
76
+                var pname = query.value(1);
77
+                query = query.invalidate();
78
+                var input = new XMLInput(buffer, 1);
79
+                var graph = findChildObject(Windows.loggingWindow, 'graph');
80
+                var log = findChildObject(Windows.loggingWindow, 'log');
81
+                log.clear();
82
+                graph.clear();
83
+                input.newTemperatureColumn.connect(log.setHeaderData);
84
+                input.newTemperatureColumn.connect(function(col, text) {
85
+                    if(text == Windows.loggingWindow.targetcolumnname) {
86
+                        targetseries = col;
87
+                    }
88
+                });
89
+                input.newAnnotationColumn.connect(log.setHeaderData);
90
+                input.measure.connect(graph.newMeasurement);
91
+                input.measure.connect(log.newMeasurement);
92
+                input.measure.connect(function(data, series) {
93
+                    if(series == targetseries) {
94
+                        targetDetector.newMeasurement(data);
95
+                    }
96
+                });
97
+                var lc;
98
+                input.lastColumn.connect(function(c) {
99
+                    lc = c;
100
+                    QSettings.setValue("liveColumn", c + 1);
101
+                    Windows.loggingWindow.postLoadColumnSetup(c);
102
+                });
103
+                input.annotation.connect(log.newAnnotation);
104
+                input.annotation.connect(function(note, tcol, ncol) {
105
+                    for(var i = tcol; i < ncol; i++) {
106
+                        log.newAnnotation(note, i, ncol);
107
+                    }
108
+                });
109
+                Windows.loggingWindow.windowTitle = "Typica - " + pname;
110
+                Windows.loggingWindow.raise();
111
+                Windows.loggingWindow.activateWindow();
112
+                input.input();
113
+                log.newAnnotation(TTR("batchDetails", "End"), 1, lc);
114
+                query = query.invalidate();
115
+            });
116
+            var viewbutton = findChildObject(this, 'viewprofile');
117
+            viewbutton.clicked.connect(function() {
118
+                var query = new QSqlQuery;
119
+                query.prepare("SELECT file, name FROM files WHERE id = :id");
120
+                query.bind(":id", Number(fileID));
121
+                query.exec();
122
+                if(query.next()) {
123
+                    var viewer = createWindow('offline');
124
+                    var buffer = new QBuffer(query.value(0));
125
+                    var pname = query.value(1);
126
+                    query = query.invalidate();
127
+                    var input = new XMLInput(buffer, 1);
128
+                    var graph = findChildObject(viewer, 'graph');
129
+                    var log = findChildObject(viewer, 'log');
130
+                    input.newTemperatureColumn.connect(log.setHeaderData);
131
+                    input.newTemperatureColumn.connect(function(column) {
132
+                        viewer.saveTemperatureColumns.push(column);
133
+                    });
134
+                    input.newAnnotationColumn.connect(log.setHeaderData);
135
+                    input.newAnnotationColumn.connect(function(column) {
136
+                        viewer.saveAnnotationColumns.push(column);
137
+                    });
138
+                    input.measure.connect(graph.newMeasurement);
139
+                    input.measure.connect(log.newMeasurement);
140
+                    input.annotation.connect(log.newAnnotation);
141
+                    var lc;
142
+                    input.lastColumn.connect(function(c) {
143
+                        lc = c;
144
+                        if(c < 3) {
145
+                            log.setHeaderData(3, "");
146
+                        }
147
+                    });
148
+                    input.annotation.connect(function(note, tcol, ncol) {
149
+                        for(var i = tcol; i < ncol; i++) {
150
+                            log.newAnnotation(note, i, ncol);
151
+                        }
152
+                    });
153
+                    viewer.windowTitle = "Typica - " + pname;
154
+                    viewer.raise();
155
+                    viewer.activateWindow();
156
+                    graph.updatesEnabled = false;
157
+                    log.updatesEnabled = false;
158
+                    input.input();
159
+                    log.updatesEnabled = true;
160
+                    graph.updatesEnabled = true;
161
+                    log.newAnnotation("End", 1, lc);
162
+                }
163
+                query = query.invalidate();
164
+            });
165
+            window.loadData = function(table, row) {
166
+                tableReference = table;
167
+                rowReference = row;
168
+                var buffer = new QBuffer;
169
+                buffer.open(3);
170
+                var output = new XmlWriter(buffer);
171
+                output.writeStartDocument("1.0");
172
+                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">');
173
+                output.writeStartElement("html");
174
+                output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
175
+                output.writeStartElement("head");
176
+                output.writeTextElement("title", TTR("batchDetails", "Batch Details"));
177
+                output.writeEndElement();
178
+                output.writeStartElement("body");
179
+                output.writeStartElement("div");
180
+                output.writeAttribute("style", "float: left; padding-right: 10px");
181
+                output.writeStartElement("p");
182
+                output.writeTextElement("strong", TTR("batchDetails", "Roasted Coffee: "));
183
+                output.writeTextElement("span", table.data(row, 2));
184
+                output.writeEndElement();
185
+                output.writeEndElement();
186
+                output.writeStartElement("div");
187
+                output.writeAttribute("style", "float: left");
188
+                output.writeStartElement("p");
189
+                output.writeTextElement("strong", TTR("batchDetails", "Roasted On: "));
190
+                batchTime = table.data(row, 0);
191
+                output.writeTextElement("span", batchTime);
192
+                output.writeEndElement();
193
+                output.writeStartElement("p");
194
+                output.writeTextElement("strong", TTR("batchDetails", "Batch Duration: "));
195
+                output.writeTextElement("span", table.data(row, 6));
196
+                output.writeEndElement();
197
+                output.writeEndElement();
198
+                output.writeStartElement("div");
199
+                output.writeAttribute("style", "clear: both");
200
+                output.writeEndElement();
201
+                output.writeStartElement("table");
202
+                output.writeStartElement("thead");
203
+                output.writeStartElement("tr");
204
+                output.writeTextElement("th", TTR("batchDetails", "Green Coffee"));
205
+                output.writeTextElement("th", TTR("batchDetails", "Weight (") + unitText + ")");
206
+                output.writeEndElement();
207
+                output.writeEndElement();
208
+                output.writeStartElement("tbody");
209
+                var query = new QSqlQuery();
210
+                query.prepare("SELECT unroasted_id, unroasted_quantity, approval, files, annotation, (unroasted_total_quantity/:c1)::numeric(12,3), (roasted_quantity/:c2)::numeric(12,3) FROM roasting_log WHERE time = :time AND machine = :machine");
211
+                query.bind(":time", batchTime);
212
+                query.bind(":c1", conversion);
213
+                query.bind(":c2", conversion);
214
+                machine = table.data(row, 1);
215
+                query.bind(":machine", machine);
216
+                query.exec();
217
+                query.next();
218
+                approval = query.value(2);
219
+                annotation = query.value(4);
220
+                var items = sqlToArray(query.value(0));
221
+                var quantities = sqlToArray(query.value(1));
222
+                var nameQuery = new QSqlQuery();
223
+                nameQuery.prepare("SELECT name FROM items WHERE id = :id");
224
+                for(var i = 0; i < items.length; i++) {
225
+                    output.writeStartElement("tr");
226
+                    nameQuery.bind(":id", items[i]);
227
+                    nameQuery.exec();
228
+                    nameQuery.next();
229
+                    output.writeTextElement("td", nameQuery.value(0) + " (" + items[i] + ")");
230
+                    output.writeStartElement("td");
231
+                    output.writeAttribute("align", "center");
232
+                    output.writeCharacters((Number(quantities[i])/conversion).toFixed(3));
233
+                    output.writeEndElement();
234
+                    output.writeEndElement();
235
+                }
236
+                nameQuery = nameQuery.invalidate();
237
+                output.writeEndElement();
238
+                output.writeStartElement("tfoot");
239
+                output.writeStartElement("tr");
240
+                output.writeStartElement("td");
241
+                output.writeAttribute("align", "right");
242
+                output.writeTextElement("strong", TTR("batchDetails", "Green Total:"));
243
+                output.writeEndElement();
244
+                output.writeStartElement("td");
245
+                output.writeAttribute("align", "center");
246
+                output.writeCharacters(query.value(5));
247
+                output.writeEndElement();
248
+                output.writeEndElement();
249
+                output.writeStartElement("tr");
250
+                output.writeStartElement("td");
251
+                output.writeAttribute("align", "right");
252
+                output.writeTextElement("strong", TTR("batchDetails", "Roasted Weight:"));
253
+                output.writeEndElement();
254
+                output.writeStartElement("td");
255
+                output.writeAttribute("align", "center");
256
+                output.writeCharacters(query.value(6));
257
+                roastWeight = query.value(6);
258
+                output.writeEndElement();
259
+                output.writeEndElement();
260
+                output.writeStartElement("tr");
261
+                output.writeStartElement("td");
262
+                output.writeAttribute("align", "right");
263
+                output.writeTextElement("strong", TTR("batchDetails", "Weight Loss:"));
264
+                output.writeEndElement();
265
+                output.writeStartElement("td");
266
+                output.writeAttribute("align", "center");
267
+                output.writeCharacters(table.data(row, 5));
268
+                output.writeCharacters("%");
269
+                output.writeEndElement();
270
+                output.writeEndElement();
271
+                output.writeEndElement();
272
+                output.writeEndElement();
273
+                output.writeStartElement("p");
274
+                output.writeTextElement("strong", TTR("batchDetails", "Approved: "));
275
+                output.writeCharacters(approval);
276
+                output.writeEndElement();
277
+                output.writeStartElement("p");
278
+                output.writeTextElement("strong", TTR("batchDetails", "Files: "));
279
+                output.writeCharacters(query.value(3));
280
+                output.writeEndElement();
281
+                output.writeStartElement("p");
282
+                output.writeTextElement("strong", TTR("batchDetails", "Annotations:"));
283
+                var files = sqlToArray(query.value(3));
284
+                var annotations = annotationFromRecord(files[0]);
285
+                fileID = files[0];
286
+                var buffer2 = new QBuffer("<points>"+annotations+"</points>");
287
+                buffer2.open(1);
288
+                var colQuery = new XQuery;
289
+                colQuery.bind("profile", buffer2);
290
+                colQuery.setQuery('for $i in doc($profile)//tuple[1]/temperature/@series return (string($i), ";")');
291
+                var result = colQuery.exec();
292
+                buffer2.close();
293
+                var seriesHeaders = new Array();
294
+                seriesHeaders.push(TTR("batchDetails", "Time"));
295
+                var records = result.split(";");
296
+                for(var i = 0; i < records.length - 1; i++) {
297
+                    seriesHeaders.push(records[i].replace(/^\s+|\s+$/g,""));
298
+                }
299
+                seriesHeaders.push(TTR("batchDetails", "Note"));
300
+                output.writeStartElement("table");
301
+                output.writeStartElement("thead");
302
+                output.writeStartElement("tr");
303
+                for(var i = 0; i < seriesHeaders.length; i++) {
304
+                        output.writeTextElement("th", seriesHeaders[i]);
305
+                }
306
+                output.writeEndElement();
307
+                output.writeEndElement();
308
+                buffer2.open(1);
309
+                var rq = 'for $t in doc($profile) //tuple return (string($t/time), ";", ';
310
+                for(var i = 0; i < seriesHeaders.length - 2; i++) {
311
+                        rq = rq + 'string($t/temperature[' + Number(i+1) + ']), ";", ';
312
+                }
313
+                rq = rq + 'string($t/annotation), "~")';
314
+                colQuery.setQuery(rq);
315
+                var annotationData = colQuery.exec();
316
+                colQuery = colQuery.invalidate();
317
+                buffer2.close();
318
+                output.writeStartElement("tbody");
319
+                var annotationRecords = annotationData.split("~");
320
+                for(var i = 0; i < annotationRecords.length - 1; i++) {
321
+                    output.writeStartElement("tr");
322
+                    var annotationRow = annotationRecords[i].split(";");
323
+                    for(var j = 0; j < annotationRow.length; j++) {
324
+                        output.writeStartElement("td");
325
+                        output.writeAttribute("style", "border-left: 1px solid #000000");
326
+                        if(j > 0) {
327
+                            output.writeAttribute("align", "center");
328
+                        }
329
+                        if(j > 0 && j < annotationRow.length - 1) {
330
+                            output.writeCharacters(Number(annotationRow[j].replace(/^\s+|\s+$/g,"")).toFixed(2));
331
+                        }
332
+                        else {
333
+                            output.writeCharacters(annotationRow[j].replace(/^\s+|\s+$/g,""));
334
+                        }
335
+                        output.writeEndElement();
336
+                    }
337
+                    output.writeEndElement();
338
+                }
339
+                output.writeEndElement();
340
+                output.writeEndElement();
341
+                output.writeCharacters(annotation);
342
+                output.writeEndElement();
343
+                output.writeEndElement();
344
+                output.writeEndElement();
345
+                output.writeEndDocument();
346
+                dataView.setContent(buffer);
347
+                buffer.close();
348
+                query = query.invalidate();
349
+                edit.enabled = true;
350
+            };
351
+            var saveMenu = findChildObject(this, 'save');
352
+            saveMenu.triggered.connect(function() {
353
+                var filename = QFileDialog.getSaveFileName(window, TTR("batchDetails", "Save Log As..."), QSettings.value("script/lastDir", "") + "/");
354
+                if(filename != "") {
355
+                    saveFileFromDatabase(fileID, filename);
356
+                }
357
+            });
347 358
         ]]>
348 359
     </program>
349 360
 </window>

+ 56
- 40
config/Windows/editbatchdetails.xml View File

@@ -1,47 +1,63 @@
1 1
 <window id="editBatchDetails">
2 2
     <layout type="vertical">
3
-		<button type="check" name="Approved" id="approval" />
4
-		<layout type="horizontal">
5
-			<label>Annotation</label>
6
-			<textarea id="annotation" />
7
-		</layout>
8
-		<button type="push" id="submit" name="Submit" />
9
-	</layout>
3
+        <layout type="horizontal">
4
+            <label>Roasted Weight: </label>
5
+            <line id="roasted" validator="numeric" />
6
+            <line id="roastunit" writable="false" />
7
+        </layout>
8
+        <button type="check" name="Approved" id="approval" />
9
+        <layout type="horizontal">
10
+            <label>Annotation</label>
11
+            <textarea id="annotation" />
12
+        </layout>
13
+        <button type="push" id="submit" name="Submit" />
14
+    </layout>
10 15
     <program>
11 16
         <![CDATA[
12
-			var window = this;
13
-			var approvalButton = findChildObject(this, 'approval');
14
-			var annotationField = findChildObject(this, 'annotation');
15
-			var parentWindow;
16
-			var tableReference;
17
-			var rowReference;
18
-			var timeKey;
19
-			var machineKey;
20
-			var submit = findChildObject(this, 'submit');
21
-			submit.enabled = false;
22
-			this.loadData = function(parent, table, row, time, machine, approval, annotation) {
23
-				parentWindow = parent;
24
-				tableReference = table;
25
-				rowReference = row;
26
-				if(approval == "true") {
27
-					approvalButton.checked = true;
28
-				}
29
-				annotationField.plainText = annotation;
30
-				timeKey = time;
31
-				machineKey = machine;
32
-				submit.enabled = true;
33
-			};
34
-			submit.clicked.connect(function() {
35
-				var query = new QSqlQuery;
36
-				query.prepare("UPDATE roasting_log SET approval = :approval, annotation = :annotation WHERE time = :time AND machine = :machine");
37
-				query.bind("approval", approvalButton.checked);
38
-				query.bind("annotation", annotationField.plainText);
39
-				query.bind("time", timeKey);
40
-				query.bind("machine", machineKey);
41
-				query.exec();
42
-				parentWindow.loadData(tableReference, rowReference);
43
-				window.close();
44
-			});
17
+            var window = this;
18
+            var approvalButton = findChildObject(this, 'approval');
19
+            var annotationField = findChildObject(this, 'annotation');
20
+            var parentWindow;
21
+            var tableReference;
22
+            var rowReference;
23
+            var timeKey;
24
+            var machineKey;
25
+            var submit = findChildObject(this, 'submit');
26
+            submit.enabled = false;
27
+            var roastedEdit = findChildObject(this, 'roasted');
28
+            var unitEdit = findChildObject(this, 'roastunit');
29
+            var conversion = 1;
30
+            this.loadData = function(parent, table, row, time, machine, approval, annotation, roastWeight, unit) {
31
+                parentWindow = parent;
32
+                tableReference = table;
33
+                rowReference = row;
34
+                if(approval == "true") {
35
+                    approvalButton.checked = true;
36
+                }
37
+                annotationField.plainText = annotation;
38
+                timeKey = time;
39
+                machineKey = machine;
40
+                submit.enabled = true;
41
+                roastedEdit.text = roastWeight;
42
+                if(unit == 0) {
43
+                    unitEdit.text = TTR("editBatchDetails", "Kg");
44
+                    conversion = 2.2;
45
+                } else {
46
+                    unitEdit.text = TTR("editBatchDetails", "Lb");
47
+                }
48
+            };
49
+            submit.clicked.connect(function() {
50
+                var query = new QSqlQuery;
51
+                query.prepare("UPDATE roasting_log SET roasted_quantity = :roasted, approval = :approval, annotation = :annotation WHERE time = :time AND machine = :machine");
52
+                query.bind(":approval", approvalButton.checked);
53
+                query.bind(":annotation", annotationField.plainText);
54
+                query.bind(":roasted", Number(roastedEdit.text)/conversion);
55
+                query.bind(":time", timeKey);
56
+                query.bind(":machine", Number(machineKey));
57
+                query.exec();
58
+                parentWindow.loadData(tableReference, rowReference);
59
+                window.close();
60
+            });
45 61
         ]]>
46 62
     </program>
47 63
 </window>

+ 6
- 4
config/Windows/editfee.xml View File

@@ -15,22 +15,24 @@
15 15
 	<program>
16 16
 		<![CDATA[
17 17
 			window = this;
18
+                        var invoiceID = 0;
18 19
 			this.windowTitle = 'Typica - Fee Detail';
19 20
 			var descField = findChildObject(this, 'description');
20 21
 			var costField = findChildObject(this, 'cost');
21 22
 			this.dataSet = function() {
22 23
 				descField.text = window.rowData[2];
23
-				costField.text = window.rowData[4];
24
+				costField.text = window.rowData[7];
25
+                                invoiceID = window.invoiceID;
24 26
 			};
25 27
 			button = findChildObject(this, 'submit');
26 28
 			button.clicked.connect(function() {
27 29
 				var query = new QSqlQuery();
28
-				query.prepare("UPDATE invoice_items SET description = :name, cost = :cost WHERE invoice_id = :id AND record_type = 'FEE' AND item_id = NULL AND description = :oldname AND cost = :oldcost");
30
+				query.prepare("UPDATE invoice_items SET description = :name, cost = :cost WHERE invoice_id = :id AND record_type = 'FEE' AND description = :oldname AND cost = :oldcost");
29 31
 				query.bind(":name", descField.text);
30 32
 				query.bind(":cost", Number(costField.text));
31
-				query.bind(":id", Number(window.rowData[1]);
33
+				query.bind(":id", Number(invoiceID));
32 34
 				query.bind(":oldname", window.rowData[2]);
33
-				query.bind(":oldcost", window.rowData[4]);
35
+				query.bind(":oldcost", window.rowData[7]);
34 36
 				query.exec();
35 37
 				query = query.invalidate();
36 38
 				window.close();

+ 210
- 0
config/Windows/editreminder.xml View File

@@ -0,0 +1,210 @@
1
+<window id="editreminder">
2
+    <layout type="vertical">
3
+        <layout type="horizontal">
4
+            <label>Name:</label>
5
+            <line id="name" />
6
+        </layout>
7
+        <label>Description:</label>
8
+        <textarea id="description" />
9
+        <layout type="horizontal">
10
+            <label>Since:</label>
11
+            <calendar time="true" id="since" />
12
+            <button name="Now" id="now" type="push" />
13
+        </layout>
14
+        <layout type="horizontal">
15
+            <label>Condition:</label>
16
+            <sqldrop id="condition" />
17
+        </layout>
18
+        <layout type="stack" id="conditionvars">
19
+            <page>
20
+                <layout type="horizontal">
21
+                    <line id="productionweight" validator="numeric" />
22
+                    <sqldrop id="productionunit" />
23
+                </layout>
24
+            </page>
25
+            <page>
26
+                <layout type="horizontal">
27
+                    <line id="days" validator="numeric" />
28
+                </layout>
29
+            </page>
30
+            <page>
31
+                <layout type="horizontal">
32
+                    <line id="batches" validator="numeric" />
33
+                </layout>
34
+            </page>
35
+            <page>
36
+                <layout type="horizontal">
37
+                    <line id="roastingtime" validator="numeric" />
38
+                </layout>
39
+            </page>
40
+        </layout>
41
+        <layout type="horizontal">
42
+            <button name="Delete" id="delete" type="push" />
43
+            <button name="Save" id="save" type="push" />
44
+        </layout>
45
+    </layout>
46
+    <program>
47
+        <![CDATA[
48
+            var window = this;
49
+            window.setRefreshFunction = function(callback) {
50
+                window.refreshCallback = callback;
51
+            }
52
+            window.currentReminder = new Object();
53
+            this.windowTitle = TTR("editreminder", "Typica - Edit Reminder");
54
+            var unitBox = findChildObject(this, 'productionunit');
55
+            unitBox.addItem(TTR("editreminder", "Kg"));
56
+            unitBox.addItem(TTR("editreminder", "Lb"));
57
+            unitBox.currentIndex = QSettings.value("script/report_unit", 1);
58
+            var condition = findChildObject(this, 'condition');
59
+            condition.addItem(TTR("editreminder", "Roasted Coffee Production"));
60
+            condition.addItem(TTR("editreminder", "Days"));
61
+            condition.addItem(TTR("editreminder", "Batches Roasted"));
62
+            condition.addItem(TTR("editreminder", "Hours of Roasting Time"));
63
+            var conditionpage = findChildObject(this, 'conditionvars');
64
+            condition['currentIndexChanged(int)'].connect(function(c) {
65
+                conditionpage.setCurrentIndex(c);
66
+            });
67
+            var namefield = findChildObject(this, 'name');
68
+            var descfield = findChildObject(this, 'description');
69
+            var sincefield = findChildObject(this, 'since');
70
+            var prodweight = findChildObject(this, 'productionweight');
71
+            var daysfield = findChildObject(this, 'days');
72
+            var batchesfield = findChildObject(this, 'batches');
73
+            var hoursfield = findChildObject(this, 'roastingtime');
74
+            var time = new QTime();
75
+            var nowButton = findChildObject(this, 'now');
76
+            nowButton.clicked.connect(function() {
77
+                sincefield.setToCurrentTime();
78
+            });
79
+            var deleteButton = findChildObject(this, 'delete');
80
+            deleteButton.enabled = false;
81
+            deleteButton.clicked.connect(function() {
82
+                var query = new QSqlQuery;
83
+                query.prepare("DELETE FROM reminders WHERE id = :id");
84
+                query.bind("id", window.currentReminder.dbid);
85
+                query.exec();
86
+                query = query.invalidate();
87
+                window.refreshCallback();
88
+                window.close();
89
+            });
90
+            var saveButton = findChildObject(this, 'save');
91
+            saveButton.clicked.connect(function() {
92
+                r = new Object();
93
+                r.title = namefield.text;
94
+                r.description = descfield.plainText;
95
+                r.start_year = sincefield.year();
96
+                r.start_month = sincefield.month();
97
+                r.start_day = sincefield.day();
98
+                r.start_time = sincefield.time.toString("hh:mm:ss");
99
+                switch(condition.currentIndex)
100
+                {
101
+                    case 0:
102
+                        r.condition = "PRODUCTIONWEIGHT";
103
+                        r.value = Number(prodweight.text);
104
+                        if(unitBox.currentIndex == 0)
105
+                        {
106
+                            r.unit = "KG";
107
+                        }
108
+                        else
109
+                        {
110
+                            r.unit = "LB";
111
+                        }
112
+                        break;
113
+                    case 1:
114
+                        r.condition = "DAYS";
115
+                        r.value = daysfield.text;
116
+                        break;
117
+                    case 2:
118
+                        r.condition = "PRODUCTIONBATCHES";
119
+                        r.value = batchesfield.text;
120
+                        break;
121
+                    case 3:
122
+                        r.condition = "PRODUCTIONHOURS";
123
+                        r.value = hoursfield.text;
124
+                        break;
125
+                }
126
+                var query = new QSqlQuery;
127
+                if(window.currentReminder.hasOwnProperty("dbid"))
128
+                {
129
+                    query.prepare("UPDATE reminders SET reminder = :reminder WHERE id = :id");
130
+                    query.bind("reminder", JSON.stringify(r));
131
+                    query.bind("id", window.currentReminder.dbid);
132
+                }
133
+                else
134
+                {
135
+                    query.prepare("INSERT INTO reminders (id, reminder) VALUES(default, :reminder)");
136
+                    query.bind("reminder", JSON.stringify(r));
137
+                }
138
+                query.exec();
139
+                query = query.invalidate();
140
+                window.refreshCallback();
141
+                window.close();
142
+            });
143
+            window.loadData = function(reminder) {
144
+                window.currentReminder = reminder;
145
+                deleteButton.enabled = reminder.hasOwnProperty("dbid");
146
+                if(reminder.hasOwnProperty("title"))
147
+                {
148
+                    namefield.text = reminder.title;
149
+                }
150
+                if(reminder.hasOwnProperty("description"))
151
+                {
152
+                    descfield.plainText = reminder.description;
153
+                }
154
+                if(reminder.hasOwnProperty("start_year") &&
155
+                   reminder.hasOwnProperty("start_month") &&
156
+                   reminder.hasOwnProperty("start_day"))
157
+                {
158
+                    sincefield.setDate(reminder.start_year, reminder.start_month, reminder.start_day);
159
+                }
160
+                if(reminder.hasOwnProperty("start_time"))
161
+                {
162
+                    sincefield.time = time.fromString(reminder.start_time, "hh:mm:ss");
163
+                }
164
+                if(reminder.hasOwnProperty("condition"))
165
+                {
166
+                    if(reminder.condition == "PRODUCTIONWEIGHT")
167
+                    {
168
+                        condition.setCurrentIndex(0);
169
+                        if(reminder.hasOwnProperty("value"))
170
+                        {
171
+                            prodweight.text = reminder.value;
172
+                        }
173
+                        if(reminder.unit == "KG")
174
+                        {
175
+                            unitBox.setCurrentIndex(0);
176
+                        }
177
+                        else
178
+                        {
179
+                            unitBox.setCurrentIndex(1);
180
+                        }
181
+                    }
182
+                    else if(reminder.condition == "DAYS")
183
+                    {
184
+                        condition.setCurrentIndex(1);
185
+                        if(reminder.hasOwnProperty("value"))
186
+                        {
187
+                            daysfield.text = reminder.value;
188
+                        }
189
+                    }
190
+                    else if(reminder.condition == "PRODUCTIONBATCHES")
191
+                    {
192
+                        condition.setCurrentIndex(2);
193
+                        if(reminder.hasOwnProperty("value"))
194
+                        {
195
+                            batchesfield.text = reminder.value;
196
+                        }
197
+                    }
198
+                    else if(reminder.condition == "PRODUCTIONHOURS")
199
+                    {
200
+                        condition.setCurrentIndex(3);
201
+                        if(reminder.hasOwnProperty("value"))
202
+                        {
203
+                            hoursfield.text = reminder.value;
204
+                        }
205
+                    }
206
+                }
207
+            }
208
+        ]]>
209
+    </program>
210
+</window>

+ 6
- 6
config/Windows/export.xml View File

@@ -72,7 +72,7 @@
72 72
             var titleField = findChildObject(window, 'title');
73 73
             var subTitleField = findChildObject(window, 'subtitle');
74 74
             exportButton.clicked.connect(function() {
75
-                var filename = QFileDialog.getSaveFileName(window, "Export XHTML+SVG As...", QSettings.value("script/lastDir", "") + "/");
75
+                var filename = QFileDialog.getSaveFileName(window, TTR("exportWindow", "Export XHTML+SVG As..."), QSettings.value("script/lastDir", "") + "/");
76 76
                 if(filename != "")
77 77
                 {
78 78
                     window.log.clearOutputColumns();
@@ -284,11 +284,11 @@ print("Column headers output");
284 284
                     output.writeAttribute("transform", "rotate(-90 40,150)");
285 285
                     if(log.displayUnits() == 10143)
286 286
                     {
287
-                        output.writeCDATA("Temperature (°C)");
287
+                        output.writeCDATA(TTR("exportWindow", "Temperature (°C)"));
288 288
                     }
289 289
                     else
290 290
                     {
291
-                        output.writeCDATA("Temperature (°F)");
291
+                        output.writeCDATA(TTR("exportWindow", "Temperature (°F)"));
292 292
                     }
293 293
                     output.writeEndElement();
294 294
                     output.writeStartElement("text");
@@ -679,7 +679,7 @@ print("Column headers output");
679 679
                     if(lossField.text != "")
680 680
                     {
681 681
                         output.writeStartElement("p");
682
-                        output.writeCDATA("Weight loss: ");
682
+                        output.writeCDATA(TTR("exportWindow", "Weight loss: "));
683 683
                         output.writeCDATA(lossField.text);
684 684
                         if(tolField.text != "")
685 685
                         {
@@ -691,10 +691,10 @@ print("Column headers output");
691 691
                         output.writeStartElement("table");
692 692
                         output.writeStartElement("tr");
693 693
                         output.writeStartElement("th");
694
-                        output.writeCDATA("Roasted");
694
+                        output.writeCDATA(TTR("exportWindow", "Roasted"));
695 695
                         output.writeEndElement();
696 696
                         output.writeStartElement("th");
697
-                        output.writeCDATA("Green");
697
+                        output.writeCDATA(TTR("exportWindow", "Green"));
698 698
                         output.writeEndElement();
699 699
                         output.writeEndElement();
700 700
                         var green;

+ 0
- 276
config/Windows/externalroaster.xml View File

@@ -1,276 +0,0 @@
1
-<window id="basicWindow">
2
-    <layout type="vertical">
3
-        <splitter type = "vertical" id = "main">
4
-            <splitter type = "horizontal" id = "indicators">
5
-                <decoration name="Bean Temperature" type="vertical">
6
-                    <lcdtemperature id = "beans" />
7
-                </decoration>
8
-                <decoration name="Air Temperature" type="vertical">
9
-                    <lcdtemperature id = "environment" />
10
-                </decoration>
11
-                <decoration name="Batch Timer" type="vertical">
12
-                    <lcdtimer format="mm:ss" id="batch" />
13
-                </decoration>
14
-            </splitter>
15
-            <widget id="widget">
16
-                <layout type="horizontal">
17
-                    <button name="Start Batch" type="push" id="startbutton" />
18
-                    <button name="Stop Batch" type="annotation" id="stopbutton" series="1" column="3" annotation="End" />
19
-                    <spinbox id="manometer" series="1" column="3" min="0" max="10" step="0.1" decimals="1" />
20
-                    <button name="New Sample" type="annotation" id="sample" series="1" column="3" annotation="Sample %1" />
21
-                </layout>
22
-            </widget>
23
-            <splitter type="horizontal" id="logsplit">
24
-                <measurementtable id="log">
25
-                    <column>Time</column>
26
-                    <column>Bean</column>
27
-                    <column>Air</column>
28
-                    <column>Note</column>
29
-                    <column>.</column>
30
-                    <column>.</column>
31
-                    <column>.</column>
32
-                </measurementtable>
33
-                <graph id="graph" />
34
-            </splitter>
35
-        </splitter>
36
-    </layout>
37
-    <menu name="File">
38
-        <item id="open" shortcut="Ctrl+O">Open…</item>
39
-        <item id="save" shortcut="Ctrl+S">Save…</item>
40
-        <item id="export">Export CSV…</item>
41
-        <item id="quit" shortcut="Ctrl+Q">Quit</item>
42
-    </menu>
43
-    <menu name="Batch">
44
-        <item id="new" shortcut="Ctrl+N">New Batch…</item>
45
-    </menu>
46
-    <menu name="Log">
47
-        <item id="clear" shortcut="Ctrl+L">Clear Log</item>
48
-        <separator />
49
-        <item id="ms">Millisecond View</item>
50
-        <item id="1s">1 Second View</item>
51
-        <item id="5s">5 Second View</item>
52
-        <item id="10s">10 Second View</item>
53
-        <item id="15s">15 Second View</item>
54
-        <item id="30s">30 Second View</item>
55
-        <item id="1m">1 Minute View</item>
56
-        <separator />
57
-        <item id="manual" shortcut="Ctrl+E">Manual Entry</item>
58
-    </menu>
59
-    <program>
60
-        lc = 1;
61
-        this.restoreSizeAndPosition('window');
62
-        var vsplit = findChildObject(this, 'main');
63
-        vsplit.restoreState("script/mainSplitter");
64
-        var isplit = findChildObject(this, 'indicators');
65
-        isplit.restoreState("script/instrumentSplitter");
66
-        var lsplit = findChildObject(this, 'logsplit');
67
-        lsplit.restoreState("script/logSplitter");
68
-        var log = findChildObject(this, 'log');
69
-        this.show();
70
-        log.restoreState("script/log", 7);
71
-        var window = this;
72
-        this.aboutToClose.connect(function() {
73
-            window.saveSizeAndPosition("window");
74
-            vsplit.saveState("script/mainSplitter");
75
-            isplit.saveState("script/instrumentSplitter");
76
-            lsplit.saveState("script/logSplitter");
77
-            log.saveState("script/log", 7);
78
-            loggingWindow = undefined;
79
-        });
80
-        vsplit.restoreState("script/mainSplitter");
81
-        isplit.restoreState("script/instrumentSplitter");
82
-        lsplit.restoreState("script/logSplitter");
83
-        var device = new DAQ('Dev1');
84
-        var bchannel = device.newChannel(DAQ.Fahrenheit, DAQ.TypeJ);
85
-        var achannel = device.newChannel(DAQ.Fahrenheit, DAQ.TypeJ);
86
-        device.setClockRate(2.0);
87
-        device.start();
88
-        beanDisplay = findChildObject(this, 'beans');
89
-        envDisplay = findChildObject(this, 'environment');
90
-        bchannel.newData.connect(beanDisplay.setValue);
91
-        achannel.newData.connect(envDisplay.setValue);
92
-        var epoch = new QTime;
93
-        epoch = epoch.currentTime;
94
-        var boffset = new MeasurementTimeOffset(epoch);
95
-        var aoffset = new MeasurementTimeOffset(epoch);
96
-        badapt = new MeasurementAdapter(1);
97
-        aadapt = new MeasurementAdapter(2);
98
-        bzero = new ZeroEmitter(1);
99
-        azero = new ZeroEmitter(2);
100
-        bchannel.newData.connect(boffset.newMeasurement);
101
-        achannel.newData.connect(aoffset.newMeasurement);
102
-        boffset.measurement.connect(badapt.newMeasurement);
103
-        aoffset.measurement.connect(aadapt.newMeasurement);
104
-        bchannel.newData.connect(bzero.newMeasurement);
105
-        achannel.newData.connect(azero.newMeasurement);
106
-        var graph = findChildObject(this, 'graph');
107
-        bzero.measurement.connect(log.newMeasurement);
108
-        bzero.measurement.connect(graph.newMeasurement);
109
-        azero.measurement.connect(log.newMeasurement);
110
-        azero.measurement.connect(graph.newMeasurement);
111
-        var timer = findChildObject(this, 'batch');
112
-        timer.autoReset = true;
113
-        var start = findChildObject(this, 'startbutton');
114
-        start.clicked.connect(function() {
115
-            var epoch = new QTime();
116
-            epoch = epoch.currentTime();
117
-            timer.startTimer();
118
-            boffset.setZeroTime(epoch);
119
-            aoffset.setZeroTime(epoch);
120
-            bzero.emitZero();
121
-            azero.emitZero();
122
-            aadapt.measurement.connect(log.newMeasurement);
123
-            aadapt.measurement.connect(graph.newMeasurement);
124
-            badapt.measurement.connect(log.newMeasurement);
125
-            badapt.measurement.connect(graph.newMeasurement);
126
-            if(typeof(currentBatchInfo) == 'undefined') { } else {
127
-                var query = new QSqlQuery();
128
-                query.exec("SELECT now()::timestamp without time zone");
129
-                query.next();
130
-                var result = query.value(0);
131
-                var timefield = findChildObject(currentBatchInfo, 'time');
132
-                timefield.text = result.replace('T', ' ');
133
-            }
134
-        });
135
-        start.setFocus();
136
-        var spin = findChildObject(this, 'manometer');
137
-        spin.annotation.connect(log.newAnnotation);
138
-        var stop = findChildObject(this, 'stopbutton');
139
-        stop.annotation.connect(log.newAnnotation);
140
-        stop.clicked.connect(timer.stopTimer);
141
-        stop.clicked.connect(function() {
142
-            badapt.measurement.disconnect(log.newMeasurement);
143
-            badapt.measurement.disconnect(graph.newMeasurement);
144
-            aadapt.measurement.disconnect(log.newMeasurement);
145
-            aadapt.measurement.disconnect(graph.newMeasurement);
146
-            spin.resetChange();
147
-            if(typeof(currentBatchInfo) == 'undefined') { } else {
148
-                var duration = log.lastTime(lc);
149
-                var durfield = findChildObject(currentBatchInfo, 'duration');
150
-                durfield.text = duration;
151
-                log.clearOutputColumns();
152
-                log.addOutputTemperatureColumn(lc);
153
-                log.addOutputTemperatureColumn(lc + 1);
154
-                log.addOutputAnnotationColumn(lc + 2);
155
-                var filename = log.saveTemporary();
156
-                currentBatchInfo.tempData = filename;
157
-                currentBatchInfo.raise();
158
-                currentBatchInfo.activateWindow();
159
-            }
160
-        });
161
-        var sample = findChildObject(this, 'sample');
162
-        sample.annotation.connect(log.newAnnotation);
163
-        var openMenu = findChildObject(this, 'open');
164
-        var window = this;
165
-        openMenu.triggered.connect(function() {
166
-            var filename = QFileDialog.getOpenFileName(window, 'Open Log…', QSettings.value('script/lastDir', '') + '/');
167
-            if(filename != '') {
168
-                var file = new QFile(filename);
169
-                var input = new XMLInput(file, 1);
170
-                input.newTemperatureColumn.connect(log.setHeaderData);
171
-                input.newAnnotationColumn.connect(log.setHeaderData);
172
-                input.measure.connect(graph.newMeasurement);
173
-                input.measure.connect(log.newMeasurement);
174
-                input.annotation.connect(log.newAnnotation);
175
-                input.lastColumn.connect(function(c) {
176
-                    lc = c + 1;
177
-                    badapt.setColumn(c + 1);
178
-                    aadapt.setColumn(c + 2);
179
-                    bzero.setColumn(c + 1);
180
-                    azero.setColumn(c + 2);
181
-                    stop.setTemperatureColumn(c + 1);
182
-                    stop.setAnnotationColumn(c + 3);
183
-                    sample.setTemperatureColumn(c + 1);
184
-                    sample.setAnnotationColumn(c + 3);
185
-                    spin.setTemperatureColumn(c + 1);
186
-                    spin.setAnnotationColumn(c + 3);
187
-                    log.setHeaderData(c + 1, "Bean");
188
-                    log.setHeaderData(c + 2, "Air");
189
-                    log.setHeaderData(c + 3, "Note");
190
-                });
191
-                input.input();
192
-                window.windowTitle = 'Typica - ' + baseName(filename);
193
-                QSettings.setValue("script/lastDir", dir(filename));
194
-            }
195
-        });
196
-        var quitMenu = findChildObject(this, 'quit');
197
-        quitMenu.triggered.connect(function() {
198
-            window.close();
199
-            Application.quit();
200
-        });
201
-        var saveMenu = findChildObject(this, 'save');
202
-        saveMenu.triggered.connect(function() {
203
-            var filename = QFileDialog.getSaveFileName(window, "Save Log As…", QSettings.value("script/lastDir", "") + "/");
204
-            if(filename != "") {
205
-                var file = new QFile(filename);
206
-                log.clearOutputColumns();
207
-                log.addOutputTemperatureColumn(lc);
208
-                log.addOutputTemperatureColumn(lc + 1);
209
-                log.addOutputAnnotationColumn(lc + 2);
210
-                log.saveXML(file);
211
-                QSettings.setValue("script/lastDir", dir(filename));
212
-            }
213
-        });
214
-        var exportMenu = findChildObject(this, 'export');
215
-        exportMenu.triggered.connect(function() {
216
-            var filename = QFileDialog.getSaveFileName(window, "Export CSV As…", QSettings.value("script/lastDir", "") + "/");
217
-            if(filename != "") {
218
-                var file = new QFile(filename);
219
-                log.clearOutputColumns();
220
-                log.addOutputTemperatureColumn(lc);
221
-                log.addOutputTemperatureColumn(lc + 1);
222
-                log.addOutputAnnotationColumn(lc + 2);
223
-                log.saveCSV(file);
224
-                QSettings.setValue("script/lastDir", dir(filename));
225
-            }
226
-        });
227
-        var clear = findChildObject(this, 'clear');
228
-        clear.triggered.connect(log.clear);
229
-        clear.triggered.connect(graph.clear);
230
-        clear.triggered.connect(function() {
231
-            window.windowTitle = "Typica";
232
-            log.setHeaderData(0, "Time");
233
-            log.setHeaderData(1, "Bean");
234
-            log.setHeaderData(2, "Air");
235
-            log.setHeaderData(3, "Note");
236
-            log.setHeaderData(4, "");
237
-            log.setHeaderData(5, "");
238
-            log.setHeaderData(6, "");
239
-            lc = 1;
240
-            badapt.setColumn(1);
241
-            aadapt.setColumn(2);
242
-            bzero.setColumn(1);
243
-            azero.setColumn(2);
244
-            stop.setTemperatureColumn(1);
245
-            stop.setAnnotationColumn(3);
246
-            sample.setTemperatureColumn(1);
247
-            sample.setAnnotationColumn(3);
248
-            spin.setTemperatureColumn(1);
249
-            spin.setAnnotationColumn(3);
250
-        });
251
-        var v1 = findChildObject(this, 'ms');
252
-        v1.triggered.connect(log.LOD_ms);
253
-        var v2 = findChildObject(this, '1s');
254
-        v2.triggered.connect(log.LOD_1s);
255
-        var v3 = findChildObject(this, '5s');
256
-        v3.triggered.connect(log.LOD_5s);
257
-        var v4 = findChildObject(this, '10s');
258
-        v4.triggered.connect(log.LOD_10s);
259
-        var v5 = findChildObject(this, '15s');
260
-        v5.triggered.connect(log.LOD_15s);
261
-        var v6 = findChildObject(this, '30s');
262
-        v6.triggered.connect(log.LOD_30s);
263
-        var v7 = findChildObject(this, '1m');
264
-        v7.triggered.connect(log.LOD_1m);
265
-        var manual = findChildObject(this, 'manual');
266
-        manual.triggered.connect(function() {
267
-            var entry = new LogEditWindow();
268
-            entry.show();
269
-        });
270
-        var newMenu = findChildObject(this, 'new');
271
-        newMenu.triggered.connect(function() {
272
-            var bwindow = createWindow("batchWindow");
273
-            bwindow.windowTitle = "Typica - New Batch";
274
-        });
275
-    </program>
276
-</window>

+ 76
- 89
config/Windows/greeninventory.xml View File

@@ -1,109 +1,96 @@
1 1
 <window id="inventory">
2 2
     <layout type="vertical">
3
+        <layout type="horizontal">
4
+            <label>Transaction type: </label>
5
+            <sqldrop id="transactiontype" />
6
+            <stretch />
7
+        </layout>
3 8
         <layout type="horizontal">
4 9
             <sqldrop data="0" display="1" showdata="true" id="item">
5 10
                 <query><![CDATA[SELECT id, name FROM coffees WHERE quantity <> 0 ORDER BY name ASC]]></query>
6 11
             </sqldrop>
7 12
             <line id="quantity" />
8 13
             <sqldrop id="units" />
14
+            <layout type="stack" id="optional">
15
+                <page />
16
+                <page>    
17
+                    <layout type="horizontal">
18
+                        <label id="reasonlabel">Reason: </label>
19
+                        <line id="reason" />
20
+                    </layout>
21
+                </page>
22
+            </layout>
23
+        </layout>
24
+        <layout type="horizontal">
9 25
             <button type="push" name="Update" id="update" />
26
+            <stretch />
10 27
         </layout>
11 28
         <textarea id="current" />
12 29
     </layout>
13 30
     <program>
14 31
         <![CDATA[
15
-			var units = findChildObject(this, 'units');
16
-			units.addItem("bag");
17
-			units.addItem("lb");
18
-			units.addItem("kg");
19
-			var items = findChildObject(this, 'item');
20
-			var status = findChildObject(this, 'current');
21
-			var q = "SELECT quantity FROM items WHERE id = ";
22
-			q = q + items.currentData();
23
-			query = new QSqlQuery();
24
-			query.exec(q);
25
-			query.next();
26
-			var text = items.currentText;
27
-			text = text + " Current inventory: ";
28
-			text = text + query.value(0);
29
-			text = text + " pounds ("
30
-			text = text + Number(query.value(0)) / 2.2;
31
-			text = text + " Kg (";
32
-			q = "SELECT ";
33
-			q = q + query.value(0);
34
-			q = q + " / (SELECT conversion FROM lb_bag_conversion WHERE item = ";
35
-			q = q + items.currentData();
36
-			q = q + ")";
37
-			query.exec(q);
38
-			query.next();
39
-			text = text + query.value(0);
40
-			text = text + " bags)";
41
-            query = query.invalidate();
42
-			status.plainText = text;
43
-			var button = findChildObject(this, 'update');
44
-			var value = findChildObject(this, 'quantity');
45
-			button.clicked.connect(function() {
46
-				q = "INSERT INTO inventory (time, item, quantity) VALUES ('now', ";
47
-				q = q + items.currentData();
48
-				q = q + ", ";
49
-				if(units.currentText == "lb") {
50
-					q = q + value.text;
51
-				} else if (units.currentText == "kg") {
52
-					q = q + (value.text * 2.2);
53
-				}
54
-				else {
55
-					q = q + value.text;
56
-					q = q + " * (SELECT conversion FROM lb_bag_conversion WHERE item = ";
57
-					q = q + items.currentData();
58
-					q = q + ")";
59
-				}
60
-				q = q + ")";
32
+            var types = findChildObject(this, 'transactiontype');
33
+            types.addItem(TTR("inventory", "inventory"));
34
+            types.addItem(TTR("inventory", "loss"));
35
+            var optionalDisplay = findChildObject(this, 'optional');
36
+            var units = findChildObject(this, 'units');
37
+            units.addItem(TTR("inventory", "bag"));
38
+            units.addItem(TTR("inventory", "Lb"));
39
+            units.addItem(TTR("inventory", "Kg"));
40
+            var items = findChildObject(this, 'item');
41
+            var status = findChildObject(this, 'current');
42
+            function updateStatus() {
61 43
                 query = new QSqlQuery();
62
-				query.exec(q);
63
-				text = items.currentText;
64
-				q = "SELECT quantity FROM items WHERE id = ";
65
-				q = q + items.currentData();
66
-				query.exec(q);
67
-				query.next();
68
-				text = text + " Current inventory: ";
69
-				text = text + query.value(0);
70
-				text = text + " pounds (";
71
-				q = "SELECT ";
72
-				q = q + query.value(0);
73
-				q = q + " / (SELECT conversion FROM lb_bag_conversion WHERE item = ";
74
-				q = q + items.currentData();
75
-				q = q + ")";
76
-				query.exec(q);
77
-				query.next();
78
-				text = text + query.value(0);
79
-				text = text + " bags)";
80
-				status.plainText = text;
44
+                query.prepare("SELECT quantity, (quantity / 2.2)::numeric(12,3), (quantity / (SELECT conversion FROM lb_bag_conversion WHERE item = :id1))::numeric(12,2) FROM items WHERE id = :id2");
45
+                query.bind(":id1", items.currentData());
46
+                query.bind(":id2", items.currentData());
47
+                query.exec();
48
+                query.next();
49
+                var text = items.currentText;
50
+                text += " Current inventory: ";
51
+                text += query.value(0);
52
+                text += TTR("inventory", " Lb (");
53
+                text += query.value(1);
54
+                text += TTR("inventory", " Kg), ");
55
+                text += query.value(2);
56
+                text += (query.value(2) == "1" ? TTR("inventory", " bag") :
57
+                    TTR("inventory", " bags"));
81 58
                 query = query.invalidate();
82
-			});
83
-			items['currentIndexChanged(int)'].connect(function() {
84
-				q = "SELECT quantity FROM items WHERE id = ";
85
-				q = q + items.currentData();
59
+                status.plainText = text;
60
+            }
61
+            var button = findChildObject(this, 'update');
62
+            var value = findChildObject(this, 'quantity');
63
+            var reason = findChildObject(this, 'reason');
64
+            var reasonlabel = findChildObject(this, 'reasonlabel');
65
+            button.clicked.connect(function() {
66
+                q = "INSERT INTO ";
67
+                q += (types.currentIndex == 0 ?
68
+                    "inventory (time, item, quantity)" :
69
+                    "loss (time, item, quantity, reason)");
70
+                q += " VALUES ('now', ";
71
+                q = q + items.currentData();
72
+                q = q + ", ";
73
+                if(units.currentText == TTR("inventory", "Lb")) {
74
+                    q = q + value.text;
75
+                } else if (units.currentText == TTR("inventory", "Kg")) {
76
+                    q = q + (value.text * 2.2);
77
+                }
78
+                else {
79
+                    q = q + value.text;
80
+                    q = q + " * (SELECT conversion FROM lb_bag_conversion WHERE item = ";
81
+                    q = q + items.currentData();
82
+                    q = q + ")";
83
+                }
84
+                q += (types.currentIndex == 0 ?
85
+                    ")" :
86
+                    ", '" + reason.text + "')");
86 87
                 query = new QSqlQuery();
87
-				query.exec(q);
88
-				query.next();
89
-				var text = items.currentText;
90
-				text = text + " Current inventory: ";
91
-				text = text + query.value(0);
92
-				text = text + " pounds ";
93
-				text = text + Number(query.value(0)) / 2.2;
94
-				text = text + " Kg (";
95
-				q = "SELECT ";
96
-				q = q + query.value(0);
97
-				q = q + " / (SELECT conversion FROM lb_bag_conversion WHERE item = ";
98
-				q = q + items.currentData();
99
-				q = q + ")";
100
-				query.exec(q);
101
-				query.next();
102
-				text = text + query.value(0);
103
-				text = text + " bags)";
104
-				status.plainText = text;
105
-                query = query.invalidate();
106
-			});
88
+                query.exec(q);
89
+                updateStatus();
90
+            });
91
+            items['currentIndexChanged(int)'].connect(updateStatus);
92
+            types['currentIndexChanged(int)'].connect(optionalDisplay.setCurrentIndex);
93
+            updateStatus();
107 94
         ]]>
108 95
     </program>
109 96
 </window>

+ 3
- 3
config/Windows/greensales.xml View File

@@ -21,13 +21,13 @@
21 21
 	<program>
22 22
 		<![CDATA[
23 23
 			var window = this;
24
-			this.windowTitle = 'Typica - Enter Green Coffee Sales';
24
+			this.windowTitle = TTR("greensales", "Typica - Enter Green Coffee Sales");
25 25
 			var unitBox = findChildObject(this, 'units');
26 26
 			unitBox.addItem("g");
27 27
 			unitBox.addItem("Kg");
28 28
 			unitBox.addItem("oz");
29
-			unitBox.addItem("lb");
30
-			unitBox.currentIndex = (QSettings.value("script/greensales_unit", unitBox.findText("lb")));
29
+			unitBox.addItem("Lb");
30
+			unitBox.currentIndex = (QSettings.value("script/greensales_unit", unitBox.findText("Lb")));
31 31
 			unitBox['currentIndexChanged(int)'].connect(function() {
32 32
 				QSettings.setValue("script/greensales_unit", unitBox.currentIndex);
33 33
 			});

+ 0
- 47
config/Windows/history.xml View File

@@ -1,47 +0,0 @@
1
-<window id="history">
2
-    <layout type="vertical">
3
-		<layout type="horizontal">
4
-			<daterange id="dates" initial="6" /><!-- Last 7 Days -->
5
-			<stretch />
6
-		</layout>
7
-        <sqlview id="table" />
8
-    </layout>
9
-    <program>
10
-        <![CDATA[
11
-			var dateSelect = findChildObject(this, 'dates');
12
-			var dateQuery = new QSqlQuery();
13
-			dateQuery.exec("SELECT time::date FROM roasting_log WHERE time = (SELECT min(time) FROM roasting_log) OR time = (SELECT max(time) FROM roasting_log) ORDER BY time ASC");
14
-			dateQuery.next();
15
-			var lifetimeStartDate = dateQuery.value(0);
16
-			var lifetimeEndDate;
17
-			if(dateQuery.next()) {
18
-				lifetimeEndDate = dateQuery.value(0);
19
-			} else {
20
-				lifetimeEndDate = lifetimeStartDate;
21
-			}
22
-			dateSelect.setLifetimeRange(lifetimeStartDate, lifetimeEndDate);
23
-			dateQuery.invalidate();
24
-			dateSelect.currentIndex = QSettings.value("script/history/dateIndex", 6);
25
-			var table = findChildObject(this, 'table');
26
-			table.openEntryRow.connect(function(arg) {
27
-				var details = createWindow("batchDetails");
28
-				details.loadData(table, arg);
29
-			});
30
-			function refresh() {
31
-				var dateRange = dateSelect.currentRange();
32
-				var startDate = "'"+dateRange[0]+"'";
33
-				var endDate = "'"+dateRange[dateRange.length - 1]+"'";
34
-				var q = "SELECT time, machine, (SELECT name FROM items WHERE id = roasted_id) AS name, unroasted_total_quantity AS green, roasted_quantity AS roasted, CASE WHEN unroasted_total_quantity = 0 THEN NULL ELSE ((unroasted_total_quantity - roasted_quantity) / unroasted_total_quantity * 100::numeric)::numeric(12,2) END AS weight_loss, duration, annotation FROM roasting_log WHERE time >= " + startDate + "::date AND time < " + endDate + "::date + interval '1 day' ORDER BY time DESC";
35
-				table.setQuery(q);
36
-				table.hideColumn(1);
37
-			}
38
-			dateSelect.rangeUpdated.connect(function() {
39
-				if(dateSelect.currentIndex != 24) { // Custom date range
40
-					QSettings.setValue("script/history/dateIndex", dateSelect.currentIndex);
41
-				}
42
-				refresh();
43
-			});
44
-			refresh();
45
-        ]]>
46
-    </program>
47
-</window>

+ 1
- 1
config/Windows/importprofiles.xml View File

@@ -10,7 +10,7 @@
10 10
         var box = findChildObject(this, 'roasted');
11 11
         var win = this;
12 12
         button.clicked.connect(function() {
13
-            var filename = QFileDialog.getOpenFileName(win, 'Open Log…', QSettings.value('script/lastDir', '') + '/');
13
+            var filename = QFileDialog.getOpenFileName(win, TTR("importTargets", "Open Log..."), QSettings.value('script/lastDir', '') + '/');
14 14
             if(filename != '') {
15 15
                 QSettings.setValue("script/lastDir", dir(filename));
16 16
                 var q = "INSERT INTO files (id, name, type, note, file) VALUES(default, :name, 'profile', NULL, :data) RETURNING id";

+ 4
- 1
config/Windows/invoiceinfo.xml View File

@@ -14,10 +14,12 @@
14 14
 	<program>
15 15
 		<![CDATA[
16 16
 			var window = this;
17
+                        var invoiceID = 0;
17 18
 			var table = findChildObject(this, 'itemtable');
18 19
 			this.setInvoiceID = function(arg) {
19 20
 				window.invoiceID = arg;
20
-				window.windowTitle = "Typica - Invoice Details " + arg;
21
+                                invoiceID = arg;
22
+				window.windowTitle = TTR("invoiceinfo", "Typica - Invoice Details ") + arg;
21 23
 			};
22 24
 			button = findChildObject(this, 'edit');
23 25
 			button.clicked.connect(function() {
@@ -43,6 +45,7 @@
43 45
 					for(var i = 0; i < 8; i++) {
44 46
 						feeWindow.rowData[i] = table.data(arg, i);
45 47
 					}
48
+                                        feeWindow.invoiceID = invoiceID;
46 49
 					feeWindow.dataSet();
47 50
 				}
48 51
 			});

+ 0
- 28
config/Windows/invoicelist.xml View File

@@ -1,28 +0,0 @@
1
-<window id="invoicelist">
2
-	<layout type="vertical">
3
-		<sqlview id="table" />
4
-	</layout>
5
-	<program>
6
-		<![CDATA[
7
-			this.windowTitle = "Typica - Invoice List";
8
-			var table = findChildObject(this, 'table');
9
-			table.setQuery("SELECT id, time, invoice, vendor, (SELECT sum(cost) FROM invoice_items WHERE invoice_id = id) AS cost FROM invoices ORDER BY time DESC");
10
-			table.openEntry.connect(function(arg) {
11
-				var info = createWindow("invoiceinfo");
12
-				info.setInvoiceID(arg);
13
-				var query = new QSqlQuery();
14
-				query.exec("SELECT time, invoice, vendor FROM invoices WHERE id = " + arg);
15
-				query.next();
16
-				var timefield = findChildObject(info, 'date');
17
-				timefield.text = query.value(0);
18
-				var vendorfield = findChildObject(info, 'vendor');
19
-				vendorfield.text = query.value(2);
20
-				var invoicefield = findChildObject(info, 'invoice');
21
-				invoicefield.text = query.value(1);
22
-				var itemtable = findChildObject(info, 'itemtable');
23
-				itemtable.setQuery("SELECT record_type, item_id, description, (SELECT reference FROM items WHERE id = item_id) AS reference, (SELECT cost FROM purchase WHERE item = item_id) AS unit_cost, (SELECT quantity FROM purchase WHERE item = item_id) AS quantity, ((SELECT quantity FROM purchase WHERE item = item_id)/(SELECT conversion FROM lb_bag_conversion WHERE item = item_id))::numeric(12,2) AS sacks, cost FROM invoice_items WHERE invoice_id = " + arg + " AND record_type = 'PURCHASE' UNION SELECT record_type, NULL, description, NULL, NULL, NULL, NULL, cost FROM invoice_items WHERE invoice_id = " + arg + " AND record_type = 'FEE' ORDER BY item_id");
24
-				query = query.invalidate();
25
-			});
26
-		]]>
27
-	</program>
28
-</window>

+ 202
- 198
config/Windows/navigation.xml View File

@@ -1,114 +1,100 @@
1 1
 <window id="navwindow">
2
-	<layout type="grid">
3
-		<row>
4
-			<column>
5
-				<button name="Configure Roasters" id="configure" type="push" />
6
-			</column>
7
-		</row>
8
-		<row>
9
-			<column>
10
-				<sqldrop id="machineselector" />
11
-			</column>
12
-			<column>
13
-				<button name="Roast Coffee" id="roast" type="push" />
14
-			</column>
15
-		</row>
16
-		<row>
17
-			<column>
18
-				<button name="Purchase Green Coffee" id="green" type="push" />
19
-			</column>
20
-		</row>
21
-		<row>
22
-			<column>
23
-				<button name="Manage Roasted Coffee Items" id="newroasted" type="push" />
24
-			</column>
25
-		</row>
26
-		<row>
27
-			<column>
28
-				<button name="Update Inventory" id="inventory" type="push" />
29
-			</column>
30
-		</row>
31
-		<row>
32
-			<column>
33
-				<button name="Batch Log" id="history" type="push" />
34
-			</column>
35
-		</row>
36
-		<row>
37
-			<column>
38
-				<button name="New Cupping Session" id="createcupping"
39
-type="push" />
40
-			</column>
41
-		</row>
42
-		<row>
43
-			<column>
44
-				<button name="Join Cupping Session" id="joincupping" type="push"
45
-/>
46
-			</column>
47
-		</row>
48
-		<row>
49
-			<column>
50
-				<button name="Summarize Cupping Session" id="sumcupping"
51
-type="push" />
52
-			</column>
53
-		</row>
54
-		<row>
55
-			<column>
56
-				<button name="View Target Roast Profiles" id="profilehistory"
57
-					type="push" />
58
-			</column>
59
-		</row>
60
-		<row>
61
-			<column>
62
-				<button name="Import Target Roast Profiles" id="target"
63
-type="push" />
64
-			</column>
65
-		</row>
66
-		<row>
67
-			<column>
68
-				<button name="Invoice List" id="invoicelist" type="push" />
69
-			</column>
70
-		</row>
71
-		<row>
72
-			<column>
73
-				<button name="Enter Green Coffee Sales" id="greensales" type="push" />
74
-			</column>
75
-		</row>
76
-	</layout>
2
+    <layout type="grid">
3
+        <row>
4
+            <column>
5
+                <button name="Configure Roasters" id="configure" type="push" />
6
+            </column>
7
+        </row>
8
+        <row>
9
+            <column>
10
+                <sqldrop id="machineselector" />
11
+            </column>
12
+            <column>
13
+                <button name="Roast Coffee" id="roast" type="push" />
14
+            </column>
15
+        </row>
16
+        <row>
17
+            <column>
18
+                <button name="Purchase Green Coffee" id="green" type="push" />
19
+            </column>
20
+        </row>
21
+        <row>
22
+            <column>
23
+                <button name="Manage Roasted Coffee Items" id="newroasted" type="push" />
24
+            </column>
25
+        </row>
26
+        <row>
27
+            <column>
28
+                <button name="Edit Roasting Specification" id="roastspec" type="push" />
29
+            </column>
30
+        </row>
31
+        <row>
32
+            <column>
33
+                <button name="Update Inventory" id="inventory" type="push" />
34
+            </column>
35
+        </row>
36
+        <row>
37
+            <column>
38
+                <button name="New Cupping Session" id="createcupping" type="push" />
39
+            </column>
40
+        </row>
41
+        <row>
42
+            <column>
43
+                <button name="Join Cupping Session" id="joincupping" type="push" />
44
+            </column>
45
+        </row>
46
+        <row>
47
+            <column>
48
+                <button name="Summarize Cupping Session" id="sumcupping" type="push" />
49
+            </column>
50
+        </row>
51
+        <row>
52
+            <column>
53
+                <button name="View Target Roast Profiles" id="profilehistory" type="push" />
54
+            </column>
55
+        </row>
56
+        <row>
57
+            <column>
58
+                <button name="Import Target Roast Profiles" id="target" type="push" />
59
+            </column>
60
+        </row>
61
+        <row>
62
+            <column>
63
+                <button name="Enter Green Coffee Sales" id="greensales" type="push" />
64
+            </column>
65
+        </row>
66
+    </layout>
77 67
     <menu name="Reports" type="reports" src="Reports" />
78
-	<menu name="Database">
79
-		<item id="resetconnection">Forget Connection Details</item>
80
-	</menu>
68
+    <menu name="Database">
69
+        <item id="resetconnection">Forget Connection Details</item>
70
+    </menu>
81 71
     <program>
82
-		var window = this;
83
-		var navigationwindow = window;
84
-		window.loggingWindow = undefined;
85
-		var roasterlist = findChildObject(this, 'machineselector');
86
-		var model = new DeviceTreeModel;
87
-		roasterlist.setModel(model);
88
-		roasterlist.currentIndex = QSettings.value("machineSelection", 0);
89
-		roasterlist['currentIndexChanged(int)'].connect(function() {
90
-			QSettings.setValue("machineSelection", roasterlist.currentIndex);
91
-		});
92
-		var resetdbconnection = findChildObject(this, 'resetconnection');
93
-		resetdbconnection.triggered.connect(function() {
94
-			QSettings.setValue("database/exists", false);
95
-			QSettings.setValue("database/hostname", "");
96
-			QSettings.setValue("database/dbname", "");
97
-			QSettings.setValue("database/user", "");
98
-			QSettings.setValue("database/password", "");
99
-		});
100
-		var profilehistory = findChildObject(this, 'profilehistory');
101
-		profilehistory.clicked.connect(function() {
102
-			createWindow("profilehistory");
103
-		});
104
-		var greensalesbutton = findChildObject(this, 'greensales');
105
-		greensalesbutton.clicked.connect(function() {
106
-			createWindow("greensales");
107
-		});
108
-		var invoicesbutton = findChildObject(this, 'invoicelist');
109
-		invoicesbutton.clicked.connect(function() {
110
-			createWindow("invoicelist");
111
-		});
72
+        var window = this;
73
+        var navigationwindow = window;
74
+        window.loggingWindow = undefined;
75
+        var roasterlist = findChildObject(this, 'machineselector');
76
+        var model = new DeviceTreeModel;
77
+        roasterlist.setModel(model);
78
+        roasterlist.currentIndex = QSettings.value("machineSelection", 0);
79
+        roasterlist['currentIndexChanged(int)'].connect(function() {
80
+            QSettings.setValue("machineSelection", roasterlist.currentIndex);
81
+        });
82
+        var resetdbconnection = findChildObject(this, 'resetconnection');
83
+        resetdbconnection.triggered.connect(function() {
84
+            QSettings.setValue("database/exists", false);
85
+            QSettings.setValue("database/hostname", "");
86
+            QSettings.setValue("database/dbname", "");
87
+            QSettings.setValue("database/user", "");
88
+            QSettings.setValue("database/password", "");
89
+        });
90
+        var profilehistory = findChildObject(this, 'profilehistory');
91
+        profilehistory.clicked.connect(function() {
92
+                createWindow("profilehistory");
93
+        });
94
+        var greensalesbutton = findChildObject(this, 'greensales');
95
+        greensalesbutton.clicked.connect(function() {
96
+                createWindow("greensales");
97
+        });
112 98
         var sumcup = findChildObject(this, 'sumcupping');
113 99
         sumcup.clicked.connect(function() {
114 100
             var sessionlist = createWindow("finsessionlist");
@@ -136,10 +122,9 @@ type="push" />
136 122
             var invwin = createWindow("inventory");
137 123
             invwin.windowTitle = "Typica - Inventory";
138 124
         });
139
-        var history = findChildObject(this, 'history');
140
-        history.clicked.connect(function() {
141
-            var histwindow = createWindow("history");
142
-            histwindow.windowTitle = "Typica - Batch Log";
125
+        var roastspecbutton = findChildObject(this, 'roastspec');
126
+        roastspecbutton.clicked.connect(function() {
127
+            var specwindow = createWindow("roastspec");
143 128
         });
144 129
         var gbutton = findChildObject(this, 'green');
145 130
         gbutton.clicked.connect(function() {
@@ -161,82 +146,81 @@ type="push" />
161 146
             {
162 147
                 window.loggingWindow = createWindow("basicWindow");
163 148
                 window.loggingWindow.windowTitle = "Typica [*]";
164
-				window.loggingWindow.navigationWindow = window;
149
+                window.loggingWindow.navigationWindow = window;
165 150
             }
166 151
             else
167 152
             {
168
-				print(window.loggingWindow);
169 153
                 window.loggingWindow.raise();
170 154
                 window.loggingWindow.activateWindow();
171 155
             }
172 156
         });
173
-		var configurebutton = findChildObject(this, 'configure');
174
-		configurebutton.clicked.connect(function() {
175
-			var confwindow = new SettingsWindow;
176
-			confwindow.show();
177
-		});
157
+        var configurebutton = findChildObject(this, 'configure');
158
+        configurebutton.clicked.connect(function() {
159
+            var confwindow = new SettingsWindow;
160
+            confwindow.show();
161
+        });
178 162
         <![CDATA[
179
-		var DBCreateBase = function() {
180
-			var query = new QSqlQuery();
181
-            query.exec("CREATE TABLE IF NOT EXISTS certifications (item bigint NOT NULL, certification text NOT NULL)");
182
-            query.exec("CREATE TABLE IF NOT EXISTS cupping_samples (session bigint NOT NULL, sample text NOT NULL, position bigint NOT NULL, type text NOT NULL, \"time\" timestamp without time zone, machine bigint, point text, item bigint)");
183
-            query.exec("CREATE TABLE cupping_sessions (id bigserial NOT NULL, event text, name text NOT NULL, \"time\" timestamp without time zone NOT NULL, blind boolean NOT NULL, open boolean NOT NULL, note text)");
184
-            query.exec("CREATE TABLE IF NOT EXISTS cuppingforms (session bigint NOT NULL, sample text NOT NULL, position bigint NOT NULL, grader text, finalscore numeric, notes text, serialization text)");
185
-            query.exec("CREATE TABLE IF NOT EXISTS cuppingform_t1 (aroma numeric, flavor numeric, aftertaste numeric, acidity numeric, body numeric, uniformity numeric, balance numeric, cleancup numeric, sweetness numeric, overall numeric, total numeric) INHERITS (cuppingforms)");
186
-            query.exec("CREATE TABLE IF NOT EXISTS invoices (id bigserial PRIMARY KEY NOT NULL, invoice text, vendor text NOT NULL, \"time\" timestamp without time zone NOT NULL)");
187
-            query.exec("CREATE TABLE IF NOT EXISTS invoice_items (invoice_id bigint NOT NULL, record_type text NOT NULL, item_id bigint, description text NOT NULL, cost numeric NOT NULL)");
188
-			query.exec("CREATE TABLE IF NOT EXISTS transactions (\"time\" timestamp without time zone NOT NULL, item bigint NOT NULL)");
189
-			query.exec("CREATE TABLE IF NOT EXISTS inventory (quantity numeric NOT NULL) INHERITS (transactions)");
190
-			query.exec("CREATE TABLE IF NOT EXISTS loss (quantity numeric NOT NULL, reason text) INHERITS (transactions)");
191
-			query.exec("CREATE TABLE IF NOT EXISTS make (quantity numeric NOT NULL) INHERITS (transactions)");
192
-			query.exec("CREATE TABLE IF NOT EXISTS purchase (quantity numeric NOT NULL, cost numeric NOT NULL, vendor text NOT NULL) INHERITS (transactions)");
193
-			query.exec("CREATE TABLE IF NOT EXISTS sale (quantity numeric NOT NULL, customer text) INHERITS (transactions)");
194
-			query.exec("CREATE TABLE IF NOT EXISTS use (quantity numeric NOT NULL) INHERITS (transactions)");
195
-			query.exec("CREATE VIEW all_transactions AS ((((SELECT purchase.\"time\", purchase.item, purchase.quantity, purchase.cost, purchase.vendor, NULL::unknown AS reason, NULL::unknown AS customer, 'PURCHASE' AS type FROM purchase UNION SELECT use.\"time\", use.item, use.quantity, NULL::unknown AS cost, NULL::unknown AS vendor, NULL::unknown AS reason, NULL::unknown AS customer, 'USE' AS type FROM use) UNION SELECT inventory.\"time\", inventory.item, inventory.quantity, NULL::unknown AS cost, NULL::unknown AS vendor, NULL::unknown AS reason, NULL::unknown AS customer, 'INVENTORY' AS type FROM inventory) UNION SELECT loss.\"time\", loss.item, loss.quantity, NULL::unknown AS cost, NULL::unknown AS vendor, loss.reason, NULL::unknown AS customer, 'LOSS' AS type FROM loss) UNION SELECT make.\"time\", make.item, make.quantity, NULL::unknown AS cost, NULL::unknown AS vendor, NULL::unknown AS reason, NULL::unknown AS customer, 'MAKE' AS type FROM make) UNION SELECT sale.\"time\", sale.item, sale.quantity, NULL::unknown AS cost, NULL::unknown AS vendor, NULL::unknown AS reason, sale.customer, 'SALE' AS type FROM sale");
196
-			query.exec("CREATE FUNCTION time_range(bigint) RETURNS integer AS $$ BEGIN IF (SELECT quantity FROM items WHERE id = $1) > 0 THEN RETURN (SELECT current_date - min(time)::date + 1 FROM use WHERE item = $1); ELSE RETURN (SELECT max(time)::date - min(time)::date + 1 FROM use WHERE item = $1); END IF; END; $$ LANGUAGE plpgsql STRICT");
197
-			query.exec("CREATE TABLE IF NOT EXISTS items(id bigint NOT NULL, name text NOT NULL, reference text, unit text NOT NULL, quantity numeric DEFAULT 0, category text)");
198
-			query.exec("CREATE SEQUENCE items_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1");
199
-			query.exec("CREATE TABLE IF NOT EXISTS coffees(origin text NOT NULL, region text, producer text, grade text, milling text, drying text) INHERITS (items)");
200
-			query.exec("CREATE VIEW coffee_history AS SELECT coffees.id, coffees.name, coffees.origin, coffees.quantity AS stock, (SELECT sum(use.quantity) AS sum FROM use WHERE (use.item = coffees.id)) AS used, time_range(coffees.id) AS \"interval\", ((SELECT (sum(use.quantity) / (time_range(use.item))::numeric) FROM use WHERE (use.item = coffees.id) GROUP BY use.item))::numeric(10,2) AS rate, (SELECT (('now'::text)::date + ((coffees.quantity / (SELECT (sum(use.quantity) / (time_range(use.item))::numeric) FROM use WHERE (use.item = coffees.id) GROUP BY use.item)))::integer)) AS \"out\" FROM coffees WHERE (coffees.id IN (SELECT use.item FROM use)) ORDER BY coffees.origin");
201
-			query.exec("CREATE TABLE IF NOT EXISTS current_items (item bigint NOT NULL)");
202
-			query.exec("CREATE TABLE IF NOT EXISTS decaf_coffees (decaf_method text NOT NULL) INHERITS (coffees)");
203
-			query.exec("CREATE TABLE IF NOT EXISTS files (id bigint NOT NULL, name text NOT NULL, type text NOT NULL, note text, file bytea NOT NULL)");
204
-			query.exec("CREATE TABLE IF NOT EXISTS item_files(\"time\" timestamp without time zone NOT NULL, item bigint NOT NULL, files bigint[] NOT NULL)");
205
-			query.exec("CREATE TYPE item_transaction_with_balance AS (\"time\" timestamp without time zone, item bigint, quantity numeric, cost numeric, vendor text, reason text, customer text, type text, balance numeric)");
206
-			query.exec("CREATE TABLE IF NOT EXISTS lb_bag_conversion (item bigint NOT NULL, conversion numeric NOT NULL)");
207
-			query.exec("CREATE TABLE IF NOT EXISTS machine (id bigint NOT NULL, name text NOT NULL)");
208
-			query.exec("CREATE VIEW regular_coffees AS SELECT coffees.id, coffees.name, coffees.reference, coffees.unit, coffees.quantity, coffees.category, coffees.origin, coffees.region, coffees.producer, coffees.grade, coffees.milling, coffees.drying FROM coffees WHERE (NOT (coffees.id IN (SELECT decaf_coffees.id FROM decaf_coffees)))");
209
-			query.exec("CREATE TABLE IF NOT EXISTS roasting_log (\"time\" timestamp without time zone NOT NULL, unroasted_id bigint[], unroasted_quantity numeric[], unroasted_total_quantity numeric, roasted_id bigint, roasted_quantity numeric, transaction_type text NOT NULL, annotation text, machine bigint NOT NULL, duration interval, approval boolean, humidity numeric, barometric numeric, indoor_air numeric, outdoor_air numeric, files bigint[])");
210
-			query.exec("CREATE VIEW short_log AS SELECT roasting_log.\"time\", (SELECT items.name FROM items WHERE (items.id = roasting_log.roasted_id)) AS name, roasting_log.unroasted_total_quantity, roasting_log.roasted_quantity, ((((roasting_log.unroasted_total_quantity - roasting_log.roasted_quantity) / roasting_log.unroasted_total_quantity) * (100)::numeric))::numeric(12,2) AS weight_loss, roasting_log.duration FROM roasting_log ORDER BY roasting_log.\"time\"");
211
-			query.exec("CREATE FUNCTION add_inventory() RETURNS trigger AS $$ BEGIN UPDATE items SET quantity = quantity + NEW.quantity WHERE id = NEW.item; RETURN NEW; END; $$ LANGUAGE plpgsql");
212
-			query.exec("CREATE FUNCTION bags_in_stock(bigint) RETURNS numeric AS $_$SELECT quantity / (SELECT conversion FROM lb_bag_conversion WHERE item = id) FROM items WHERE id = $1;$_$ LANGUAGE sql IMMUTABLE STRICT");
213
-			query.exec("CREATE FUNCTION log_use() RETURNS trigger AS $$ DECLARE i integer := array_lower(NEW.unroasted_id, 1); u integer := array_upper(NEW.unroasted_id, 1); BEGIN WHILE i <= u LOOP INSERT INTO use VALUES(NEW.time, NEW.unroasted_id[i], NEW.unroasted_quantity[i]); i := i + 1; END LOOP; RETURN NEW; END; $$ LANGUAGE plpgsql");
214
-			query.exec("CREATE FUNCTION replace_inventory() RETURNS trigger AS $$ BEGIN UPDATE items SET quantity = NEW.quantity WHERE id = NEW.item; RETURN NEW; END; $$ LANGUAGE plpgsql");
215
-			query.exec("CREATE FUNCTION subtract_inventory() RETURNS trigger AS $$ BEGIN UPDATE items SET quantity = quantity - NEW.quantity WHERE id = NEW.item; RETURN NEW; END; $$ LANGUAGE plpgsql");
216
-			query.exec("CREATE SEQUENCE files_id_seq START WITH 1 INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1");
217
-			query.exec("ALTER TABLE files ALTER COLUMN id SET DEFAULT nextval('files_id_seq'::regclass)");
218
-			query.exec("ALTER TABLE items ALTER COLUMN id SET DEFAULT nextval('items_id_seq'::regclass)");
219
-			query.exec("ALTER TABLE ONLY files ADD CONSTRAINT file_pkey PRIMARY KEY (id)");
220
-			query.exec("ALTER TABLE ONLY items ADD CONSTRAINT items_pkey PRIMARY KEY (id)");
221
-			query.exec("ALTER TABLE ONLY lb_bag_conversion ADD CONSTRAINT lb_bag_conversion_item_key UNIQUE (item)");
222
-			query.exec("ALTER TABLE ONLY roasting_log ADD CONSTRAINT roasting_log_pkey PRIMARY KEY (\"time\", machine)");
223
-			query.exec("CREATE INDEX itemcategories ON items USING btree (category)");
224
-			query.exec("CREATE INDEX itemnames ON items USING btree (name)");
225
-			query.exec("CREATE INDEX roasting_log_index ON roasting_log USING btree (\"time\")");
226
-			query.exec("CREATE INDEX transactionitems ON transactions USING btree (item)");
227
-			query.exec("CREATE INDEX transactiontimes ON transactions USING btree (\"time\")");
228
-			query.exec("CREATE TRIGGER add_inventory_trigger AFTER INSERT ON purchase FOR EACH ROW EXECUTE PROCEDURE add_inventory()");
229
-			query.exec("CREATE TRIGGER add_inventory_trigger AFTER INSERT ON make FOR EACH ROW EXECUTE PROCEDURE add_inventory()");
230
-			query.exec("CREATE TRIGGER log_use_trigger AFTER INSERT ON roasting_log FOR EACH ROW EXECUTE PROCEDURE log_use()");
231
-			query.exec("CREATE TRIGGER replace_inventory_trigger AFTER INSERT ON inventory FOR EACH ROW EXECUTE PROCEDURE replace_inventory()");
232
-			query.exec("CREATE TRIGGER subtract_inventory_trigger AFTER INSERT ON loss FOR EACH ROW EXECUTE PROCEDURE subtract_inventory()");
233
-			query.exec("CREATE TRIGGER subtract_inventory_trigger AFTER INSERT ON sale FOR EACH ROW EXECUTE PROCEDURE subtract_inventory()");
234
-			query.exec("CREATE TRIGGER subtract_inventory_trigger AFTER INSERT ON use FOR EACH ROW EXECUTE PROCEDURE subtract_inventory()");
235
-			query.exec("ALTER TABLE ONLY item_files ADD CONSTRAINT item_files_item_fkey FOREIGN KEY (item) REFERENCES items(id)");
236
-			query.exec("ALTER TABLE ONLY transactions ADD CONSTRAINT transactions_item_fkey FOREIGN KEY (item) REFERENCES items(id)");
237
-			query.exec("INSERT INTO TypicaFeatures (feature, enabled, version) VALUES('base-features', TRUE, 1)");
238
-			query = query.invalidate();
239
-		};
163
+            var DBCreateBase = function() {
164
+                var query = new QSqlQuery();
165
+                query.exec("CREATE TABLE IF NOT EXISTS certifications (item bigint NOT NULL, certification text NOT NULL)");
166
+                query.exec("CREATE TABLE IF NOT EXISTS cupping_samples (session bigint NOT NULL, sample text NOT NULL, position bigint NOT NULL, type text NOT NULL, \"time\" timestamp without time zone, machine bigint, point text, item bigint)");
167
+                query.exec("CREATE TABLE cupping_sessions (id bigserial NOT NULL, event text, name text NOT NULL, \"time\" timestamp without time zone NOT NULL, blind boolean NOT NULL, open boolean NOT NULL, note text)");
168
+                query.exec("CREATE TABLE IF NOT EXISTS cuppingforms (session bigint NOT NULL, sample text NOT NULL, position bigint NOT NULL, grader text, finalscore numeric, notes text, serialization text)");
169
+                query.exec("CREATE TABLE IF NOT EXISTS cuppingform_t1 (aroma numeric, flavor numeric, aftertaste numeric, acidity numeric, body numeric, uniformity numeric, balance numeric, cleancup numeric, sweetness numeric, overall numeric, total numeric) INHERITS (cuppingforms)");
170
+                query.exec("CREATE TABLE IF NOT EXISTS invoices (id bigserial PRIMARY KEY NOT NULL, invoice text, vendor text NOT NULL, \"time\" timestamp without time zone NOT NULL)");
171
+                query.exec("CREATE TABLE IF NOT EXISTS invoice_items (invoice_id bigint NOT NULL, record_type text NOT NULL, item_id bigint, description text NOT NULL, cost numeric NOT NULL)");
172
+                query.exec("CREATE TABLE IF NOT EXISTS transactions (\"time\" timestamp without time zone NOT NULL, item bigint NOT NULL)");
173
+                query.exec("CREATE TABLE IF NOT EXISTS inventory (quantity numeric NOT NULL) INHERITS (transactions)");
174
+                query.exec("CREATE TABLE IF NOT EXISTS loss (quantity numeric NOT NULL, reason text) INHERITS (transactions)");
175
+                query.exec("CREATE TABLE IF NOT EXISTS make (quantity numeric NOT NULL) INHERITS (transactions)");
176
+                query.exec("CREATE TABLE IF NOT EXISTS purchase (quantity numeric NOT NULL, cost numeric NOT NULL, vendor text NOT NULL) INHERITS (transactions)");
177
+                query.exec("CREATE TABLE IF NOT EXISTS sale (quantity numeric NOT NULL, customer text) INHERITS (transactions)");
178
+                query.exec("CREATE TABLE IF NOT EXISTS use (quantity numeric NOT NULL) INHERITS (transactions)");
179
+                query.exec("CREATE VIEW all_transactions AS ((((SELECT purchase.\"time\", purchase.item, purchase.quantity, purchase.cost, purchase.vendor, NULL::unknown AS reason, NULL::unknown AS customer, 'PURCHASE' AS type FROM purchase UNION SELECT use.\"time\", use.item, use.quantity, NULL::unknown AS cost, NULL::unknown AS vendor, NULL::unknown AS reason, NULL::unknown AS customer, 'USE' AS type FROM use) UNION SELECT inventory.\"time\", inventory.item, inventory.quantity, NULL::unknown AS cost, NULL::unknown AS vendor, NULL::unknown AS reason, NULL::unknown AS customer, 'INVENTORY' AS type FROM inventory) UNION SELECT loss.\"time\", loss.item, loss.quantity, NULL::unknown AS cost, NULL::unknown AS vendor, loss.reason, NULL::unknown AS customer, 'LOSS' AS type FROM loss) UNION SELECT make.\"time\", make.item, make.quantity, NULL::unknown AS cost, NULL::unknown AS vendor, NULL::unknown AS reason, NULL::unknown AS customer, 'MAKE' AS type FROM make) UNION SELECT sale.\"time\", sale.item, sale.quantity, NULL::unknown AS cost, NULL::unknown AS vendor, NULL::unknown AS reason, sale.customer, 'SALE' AS type FROM sale");
180
+                query.exec("CREATE FUNCTION time_range(bigint) RETURNS integer AS $$ BEGIN IF (SELECT quantity FROM items WHERE id = $1) > 0 THEN RETURN (SELECT current_date - min(time)::date + 1 FROM use WHERE item = $1); ELSE RETURN (SELECT max(time)::date - min(time)::date + 1 FROM use WHERE item = $1); END IF; END; $$ LANGUAGE plpgsql STRICT");
181
+                query.exec("CREATE TABLE IF NOT EXISTS items(id bigint NOT NULL, name text NOT NULL, reference text, unit text NOT NULL, quantity numeric DEFAULT 0, category text)");
182
+                query.exec("CREATE SEQUENCE items_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1");
183
+                query.exec("CREATE TABLE IF NOT EXISTS coffees(origin text NOT NULL, region text, producer text, grade text, milling text, drying text) INHERITS (items)");
184
+                query.exec("CREATE VIEW coffee_history AS SELECT coffees.id, coffees.name, coffees.origin, coffees.quantity AS stock, (SELECT sum(use.quantity) AS sum FROM use WHERE (use.item = coffees.id)) AS used, time_range(coffees.id) AS \"interval\", ((SELECT (sum(use.quantity) / (time_range(use.item))::numeric) FROM use WHERE (use.item = coffees.id) GROUP BY use.item))::numeric(10,2) AS rate, (SELECT (('now'::text)::date + ((coffees.quantity / (SELECT (sum(use.quantity) / (time_range(use.item))::numeric) FROM use WHERE (use.item = coffees.id) GROUP BY use.item)))::integer)) AS \"out\" FROM coffees WHERE (coffees.id IN (SELECT use.item FROM use)) ORDER BY coffees.origin");
185
+                query.exec("CREATE TABLE IF NOT EXISTS current_items (item bigint NOT NULL)");
186
+                query.exec("CREATE TABLE IF NOT EXISTS decaf_coffees (decaf_method text NOT NULL) INHERITS (coffees)");
187
+                query.exec("CREATE TABLE IF NOT EXISTS files (id bigint NOT NULL, name text NOT NULL, type text NOT NULL, note text, file bytea NOT NULL)");
188
+                query.exec("CREATE TABLE IF NOT EXISTS item_files(\"time\" timestamp without time zone NOT NULL, item bigint NOT NULL, files bigint[] NOT NULL)");
189
+                query.exec("CREATE TYPE item_transaction_with_balance AS (\"time\" timestamp without time zone, item bigint, quantity numeric, cost numeric, vendor text, reason text, customer text, type text, balance numeric)");
190
+                query.exec("CREATE TABLE IF NOT EXISTS lb_bag_conversion (item bigint NOT NULL, conversion numeric NOT NULL)");
191
+                query.exec("CREATE TABLE IF NOT EXISTS machine (id bigint NOT NULL, name text NOT NULL)");
192
+                query.exec("CREATE VIEW regular_coffees AS SELECT coffees.id, coffees.name, coffees.reference, coffees.unit, coffees.quantity, coffees.category, coffees.origin, coffees.region, coffees.producer, coffees.grade, coffees.milling, coffees.drying FROM coffees WHERE (NOT (coffees.id IN (SELECT decaf_coffees.id FROM decaf_coffees)))");
193
+                query.exec("CREATE TABLE IF NOT EXISTS roasting_log (\"time\" timestamp without time zone NOT NULL, unroasted_id bigint[], unroasted_quantity numeric[], unroasted_total_quantity numeric, roasted_id bigint, roasted_quantity numeric, transaction_type text NOT NULL, annotation text, machine bigint NOT NULL, duration interval, approval boolean, humidity numeric, barometric numeric, indoor_air numeric, outdoor_air numeric, files bigint[])");
194
+                query.exec("CREATE VIEW short_log AS SELECT roasting_log.\"time\", (SELECT items.name FROM items WHERE (items.id = roasting_log.roasted_id)) AS name, roasting_log.unroasted_total_quantity, roasting_log.roasted_quantity, ((((roasting_log.unroasted_total_quantity - roasting_log.roasted_quantity) / roasting_log.unroasted_total_quantity) * (100)::numeric))::numeric(12,2) AS weight_loss, roasting_log.duration FROM roasting_log ORDER BY roasting_log.\"time\"");
195
+                query.exec("CREATE FUNCTION add_inventory() RETURNS trigger AS $$ BEGIN UPDATE items SET quantity = quantity + NEW.quantity WHERE id = NEW.item; RETURN NEW; END; $$ LANGUAGE plpgsql");
196
+                query.exec("CREATE FUNCTION bags_in_stock(bigint) RETURNS numeric AS $_$SELECT quantity / (SELECT conversion FROM lb_bag_conversion WHERE item = id) FROM items WHERE id = $1;$_$ LANGUAGE sql IMMUTABLE STRICT");
197
+                query.exec("CREATE FUNCTION log_use() RETURNS trigger AS $$ DECLARE i integer := array_lower(NEW.unroasted_id, 1); u integer := array_upper(NEW.unroasted_id, 1); BEGIN WHILE i <= u LOOP INSERT INTO use VALUES(NEW.time, NEW.unroasted_id[i], NEW.unroasted_quantity[i]); i := i + 1; END LOOP; RETURN NEW; END; $$ LANGUAGE plpgsql");
198
+                query.exec("CREATE FUNCTION replace_inventory() RETURNS trigger AS $$ BEGIN UPDATE items SET quantity = NEW.quantity WHERE id = NEW.item; RETURN NEW; END; $$ LANGUAGE plpgsql");
199
+                query.exec("CREATE FUNCTION subtract_inventory() RETURNS trigger AS $$ BEGIN UPDATE items SET quantity = quantity - NEW.quantity WHERE id = NEW.item; RETURN NEW; END; $$ LANGUAGE plpgsql");
200
+                query.exec("CREATE SEQUENCE files_id_seq START WITH 1 INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1");
201
+                query.exec("ALTER TABLE files ALTER COLUMN id SET DEFAULT nextval('files_id_seq'::regclass)");
202
+                query.exec("ALTER TABLE items ALTER COLUMN id SET DEFAULT nextval('items_id_seq'::regclass)");
203
+                query.exec("ALTER TABLE ONLY files ADD CONSTRAINT file_pkey PRIMARY KEY (id)");
204
+                query.exec("ALTER TABLE ONLY items ADD CONSTRAINT items_pkey PRIMARY KEY (id)");
205
+                query.exec("ALTER TABLE ONLY lb_bag_conversion ADD CONSTRAINT lb_bag_conversion_item_key UNIQUE (item)");
206
+                query.exec("ALTER TABLE ONLY roasting_log ADD CONSTRAINT roasting_log_pkey PRIMARY KEY (\"time\", machine)");
207
+                query.exec("CREATE INDEX itemcategories ON items USING btree (category)");
208
+                query.exec("CREATE INDEX itemnames ON items USING btree (name)");
209
+                query.exec("CREATE INDEX roasting_log_index ON roasting_log USING btree (\"time\")");
210
+                query.exec("CREATE INDEX transactionitems ON transactions USING btree (item)");
211
+                query.exec("CREATE INDEX transactiontimes ON transactions USING btree (\"time\")");
212
+                query.exec("CREATE TRIGGER add_inventory_trigger AFTER INSERT ON purchase FOR EACH ROW EXECUTE PROCEDURE add_inventory()");
213
+                query.exec("CREATE TRIGGER add_inventory_trigger AFTER INSERT ON make FOR EACH ROW EXECUTE PROCEDURE add_inventory()");
214
+                query.exec("CREATE TRIGGER log_use_trigger AFTER INSERT ON roasting_log FOR EACH ROW EXECUTE PROCEDURE log_use()");
215
+                query.exec("CREATE TRIGGER replace_inventory_trigger AFTER INSERT ON inventory FOR EACH ROW EXECUTE PROCEDURE replace_inventory()");
216
+                query.exec("CREATE TRIGGER subtract_inventory_trigger AFTER INSERT ON loss FOR EACH ROW EXECUTE PROCEDURE subtract_inventory()");
217
+                query.exec("CREATE TRIGGER subtract_inventory_trigger AFTER INSERT ON sale FOR EACH ROW EXECUTE PROCEDURE subtract_inventory()");
218
+                query.exec("CREATE TRIGGER subtract_inventory_trigger AFTER INSERT ON use FOR EACH ROW EXECUTE PROCEDURE subtract_inventory()");
219
+                query.exec("ALTER TABLE ONLY item_files ADD CONSTRAINT item_files_item_fkey FOREIGN KEY (item) REFERENCES items(id)");
220
+                query.exec("ALTER TABLE ONLY transactions ADD CONSTRAINT transactions_item_fkey FOREIGN KEY (item) REFERENCES items(id)");
221
+                query.exec("INSERT INTO TypicaFeatures (feature, enabled, version) VALUES('base-features', TRUE, 1)");
222
+                query = query.invalidate();
223
+            };
240 224
 		
241 225
 		/* Some changes to the database are required for sample roasting features in
242 226
 		   Typica 1.6 and later. */
@@ -293,7 +277,19 @@ type="push" />
293 277
 			query.exec("CREATE OR REPLACE FUNCTION log_use() RETURNS trigger AS $$ DECLARE i integer := array_lower(NEW.unroasted_id, 1); u integer := array_upper(NEW.unroasted_id, 1); BEGIN IF NEW.transaction_type = 'ROAST' THEN WHILE i <= u LOOP INSERT INTO use (time, item, quantity) VALUES(NEW.time, NEW.unroasted_id[i], NEW.unroasted_quantity[i]); i := i + 1; END LOOP; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql");
294 278
 			query.exec("UPDATE TypicaFeatures SET version = 4 WHERE feature = 'base-features'");
295 279
 		};
296
-		
280
+                var DBUpdateReminders = function() {
281
+                    var query = new QSqlQuery;
282
+                    query.exec("CREATE TABLE IF NOT EXISTS reminders (id bigserial PRIMARY KEY NOT NULL, reminder text NOT NULL)");
283
+                    query.exec("UPDATE TypicaFeatures SET version = 5 WHERE feature = 'base-features'");
284
+                    query = query.invalidate();
285
+                };
286
+		var DBUpdateSpecification = function() {
287
+                    var query = new QSqlQuery;
288
+                    query.exec("CREATE TABLE IF NOT EXISTS roasting_specification (\"time\" timestamp without time zone NOT NULL, item bigint NOT NULL, loss numeric, tolerance numeric, notes text)");
289
+                    query.exec("UPDATE TypicaFeatures SET version = 6 WHERE feature = 'base-features'");
290
+                    query = query.invalidate();
291
+                };
292
+                
297 293
 		query = new QSqlQuery();
298 294
 		/* A table keeps track of database versioning information. This table is created
299 295
 		   if required. */
@@ -304,23 +300,31 @@ type="push" />
304 300
 		query.exec("SELECT feature, enabled, version FROM TypicaFeatures WHERE feature = 'base-features'");
305 301
 		if(query.next())
306 302
 		{
307
-			if(query.value(2) < 1)
308
-			{
309
-				DBCreateBase();
310
-			}
311
-			if(query.value(2) < 2)
312
-			{
313
-				DBUpdateMultiUser();
314
-				DBUpdateHistory();
315
-			}
316
-			if(query.value(2) < 3)
317
-			{
318
-				DBUpdateNotifications();
319
-			}
320
-			if(query.value(2) < 4)
321
-			{
322
-				DBUpdateTriggers();
323
-			}
303
+                    if(query.value(2) < 1)
304
+                    {
305
+                            DBCreateBase();
306
+                    }
307
+                    if(query.value(2) < 2)
308
+                    {
309
+                            DBUpdateMultiUser();
310
+                            DBUpdateHistory();
311
+                    }
312
+                    if(query.value(2) < 3)
313
+                    {
314
+                            DBUpdateNotifications();
315
+                    }
316
+                    if(query.value(2) < 4)
317
+                    {
318
+                            DBUpdateTriggers();
319
+                    }
320
+                    if(query.value(2) < 5)
321
+                    {
322
+                        DBUpdateReminders();
323
+                    }
324
+                    if(query.value(2) < 6)
325
+                    {
326
+                        DBUpdateSpecification();
327
+                    }
324 328
 		}
325 329
 		else
326 330
 		{

+ 484
- 275
config/Windows/newbatch.xml View File

@@ -1,142 +1,269 @@
1 1
 <window id="batchWindow">
2 2
     <menu name="Batch">
3
-        <item id="new" shortcut="Ctrl+N">New Batch</item>
3
+        <item id="new" shortcut="Ctrl+N">New Batch...</item>
4 4
     </menu>
5
-	<layout type="horizontal">
6
-    <layout type="vertical">
7
-        <layout type="horizontal">
8
-			<label>Machine:</label>
9
-			<line id="machine" writable="false" />
10
-			<label>Unit:</label>
11
-			<sqldrop id="unit" />
12
-            <stretch />
5
+    <layout type="horizontal">
6
+        <layout type="vertical">
7
+            <layout type="horizontal">
8
+                <label>Machine:</label>
9
+                <line id="machine" writable="false" />
10
+                <label>Unit:</label>
11
+                <sqldrop id="unit" />
12
+                <stretch />
13
+            </layout>
14
+            <layout type="horizontal">
15
+                <label>Roasted Coffee:</label>
16
+                <sqldrop data="0" display="1" showdata="true" id="roasted">
17
+                    <null />
18
+                    <query>SELECT id, name FROM items WHERE category = 'Coffee: Roasted' AND id IN (SELECT item FROM current_items) ORDER BY name</query>
19
+                </sqldrop>
20
+                <stretch />
21
+            </layout>
22
+            <label>Green Coffee:</label>
23
+            <sqltablearray columns="3" id="greens">
24
+                <column name="Coffee" delegate="sql" showdata="true" null="true" nulltext="Delete" nulldata="delete" data="0" display="1">SELECT id, name FROM coffees WHERE quantity &lt;&gt; 0 ORDER BY name</column>
25
+                <column name="Weight" delegate="numeric" />
26
+                <column name="Remaining" />
27
+            </sqltablearray>
28
+            <layout type="horizontal">
29
+                <label>Green Weight:</label>
30
+                <line id="green" writable="false">0.0</line>
31
+            </layout>
32
+            <layout type="horizontal">
33
+                <button name="Load Profile" type="push" id="load" />
34
+                <button name="No Profile" type="push" id="noprofile" />
35
+            </layout>
36
+            <layout type="horizontal">
37
+                <label>Time:</label>
38
+                <line id="time" writable="false" />
39
+                <label>Duration:</label>
40
+                <line id="duration" writable="false" />
41
+            </layout>
42
+            <layout type="horizontal">
43
+                <label>Roasted Weight:</label>
44
+                <line id="roast" validator="numeric">0.0</line>
45
+                <label>Weight Loss:</label>
46
+                <line id="wloss" writable="false" />
47
+                <button type="check" id="approval" name="Approved" />
48
+            </layout>
49
+            <layout type="horizontal">
50
+                <label>Annotation:</label>
51
+                <textarea id="annotation" />
52
+            </layout>
53
+            <layout type="horizontal">
54
+                <button name="Submit" id="submit" type="push" />
55
+                <button name="Save log as target profile" type="check" id="target" />
56
+            </layout>
13 57
         </layout>
14
-        <layout type="horizontal">
15
-            <label>Roasted Coffee:</label>
16
-            <sqldrop data="0" display="1" showdata="true" id="roasted">
17
-                <null />
18
-                <query>SELECT id, name FROM items WHERE category = 'Coffee: Roasted' AND id IN (SELECT item FROM current_items) ORDER BY name</query>
19
-            </sqldrop>
58
+        <layout type="vertical">
59
+            <label>Connected Scales</label>
60
+            <layout type="vertical" id="scales" />
61
+            <label>Expected Weight Loss</label>
62
+            <line id="lossspec" writable="false" />
63
+            <label>Expected Roasted Weight</label>
64
+            <layout type="horizontal">
65
+                <label>Min:</label>
66
+                <line id="minroastweight" writable="false" />
67
+            </layout>
68
+            <layout type="horizontal">
69
+                <label>Expected:</label>
70
+                <line id="expectedroastweight" writable="false" />
71
+            </layout>
72
+            <layout type="horizontal">
73
+                <label>Max:</label>
74
+                <line id="maxroastweight" writable="false" />
75
+            </layout>
76
+            <label>Specification Details</label>
77
+            <textarea id="specnotes" />
20 78
             <stretch />
21 79
         </layout>
22
-        <label>Green Coffee:</label>
23
-        <sqltablearray columns="2" id="greens">
24
-            <column name="Coffee" delegate="sql" showdata="true" null="true" nulltext="Delete" nulldata="delete" data="0" display="1">SELECT id, name FROM coffees WHERE quantity &lt;&gt; 0 ORDER BY name</column>
25
-            <column name="Weight" delegate="numeric" />
26
-        </sqltablearray>
27
-        <layout type="horizontal">
28
-            <label>Green Weight:</label>
29
-            <line id="green" writable="false">0.0</line>
30
-        </layout>
31
-        <layout type="horizontal">
32
-            <button name="Load Profile" type="push" id="load" />
33
-            <button name="No Profile" type="push" id="noprofile" />
34
-        </layout>
35
-        <layout type="horizontal">
36
-            <label>Time:</label>
37
-            <line id="time" writable="false" />
38
-            <label>Duration:</label>
39
-            <line id="duration" writable="false" />
40
-        </layout>
41
-        <layout type="horizontal">
42
-            <label>Roasted Weight:</label>
43
-            <line id="roast" validator="numeric" />
44
-            <label>Weight Loss:</label>
45
-            <line id="wloss" writable="false" />
46
-            <button type="check" id="approval" name="Approved" />
47
-        </layout>
48
-        <layout type="horizontal">
49
-            <label>Annotation:</label>
50
-            <textarea id="annotation" />
51
-        </layout>
52
-        <layout type="horizontal">
53
-            <button name="Submit" id="submit" type="push" />
54
-            <button name="Save log as target profile" type="check" id="target" />
55
-        </layout>
56 80
     </layout>
57
-	<layout type="vertical">
58
-		<label>Connected Scales</label>
59
-		<layout type="vertical" id="scales" />
60
-		<stretch />
61
-	</layout>
62
-	</layout>
63 81
     <program>
64 82
         <![CDATA[
65
-			var unitBox = findChildObject(this, 'unit');
66
-			unitBox.addItem("g");
67
-			unitBox.addItem("Kg");
68
-			unitBox.addItem("oz");
69
-			unitBox.addItem("lb");
70
-			unitBox.currentIndex = (QSettings.value("script/batch_unit", unitBox.findText("lb")));
71
-			var machine = findChildObject(this, "machine");
72
-			machine.setText(selectedRoasterName + " (" + selectedRoasterID + ")");
73
-			var newMenu = findChildObject(this, 'new');
74
-			newMenu.triggered.connect(function() {
75
-				var bwindow = createWindow("batchWindow");
76
-				bwindow.windowTitle = "Typica - [*]New Batch";
77
-			});
78
-			var batch = this;
79
-			var table = findChildObject(this, 'greens');
80
-			var green = findChildObject(this, 'green');
81
-			var model = table.model();
83
+            var unitBox = findChildObject(this, 'unit');
84
+            unitBox.addItem("g");
85
+            unitBox.addItem("Kg");
86
+            unitBox.addItem("oz");
87
+            unitBox.addItem("lb");
88
+            unitBox.currentIndex = (QSettings.value("script/batch_unit", unitBox.findText("lb")));
89
+            unitBox['currentIndexChanged(int)'].connect(function() {
90
+                QSettings.setValue("script/batch_unit", unitBox.currentIndex);
91
+            });
92
+            var machine = findChildObject(this, "machine");
93
+            machine.setText(selectedRoasterName + " (" + selectedRoasterID + ")");
94
+            var newMenu = findChildObject(this, 'new');
95
+            newMenu.triggered.connect(function() {
96
+                var bwindow = createWindow("batchWindow");
97
+                bwindow.windowTitle = "Typica - [*]New Batch";
98
+            });
99
+            var batch = this;
100
+            var table = findChildObject(this, 'greens');
101
+            var green = findChildObject(this, 'green');
102
+            var model = table.model();
82 103
             var lossField = findChildObject(this, 'wloss');
83 104
             lossField.maximumWidth = 80;
84 105
             var roasted = findChildObject(this, 'roasted');
85 106
             var roastwt = findChildObject(this, 'roast');
86 107
             roastwt.maximumWidth = 80;
87
-			var scalesLayout = findChildObject(this, 'scales');
88
-			scalesLayout.spacing = 10;
89
-			if(navigationwindow.loggingWindow.scales.length > 0) {
90
-				for(var i = 0; i < navigationwindow.loggingWindow.scales.length; i++) {
91
-					var scale = navigationwindow.loggingWindow.scales[i];
92
-					var label = new DragLabel();
93
-					var weighButton = new QPushButton();
94
-					weighButton.text = "Weigh";
95
-					weighButton.clicked.connect(scale.weigh);
96
-					label.updateMeasurement = function(m, u) {
97
-						switch(unitBox.currentIndex) {
98
-							case 0:
99
-								this.text = Units.convertWeight(m, u, Units.Gram).toFixed(1);
100
-								break;
101
-							case 1:
102
-								this.text = Units.convertWeight(m, u, Units.Kilogram).toFixed(4);
103
-								break;
104
-							case 2:
105
-								this.text = Units.convertWeight(m, u, Units.Ounce).toFixed(3);
106
-								break;
107
-							case 3:
108
-								this.text = Units.convertWeight(m, u, Units.Pound).toFixed(4);
109
-								break;
110
-						}
111
-					};
112
-					scalesLayout.addWidget(label);
113
-					scalesLayout.addWidget(weighButton);
114
-					scale.newMeasurement.connect(function(m, u) {
115
-						label.updateMeasurement(m, u);
116
-					});
117
-					scale.weigh();
118
-					unitBox['currentIndexChanged(int)'].connect(scale.weigh);
119
-				}
120
-			}
121
-			model.dataChanged.connect(function() {
122
-				var deleteRow = -1;
123
-				/* The combo box delegate updates user data before display data
124
-				   and this code is executed before the model update is fully
125
-				   complete. Rather than rely on this behavior continuing, we
126
-				   check that the display value has also been updated and defer
127
-				   row removal until both updates are complete.
128
-				*/
129
-				while((deleteRow = table.findData("delete", 0)) > -1) {
130
-					if(table.data(deleteRow, 0, 0) == "Delete") {
131
-						table.removeRow(table.findData("delete", 0));
132
-					} else {
133
-						break;
134
-					}
135
-				}
136
-				green.text = table.columnSum(1, 0);
137
-				table.resizeColumnToContents(0);
108
+            var scalesLayout = findChildObject(this, 'scales');
109
+            scalesLayout.spacing = 10;
110
+            if(navigationwindow.loggingWindow.scales.length > 0) {
111
+                for(var i = 0; i < navigationwindow.loggingWindow.scales.length; i++) {
112
+                    var scale = navigationwindow.loggingWindow.scales[i];
113
+                    var label = new DragLabel();
114
+                    var weighButton = new QPushButton();
115
+                    weighButton.text = "Weigh";
116
+                    weighButton.clicked.connect(scale.weigh);
117
+                    label.updateMeasurement = function(m, u) {
118
+                        switch(unitBox.currentIndex) {
119
+                            case 0:
120
+                                this.text = Units.convertWeight(m, u, Units.Gram).toFixed(1);
121
+                                break;
122
+                            case 1:
123
+                                this.text = Units.convertWeight(m, u, Units.Kilogram).toFixed(4);
124
+                                break;
125
+                            case 2:
126
+                                this.text = Units.convertWeight(m, u, Units.Ounce).toFixed(3);
127
+                                break;
128
+                            case 3:
129
+                                this.text = Units.convertWeight(m, u, Units.Pound).toFixed(4);
130
+                                break;
131
+                        }
132
+                    };
133
+                    scalesLayout.addWidget(label);
134
+                    scalesLayout.addWidget(weighButton);
135
+                    scale.newMeasurement.connect(function(m, u) {
136
+                        label.updateMeasurement(m, u);
137
+                    });
138
+                    scale.weigh();
139
+                    unitBox['currentIndexChanged(int)'].connect(scale.weigh);
140
+                }
141
+            }
142
+            var remainingStock = new Array();
143
+            var query = new QSqlQuery();
144
+            query.exec("SELECT id, quantity, (SELECT conversion FROM lb_bag_conversion WHERE item = id) FROM coffees WHERE quantity <> 0");
145
+            while(query.next()) {
146
+                remainingStock.push({id: query.value(0),
147
+                                     quantity: query.value(1),
148
+                                     conversion: query.value(2)});
149
+            }
150
+            query = query.invalidate();
151
+            var convertToPounds = function(w, u) {
152
+                switch(u) {
153
+                    case "g":
154
+                        return w * 0.0022;
155
+                    case "oz":
156
+                        return w * 0.0625;
157
+                    case "Kg":
158
+                        return w * 2.2;
159
+                }
160
+                return w;
161
+            };
162
+            var convertFromPounds = function(w, u) {
163
+                switch(u) {
164
+                    case "g":
165
+                        return w / 0.0022;
166
+                    case "oz":
167
+                        return w / 0.0625;
168
+                    case "Kg":
169
+                        return w / 2.2;
170
+                }
171
+                return w;
172
+            };
173
+            var specnotes = findChildObject(this, 'specnotes');
174
+            specnotes.readOnly = true;
175
+            var lossspec = findChildObject(this, 'lossspec');
176
+            var minfield = findChildObject(this, 'minroastweight');
177
+            var midfield = findChildObject(this, 'expectedroastweight');
178
+            var maxfield = findChildObject(this, 'maxroastweight');
179
+            var minloss = 0;
180
+            var maxloss = 0;
181
+            var expectloss = 0;
182
+            var updateGreenTable = function() {
183
+                var deleteRow = -1;
184
+                /* The combo box delegate updates user data before display data
185
+                and this code is executed before the model update is fully
186
+                complete. Rather than rely on this behavior continuing, we
187
+                check that the display value has also been updated and defer
188
+                row removal until both updates are complete.
189
+                */
190
+                while((deleteRow = table.findData("delete", 0)) > -1) {
191
+                    if(table.data(deleteRow, 0, 0) == "Delete") {
192
+                        table.removeRow(table.findData("delete", 0));
193
+                    } else {
194
+                        break;
195
+                    }
196
+                }
197
+                green.text = table.columnSum(1, 0);
198
+                table.resizeColumnToContents(0);
199
+                var gid = 0;
200
+                var r = 0;
201
+                while(gid >= 0)
202
+                {
203
+                    gid = Number(table.data(r, 0, 32));
204
+                    if(isNaN(gid))
205
+                    {
206
+                        gid = -1;
207
+                        break;
208
+                    }
209
+                    var bagConversion = 1;
210
+                    for(var i = 0; i < remainingStock.length; i++)
211
+                    {
212
+                        if(gid == Number(remainingStock[i].id))
213
+                        {
214
+                            var displayValue = Number(remainingStock[i].quantity);
215
+                            bagConversion = Number(remainingStock[i].conversion);
216
+                            if(!isNaN(Number(table.data(r, 1, 0))))
217
+                            {
218
+                                var change = Number(table.data(r, 1, 0));
219
+                                switch(unitBox.currentIndex)
220
+                                {
221
+                                    case 0:
222
+                                        change = convertToPounds(change, "g");
223
+                                        break;
224
+                                    case 1:
225
+                                        change = convertToPounds(change, "Kg");
226
+                                        break;
227
+                                    case 2:
228
+                                        change = convertToPounds(change, "oz");
229
+                                        break;
230
+                                }
231
+                                displayValue -= change;
232
+                            }
233
+                            var bagCount = (displayValue / bagConversion).toFixed(2);
234
+                            switch(unitBox.currentIndex)
235
+                            {
236
+                                case 0:
237
+                                    displayValue = convertFromPounds(displayValue, "g");
238
+                                    break;
239
+                                case 1:
240
+                                    displayValue = convertFromPounds(displayValue, "Kg");
241
+                                    break;
242
+                                case 2:
243
+                                    displayValue = convertFromPounds(displayValue, "oz");
244
+                                    break;
245
+                            }
246
+                            displayValue = "" + Number(displayValue).toFixed(3) + " (" + Number(bagCount).toFixed(3) + " bags)";
247
+                            if(table.data(r, 2, 0) != displayValue)
248
+                            {
249
+                                table.setData(r, 2, displayValue, 0);
250
+                                table.setData(r, 2, displayValue, 2);
251
+                                table.resizeColumnToContents(2);
252
+                            }
253
+                        }
254
+                    }
255
+                    r++;
256
+                }
138 257
                 if(parseFloat(green.text) > 0)
139 258
                 {
259
+                    var expectedLossDesc = "";
260
+                    if(lossspec.text.length > 0) {
261
+                        if(minloss != expectloss) {
262
+                            minfield.text = (-(parseFloat(green.text)) * (maxloss - 1)).toFixed(2);
263
+                            maxfield.text = (-(parseFloat(green.text)) * (minloss - 1)).toFixed(2);
264
+                        }
265
+                        midfield.text = (-(parseFloat(green.text)) * (expectloss - 1)).toFixed(2);
266
+                    }
140 267
                     if(parseFloat(roastwt.text) > 0)
141 268
                     {
142 269
                         lossField.text = (((parseFloat(green.text) - parseFloat(roastwt.text)) / parseFloat(green.text)) * 100).toFixed(2) + "%";
@@ -146,7 +273,9 @@
146 273
                         lossField.text = "100%";
147 274
                     }
148 275
                 }
149
-			});
276
+            };
277
+            model.dataChanged.connect(updateGreenTable);
278
+            unitBox['currentIndexChanged(int)'].connect(updateGreenTable);
150 279
             roastwt.textChanged.connect(function() {
151 280
                 if(parseFloat(green.text) > 0)
152 281
                 {
@@ -160,21 +289,10 @@
160 289
                     }
161 290
                 }
162 291
             });
163
-			var convertToPounds = function(w, u) {
164
-				switch(u) {
165
-					case "g":
166
-						return w * 0.0022;
167
-					case "oz":
168
-						return w * 0.0625;
169
-					case "Kg":
170
-						return w * 2.2;
171
-				}
172
-				return w;
173
-			};
174 292
             var profilebutton = findChildObject(this, 'load');
175 293
             profilebutton.setEnabled(false);
176 294
             roasted['currentIndexChanged(int)'].connect(function() {
177
-				table.clear();
295
+                table.clear();
178 296
                 var query = new QSqlQuery();
179 297
                 var q = "SELECT EXISTS(SELECT 1 FROM item_files WHERE item = ";
180 298
                 q = q + roasted.currentData();
@@ -234,152 +352,243 @@
234 352
                         }
235 353
                     }
236 354
                 }
355
+                query.prepare("SELECT loss, tolerance, notes FROM roasting_specification WHERE item = :id1 AND time = (SELECT max(time) FROM roasting_specification WHERE item = :id2)");
356
+                query.bind(":id1", roasted.currentData());
357
+                query.bind(":id2", roasted.currentData());
358
+                query.exec();
359
+                var lossSpecDescription = "";
360
+                if(query.next()) {
361
+                    if(query.value(0).length > 0) {
362
+                        lossSpecDescription += (Number(query.value(0)) * 100).toFixed(2);
363
+                        minloss = Number(query.value(0));
364
+                        maxloss = Number(query.value(0));
365
+                        expectloss = Number(query.value(0));
366
+                    }
367
+                    if(query.value(1).length > 0) {
368
+                        lossSpecDescription += " +/- " + (Number(query.value(1)) * 100).toFixed(2);
369
+                        minloss -= Number(query.value(1));
370
+                        maxloss += Number(query.value(1));
371
+                    }
372
+                    if(lossSpecDescription.length > 0) {
373
+                        lossSpecDescription += "%";
374
+                    }
375
+                    lossspec.text = lossSpecDescription;
376
+                    specnotes.plainText = query.value(2);
377
+                } else {
378
+                    lossspec.text = "";
379
+                    specnotes.plainText = "";
380
+                }
381
+                roastestimate.text = "";
382
+                query = query.invalidate();
383
+            });
384
+            var validateCapacity = function() {
385
+                if(checkCapacity == "true") {
386
+                    if(convertToPounds(parseFloat(green.text), unitBox.currentText) > poundsCapacity) {
387
+                        return false;
388
+                    }
389
+                }
390
+                return true;
391
+            }
392
+            profilebutton.clicked.connect(function() {
393
+                var proceed = false;
394
+                if(validateCapacity()) {
395
+                    proceed = true;
396
+                } else {
397
+                    proceed = displayWarning(TTR("batchWindow", "Suspicious Input"),
398
+                    TTR("batchWindow", "Entered green coffee weight exceeds maximum batch size. Continue?"));
399
+                }
400
+                if(proceed) {
401
+                    doLoadProfile();
402
+                }
237 403
             });
238
-			profilebutton.clicked.connect(function() {
239
-				batch.windowModified = true;
240
-				currentBatchInfo = batch;
241
-				query = new QSqlQuery();
404
+            var doLoadProfile = function() {
405
+                batch.windowModified = true;
406
+                currentBatchInfo = batch;
407
+                query = new QSqlQuery();
242 408
                 var q = "SELECT files FROM item_files WHERE item = :item AND time = (SELECT max(time) FROM item_files WHERE item = :again)";
243
-				query.prepare(q);
409
+                query.prepare(q);
244 410
                 query.bind(":item", Number(roasted.currentData()));
245 411
                 query.bind(":again", Number(roasted.currentData()));
246
-				query.exec();
247
-				var graph;
248
-				var log;
249
-				if(query.next())
250
-				{
251
-					var files = query.value(0);
252
-					files = files.replace("{", "(");
253
-					files = files.replace("}", ")");
254
-					q = "SELECT file, name FROM files WHERE id IN ";
255
-					q = q + files;
256
-					q = q + " AND type = 'profile'";
257
-					query.exec(q);
258
-					if(query.next())
259
-					{
260
-						var targetseries = -1;
261
-						var buffer = new QBuffer(query.value(0));
262
-						var pname = query.value(1);
263
-						var input = new XMLInput(buffer, 1);
264
-						graph = findChildObject(navigationwindow.loggingWindow, 'graph');
265
-						log = findChildObject(navigationwindow.loggingWindow, 'log');
266
-						log.clear();
267
-						graph.clear();
268
-						input.newTemperatureColumn.connect(log.setHeaderData);
269
-						input.newTemperatureColumn.connect(function(col, text) {
270
-							if(text == navigationwindow.loggingWindow.targetcolumnname)
271
-							{
272
-								targetseries = col;
273
-							}
274
-						});
275
-						input.newAnnotationColumn.connect(log.setHeaderData);
276
-						input.measure.connect(graph.newMeasurement);
277
-						input.measure.connect(log.newMeasurement);
278
-						input.measure.connect(function(data, series) {
279
-							if(series == targetseries)
280
-							{
281
-								targetDetector.newMeasurement(data);
282
-							}
283
-						});
284
-						var lc;
285
-						input.lastColumn.connect(function(c) {
286
-							lc = c;
287
-							QSettings.setValue("liveColumn", c + 1);
288
-							navigationwindow.loggingWindow.postLoadColumnSetup(c)
289
-						});
290
-						input.annotation.connect(function(note, tcol, ncol) {
291
-							for(var i = tcol; i < ncol; i++) {
292
-								log.newAnnotation(note, i, ncol);
293
-							}
294
-						});
295
-					}
296
-				}
412
+                query.exec();
413
+                var graph;
414
+                var log;
415
+                if(query.next())
416
+                {
417
+                    var files = query.value(0);
418
+                    files = files.replace("{", "(");
419
+                    files = files.replace("}", ")");
420
+                    q = "SELECT file, name FROM files WHERE id IN ";
421
+                    q = q + files;
422
+                    q = q + " AND type = 'profile'";
423
+                    query.exec(q);
424
+                    if(query.next())
425
+                    {
426
+                        var targetseries = -1;
427
+                        var buffer = new QBuffer(query.value(0));
428
+                        var pname = query.value(1);
429
+                        var input = new XMLInput(buffer, 1);
430
+                        graph = findChildObject(navigationwindow.loggingWindow, 'graph');
431
+                        log = findChildObject(navigationwindow.loggingWindow, 'log');
432
+                        log.clear();
433
+                        graph.clear();
434
+                        input.newTemperatureColumn.connect(log.setHeaderData);
435
+                        input.newTemperatureColumn.connect(function(col, text) {
436
+                            if(text == navigationwindow.loggingWindow.targetcolumnname)
437
+                            {
438
+                                targetseries = col;
439
+                            }
440
+                        });
441
+                        input.newAnnotationColumn.connect(log.setHeaderData);
442
+                        input.measure.connect(graph.newMeasurement);
443
+                        input.measure.connect(log.newMeasurement);
444
+                        input.measure.connect(function(data, series) {
445
+                            if(series == targetseries)
446
+                            {
447
+                                targetDetector.newMeasurement(data);
448
+                            }
449
+                        });
450
+                        var lc;
451
+                        input.lastColumn.connect(function(c) {
452
+                            lc = c;
453
+                            QSettings.setValue("liveColumn", c + 1);
454
+                            navigationwindow.loggingWindow.postLoadColumnSetup(c)
455
+                        });
456
+                        input.annotation.connect(function(note, tcol, ncol) {
457
+                            for(var i = tcol; i < ncol; i++) {
458
+                                log.newAnnotation(note, i, ncol);
459
+                            }
460
+                        });
461
+                    }
462
+                }
297 463
                 query = query.invalidate();
298
-				navigationwindow.loggingWindow.windowTitle = "Typica - [*]" + pname;
299
-				navigationwindow.loggingWindow.raise();
300
-				navigationwindow.loggingWindow.activateWindow();
301
-				graph.updatesEnabled = false;
302
-				log.updatesEnabled = false;
303
-				input.input();
304
-				log.updatesEnabled = true;
305
-				graph.updatesEnabled = true;
306
-				log.newAnnotation("End", 1, lc);
307
-			});
308
-			var noprofilebutton = findChildObject(this, 'noprofile');
309
-			noprofilebutton.clicked.connect(function() {
310
-				batch.windowModified = true;
311
-				currentBatchInfo = batch;
312
-				navigationwindow.loggingWindow.raise();
313
-				navigationwindow.loggingWindow.activateWindow();
314
-			});
315
-			var submitbutton = findChildObject(this, 'submit');
316
-			var timefield = findChildObject(this, 'time');
317
-			var notes = findChildObject(this, 'annotation');
318
-			var duration = findChildObject(this, 'duration');
319
-			var approval = findChildObject(this, 'approval');
320
-			approval.checked = true;
321
-			var target = findChildObject(this, 'target');
322
-			submitbutton.clicked.connect(function() {
323
-				checkQuery = new QSqlQuery();
324
-				checkQuery.exec("SELECT 1 FROM machine WHERE id = " + selectedRoasterID);
325
-				if(!checkQuery.next())
326
-				{
327
-					checkQuery.prepare("INSERT INTO machine (id, name) VALUES(:id, :name)");
328
-					checkQuery.bind(":id", selectedRoasterID);
329
-					checkQuery.bind(":name", selectedRoasterName);
330
-					checkQuery.exec();
331
-				}
332
-				checkQuery = checkQuery.invalidate();
333
-				var q = "INSERT INTO files (id, name, type, note, file) VALUES(default, :name, 'profile', NULL, :data) RETURNING id";
334
-				query = new QSqlQuery();
335
-				query.prepare(q);
336
-				query.bind(":name", timefield.text + " " + roasted.currentText);
337
-				query.bindFileData(":data", batch.tempData);
338
-				query.exec();
339
-				query.next();
340
-				var fileno = query.value(0);
341
-				var file = new QFile(batch.tempData);
342
-				file.remove();
343
-				var q2 = "INSERT INTO roasting_log (time, unroasted_id, unroasted_quantity, unroasted_total_quantity, roasted_id, roasted_quantity, transaction_type, annotation, machine, duration, approval, humidity, barometric, indoor_air, outdoor_air, files) VALUES(:time, ";
344
-				q2 = q2 + table.columnArray(0, 32);
345
-				q2 = q2 + ", ";
346
-				for(var i = 0; table.data(i, 1, 0).value != ""; i++)
347
-				{
348
-					table.setData(i, 1, convertToPounds(parseFloat(table.data(i, 1, 0)), unitBox.currentText) ,32)
349
-				}
350
-				q2 = q2 + table.columnArray(1, 32);
351
-				q2 = q2 + ", ";
352
-				q2 = q2 + convertToPounds(parseFloat(green.text), unitBox.currentText);
353
-				q2 = q2 + ", ";
354
-				q2 = q2 + roasted.currentData();
355
-				q2 = q2 + ", ";
356
-				q2 = q2 + convertToPounds(parseFloat(roastwt.text), unitBox.currentText);
357
-				q2 = q2 + ", 'ROAST', :annotation, ";
358
-				q2 = q2 + selectedRoasterID;
359
-				q2 = q2 + ", :duration, :approval, NULL, NULL, NULL, NULL, '{";
360
-				q2 = q2 + fileno;
361
-				q2 = q2 + "}')";
362
-				query2 = new QSqlQuery();
363
-				query2.prepare(q2);
364
-				query2.bind(":time", timefield.text);
365
-				query2.bind(":annotation", notes.plainText);
366
-				query2.bind(":duration", duration.text);
367
-				query2.bind(":approval", approval.checked);
368
-				query2.exec();
464
+                navigationwindow.loggingWindow.windowTitle = "Typica - [*]" + pname;
465
+                navigationwindow.loggingWindow.raise();
466
+                navigationwindow.loggingWindow.activateWindow();
467
+                graph.updatesEnabled = false;
468
+                log.updatesEnabled = false;
469
+                input.input();
470
+                log.updatesEnabled = true;
471
+                graph.updatesEnabled = true;
472
+                log.newAnnotation(TTR("batchWindow", "End"), 1, lc);
473
+            }
474
+            var noprofilebutton = findChildObject(this, 'noprofile');
475
+            noprofilebutton.clicked.connect(function() {
476
+                var proceed = false;
477
+                if(validateCapacity()) {
478
+                    proceed = true;
479
+                } else {
480
+                    proceed = displayWarning(TTR("batchWindow", "Suspicious Input"),
481
+                    TTR("batchWindow", "Entered green coffee weight exceeds maximum batch size. Continue?"));
482
+                }
483
+                if(proceed) {
484
+                    doNoProfile();
485
+                }
486
+            });
487
+            var doNoProfile = function() {
488
+                batch.windowModified = true;
489
+                currentBatchInfo = batch;
490
+                navigationwindow.loggingWindow.raise();
491
+                navigationwindow.loggingWindow.activateWindow();
492
+            }
493
+            var submitbutton = findChildObject(this, 'submit');
494
+            var timefield = findChildObject(this, 'time');
495
+            var notes = findChildObject(this, 'annotation');
496
+            var duration = findChildObject(this, 'duration');
497
+            var approval = findChildObject(this, 'approval');
498
+            approval.checked = true;
499
+            var target = findChildObject(this, 'target');
500
+            var checkSubmitEnable = function () {
501
+                if(roasted.currentIndex > 0) {
502
+                    if(timefield.text.length > 0) {
503
+                        if(duration.text.length > 0) {
504
+                            if(batch.tempData.length > 0) {
505
+                                if(green.text.length > 0) {
506
+                                    return true;
507
+                                }
508
+                            }
509
+                        }
510
+                    }
511
+                }
512
+                return false;
513
+            }
514
+            submitbutton.clicked.connect(function() {
515
+                var proceed = false;
516
+                if(validateCapacity()) {
517
+                    proceed = true;
518
+                } else {
519
+                    proceed = displayWarning(TTR("batchWindow", "Suspicious Input"),
520
+                    TTR("batchWindow", "Entered green coffee weight exceeds maximum batch size. Continue?"));
521
+                }
522
+                if(proceed) {
523
+                    if(checkSubmitEnable()) {
524
+                        doSubmit();
525
+                    } else {
526
+                        displayError(TTR("batchWindow", "Incomplete Input"),
527
+                        TTR("batchWindow", "Some required information is not available. Please check inputs and try again."));
528
+                    }
529
+                }
530
+            });
531
+            var doSubmit = function() {
532
+                checkQuery = new QSqlQuery();
533
+                checkQuery.exec("SELECT 1 FROM machine WHERE id = " + selectedRoasterID);
534
+                if(!checkQuery.next())
535
+                {
536
+                    checkQuery.prepare("INSERT INTO machine (id, name) VALUES(:id, :name)");
537
+                    checkQuery.bind(":id", selectedRoasterID);
538
+                    checkQuery.bind(":name", selectedRoasterName);
539
+                    checkQuery.exec();
540
+                }
541
+                checkQuery = checkQuery.invalidate();
542
+                var q = "INSERT INTO files (id, name, type, note, file) VALUES(default, :name, 'profile', NULL, :data) RETURNING id";
543
+                query = new QSqlQuery();
544
+                query.prepare(q);
545
+                query.bind(":name", timefield.text + " " + roasted.currentText);
546
+                query.bindFileData(":data", batch.tempData);
547
+                query.exec();
548
+                query.next();
549
+                var fileno = query.value(0);
550
+                var file = new QFile(batch.tempData);
551
+                file.remove();
552
+                var q2 = "INSERT INTO roasting_log (time, unroasted_id, unroasted_quantity, unroasted_total_quantity, roasted_id, roasted_quantity, transaction_type, annotation, machine, duration, approval, humidity, barometric, indoor_air, outdoor_air, files) VALUES(:time, ";
553
+                q2 = q2 + table.columnArray(0, 32);
554
+                q2 = q2 + ", ";
555
+                for(var i = 0; table.data(i, 1, 0).value != ""; i++)
556
+                {
557
+                    table.setData(i, 1, convertToPounds(parseFloat(table.data(i, 1, 0)), unitBox.currentText) ,32)
558
+                }
559
+                q2 = q2 + table.columnArray(1, 32);
560
+                q2 = q2 + ", ";
561
+                q2 = q2 + convertToPounds(parseFloat(green.text), unitBox.currentText);
562
+                q2 = q2 + ", ";
563
+                q2 = q2 + roasted.currentData();
564
+                q2 = q2 + ", ";
565
+                q2 = q2 + convertToPounds(parseFloat(roastwt.text), unitBox.currentText);
566
+                q2 = q2 + ", 'ROAST', :annotation, ";
567
+                q2 = q2 + selectedRoasterID;
568
+                q2 = q2 + ", :duration, :approval, NULL, NULL, NULL, NULL, '{";
569
+                q2 = q2 + fileno;
570
+                q2 = q2 + "}')";
571
+                query2 = new QSqlQuery();
572
+                query2.prepare(q2);
573
+                query2.bind(":time", timefield.text);
574
+                query2.bind(":annotation", notes.plainText);
575
+                query2.bind(":duration", duration.text);
576
+                query2.bind(":approval", approval.checked);
577
+                query2.exec();
369 578
                 query2 = query2.invalidate();
370
-				if(target.checked) {
371
-					var q3 = "INSERT INTO item_files (time, item, files) VALUES(:time, :item, '{";
372
-					q3 = q3 + fileno;
373
-					q3 = q3 + "}')";
374
-					query.prepare(q3);
375
-					query.bind(":time", timefield.text);
376
-					query.bind(":item", roasted.currentData());
377
-					query.exec();
378
-				}
579
+                if(target.checked) {
580
+                    var q3 = "INSERT INTO item_files (time, item, files) VALUES(:time, :item, '{";
581
+                    q3 = q3 + fileno;
582
+                    q3 = q3 + "}')";
583
+                    query.prepare(q3);
584
+                    query.bind(":time", timefield.text);
585
+                    query.bind(":item", roasted.currentData());
586
+                    query.exec();
587
+                }
379 588
                 query = query.invalidate();
380
-				batch.windowModified = false;
381
-				batch.close();
382
-			});
589
+                batch.windowModified = false;
590
+                batch.close();
591
+            }
383 592
         ]]>
384 593
     </program>
385 594
 </window>

+ 42
- 9
config/Windows/newsamplebatch.xml View File

@@ -98,7 +98,7 @@
98 98
 					var scale = navigationwindow.loggingWindow.scales[i];
99 99
 					var label = new DragLabel();
100 100
 					var weighButton = new QPushButton();
101
-					weighButton.text = "Weigh";
101
+					weighButton.text = TTR("sampleRoastingBatch", "Weigh");
102 102
 					weighButton.clicked.connect(scale.weigh);
103 103
 					label.updateMeasurement = function(m, u) {
104 104
 						switch(GunitBox.currentIndex) {
@@ -127,7 +127,7 @@
127 127
 			}
128 128
 			var submit = findChildObject(this, 'submit');
129 129
 			submit.setEnabled(false);
130
-			this.windowTitle = "Typica - New Sample Roasting Batch";
130
+			this.windowTitle = TTR("sampleRoastingBatch", "Typica - New Sample Roasting Batch");
131 131
 			var newMenu = findChildObject(this, 'new');
132 132
 			newMenu.triggered.connect(function() {
133 133
 				createWindow("sampleRoastingBatch");
@@ -179,7 +179,27 @@
179 179
 			stop.clicked.connect(function() {
180 180
 				submit.setEnabled(true);
181 181
 			});
182
-			roastButton.clicked.connect(function() {
182
+                        var validateCapacity = function() {
183
+                            if(checkCapacity == "true") {
184
+                                if(convertToPounds(parseFloat(green.text), GunitBox.currentText) > poundsCapacity) {
185
+                                    return false;
186
+                                }
187
+                            }
188
+                            return true;
189
+                        }
190
+                        roastButton.clicked.connect(function() {
191
+                            var proceed = false;
192
+                            if(validateCapacity()) {
193
+                                proceed = true;
194
+                            } else {
195
+                                proceed = displayWarning(TTR("sampleRoastingBatch", "Suspicious Input"),
196
+                                TTR("sampleRoastingBatch", "Entered green coffee weight exceeds maximum batch size. Continue?"));
197
+                            }
198
+                            if(proceed) {
199
+                                doRoast();
200
+                            }
201
+                        });
202
+                        var doRoast = function() {
183 203
 				var lc = 1;
184 204
 				currentBatchInfo = batch;
185 205
 				query = new QSqlQuery();
@@ -232,17 +252,17 @@
232 252
 						input.input();
233 253
 						graph.updatesEnabled = true;
234 254
 						log.updatesEnabled = true;
235
-						log.newAnnotation("End", 1, lc);
255
+						log.newAnnotation(TTR("sampleRoastingBatch", "End"), 1, lc);
236 256
 					}
237 257
 				}
238 258
 				query = query.invalidate();
239
-				var t = "Typica - Sample Roasting: [*]" + name.text;
259
+				var t = TTR("sampleRoastingBatch", "Typica - Sample Roasting: [*]") + name.text;
240 260
 				if(profileName.currentText != '')
241 261
 				{
242 262
 					t = t + ", " + profileName.currentText;
243 263
 				}
244 264
 				navigationwindow.loggingWindow.windowTitle = t;
245
-			});
265
+			};
246 266
 			var notes = findChildObject(this, 'annotation');
247 267
 			var machine = findChildObject(this, 'machine');
248 268
 			var duration = findChildObject(this, 'duration');
@@ -250,7 +270,19 @@
250 270
 			var vendor = findChildObject(this, 'vendor');
251 271
 			var attributes = findChildObject(this, 'attributes');
252 272
 			var target = findChildObject(this, 'target');
253
-			submit.clicked.connect(function() {
273
+                        submit.clicked.connect(function() {
274
+                            var proceed = false;
275
+                            if(validateCapacity()) {
276
+                                proceed = true;
277
+                            } else {
278
+                                proceed = displayWarning(TTR("sampleRoastingBatch", "Suspicious Input"),
279
+                                TTR("sampleRoastingBatch", "Entered green coffee weight exceeds maximum batch size. Continue?"));
280
+                            }
281
+                            if(proceed) {
282
+                                doLoadProfile();
283
+                            }
284
+                        });
285
+                        var doSubmit = function() {
254 286
 				query = new QSqlQuery();
255 287
 				query.prepare("INSERT INTO files (id, name, type, note, file) VALUES(DEFAULT, :name, 'profile', NULL, :data) RETURNING id");
256 288
 				query.bind(":name", timefield.text + " " + name.text + " " + profileName.currentText);
@@ -314,9 +346,10 @@
314 346
 				query.exec();
315 347
 				query.next();
316 348
 				var roastedId = query.value(0);
317
-				query.prepare("INSERT INTO roasting_log (time, unroasted_id, unroasted_quantity, unroasted_total_quantity, roasted_id, roasted_quantity, transaction_type, annotation, machine, duration, approval, humidity, barometric, indoor_air, outdoor_air, files) VALUES(:time, :unroastedids, NULL, :green, :roastedid, :roasted, 'SAMPLEROAST', :note, :machine, :duration, TRUE, NULL, NULL, NULL, NULL, :files)");
349
+				query.prepare("INSERT INTO roasting_log (time, unroasted_id, unroasted_quantity, unroasted_total_quantity, roasted_id, roasted_quantity, transaction_type, annotation, machine, duration, approval, humidity, barometric, indoor_air, outdoor_air, files) VALUES(:time, :unroastedids, :greens, :green, :roastedid, :roasted, 'SAMPLEROAST', :note, :machine, :duration, TRUE, NULL, NULL, NULL, NULL, :files)");
318 350
 				query.bind(":time", timefield.text);
319 351
 				query.bind(":unroastedids", "{" + greenId + "}");
352
+                                query.bind(":greens", "{" + convertToPounds(parseFloat(green.text), GunitBox.currentText) + "}");
320 353
 				query.bind(":green", convertToPounds(parseFloat(green.text), GunitBox.currentText));
321 354
 				query.bind(":roastedid", Number(roastedId));
322 355
 				query.bind(":roasted", convertToPounds(parseFloat(roasted.text), GunitBox.currentText));
@@ -327,7 +360,7 @@
327 360
 				query.exec();
328 361
 				query = query.invalidate();
329 362
 				batch.close();
330
-			});
363
+			}
331 364
 		]]>
332 365
 	</program>
333 366
 </window>

+ 8
- 8
config/Windows/offline.xml View File

@@ -11,10 +11,10 @@
11 11
         </splitter>
12 12
     </layout>
13 13
 	<menu name="File">
14
-		<item id="save" shortcut="Ctrl+S">Save</item>
15
-		<item id="print" shortcut="Ctrl+P">Print</item>
16
-		<item id="export">Export CSV</item>
17
-		<item id="svgexport">Export XHTML+SVG</item>
14
+		<item id="save" shortcut="Ctrl+S">Save...</item>
15
+		<item id="print" shortcut="Ctrl+P">Print...</item>
16
+		<item id="export">Export CSV...</item>
17
+		<item id="svgexport">Export XHTML+SVG...</item>
18 18
 		<item id="quit" shortcut="Ctrl+Q">Quit</item>
19 19
 	</menu>
20 20
 	<menu name="Log">
@@ -54,7 +54,7 @@
54 54
 			};
55 55
 			var saveMenu = findChildObject(this, 'save');
56 56
 			saveMenu.triggered.connect(function() {
57
-				var filename = QFileDialog.getSaveFileName(window, "Save Log As…", QSettings.value("script/lastDir", "") + "/");
57
+				var filename = QFileDialog.getSaveFileName(window, TTR("offline", "Save Log As..."), QSettings.value("script/lastDir", "") + "/");
58 58
 				if(filename != "") {
59 59
 					var file = new QFile(filename);
60 60
 					setLogOutputColumns();
@@ -65,12 +65,12 @@
65 65
 			var printMenu = findChildObject(this, 'print');
66 66
 			printMenu.triggered.connect(function() {
67 67
 				var exportWindow = createWindow("print");
68
-				exportWindow.windowTitle = "Typica - Print";
68
+				exportWindow.windowTitle = TTR("offline", "Typica - Print");
69 69
 				exportWindow.log = log;
70 70
 			});
71 71
 			var exportMenu = findChildObject(this, 'export');
72 72
 			exportMenu.triggered.connect(function() {
73
-				var filename = QFileDialog.getSaveFileName(window, "Export CSV As…", QSettings.value("script/lastDir", "") + "/");
73
+				var filename = QFileDialog.getSaveFileName(window, TTR("offline", "Export CSV As..."), QSettings.value("script/lastDir", "") + "/");
74 74
 				if(filename != "") {
75 75
 					var file = new QFile(filename);
76 76
 					setLogOutputColumns();
@@ -81,7 +81,7 @@
81 81
 			var svgExportMenu = findChildObject(this, 'svgexport');
82 82
 			svgExportMenu.triggered.connect(function() {
83 83
 				var exportWindow = createWindow("exportWindow");
84
-				exportWindow.windowTitle = "Typica - Export XHTML+SVG";
84
+				exportWindow.windowTitle = TTR("offline", "Typica - Export XHTML+SVG");
85 85
 				exportWindow.log = log;
86 86
 			});
87 87
 			var quitMenu = findChildObject(this, 'quit');

+ 6
- 6
config/Windows/print.xml View File

@@ -279,18 +279,18 @@
279 279
                 output.writeAttribute("transform", "rotate(-90 40,150)");
280 280
                 if(log.displayUnits() == 10143)
281 281
                 {
282
-                    output.writeCDATA("Temperature (°C)");
282
+                    output.writeCDATA(TTR("print", "Temperature (°C)"));
283 283
                 }
284 284
                 else
285 285
                 {
286
-                    output.writeCDATA("Temperature (°F)");
286
+                    output.writeCDATA(TTR("print", "Temperature (°F)"));
287 287
                 }
288 288
                 output.writeEndElement();
289 289
                 output.writeStartElement("text");
290 290
                 output.writeAttribute("x", "1.9in");
291 291
                 output.writeAttribute("y", "3.3in");
292 292
                 output.writeAttribute("font-size", "12");
293
-                output.writeCDATA("Time (minutes)");
293
+                output.writeCDATA(TTR("print", "Time (minutes)"));
294 294
                 output.writeEndElement();
295 295
                 output.writeStartElement("line");
296 296
                 output.writeAttribute("x1", "0.4in");
@@ -675,7 +675,7 @@
675 675
                 if(lossField.text != "")
676 676
                 {
677 677
                     output.writeStartElement("p");
678
-                    output.writeCDATA("Weight loss: ");
678
+                    output.writeCDATA(TTR("print", "Weight loss: "));
679 679
                     output.writeCDATA(lossField.text);
680 680
                     if(tolField.text != "")
681 681
                     {
@@ -687,10 +687,10 @@
687 687
                     output.writeStartElement("table");
688 688
                     output.writeStartElement("tr");
689 689
                     output.writeStartElement("th");
690
-                    output.writeCDATA("Roasted");
690
+                    output.writeCDATA(TTR("print", "Roasted"));
691 691
                     output.writeEndElement();
692 692
                     output.writeStartElement("th");
693
-                    output.writeCDATA("Green");
693
+                    output.writeCDATA(TTR("print", "Green"));
694 694
                     output.writeEndElement();
695 695
                     output.writeEndElement();
696 696
                     var green;

+ 723
- 705
config/Windows/productionroaster.xml
File diff suppressed because it is too large
View File


+ 4
- 4
config/Windows/roastmanager.xml View File

@@ -24,7 +24,7 @@
24 24
     </layout>
25 25
     <program>
26 26
         <![CDATA[
27
-            this.displayStatus("Ready.");
27
+            this.displayStatus(TTR("newroasted", "Ready."));
28 28
             var itemname = findChildObject(this, 'name');
29 29
             var newItemButton = findChildObject(this, 'ok');
30 30
             var window = this;
@@ -43,7 +43,7 @@
43 43
                 drop2.clear();
44 44
                 drop1.addSqlOptions("SELECT id, name FROM items WHERE id IN (SELECT item FROM current_items) ORDER BY name");
45 45
                 drop2.addSqlOptions("SELECT id, name FROM items WHERE category = 'Coffee: Roasted' AND id NOT IN (SELECT item FROM current_items) ORDER BY name");
46
-                window.displayStatus("Item removed.");
46
+                window.displayStatus(TTR("newroasted", "Item removed."));
47 47
             });
48 48
             restoreButton.clicked.connect(function() {
49 49
                 var q = "INSERT INTO current_items (item) VALUES (:id)";
@@ -56,7 +56,7 @@
56 56
                 drop2.clear();
57 57
                 drop1.addSqlOptions("SELECT id, name FROM items WHERE id IN (SELECT item FROM current_items) ORDER BY name");
58 58
                 drop2.addSqlOptions("SELECT id, name FROM items WHERE category = 'Coffee: Roasted' AND id NOT IN (SELECT item FROM current_items) ORDER BY name");
59
-                window.displayStatus("Item restored.");
59
+                window.displayStatus(TTR("newroasted", "Item restored."));
60 60
             });
61 61
             newItemButton.clicked.connect(function() {
62 62
                 var q = "INSERT INTO items (id, name, reference, unit, quantity, category) VALUES (default, :name, NULL, 'lb', 0, 'Coffee: Roasted') RETURNING id";
@@ -76,7 +76,7 @@
76 76
                 drop2.clear();
77 77
                 drop1.addSqlOptions("SELECT id, name FROM items WHERE id IN (SELECT item FROM current_items) ORDER BY name");
78 78
                 drop2.addSqlOptions("SELECT id, name FROM items WHERE category = 'Coffee: Roasted' AND id NOT IN (SELECT item FROM current_items) ORDER BY name");
79
-                window.displayStatus("Item added.");
79
+                window.displayStatus(TTR("newroasted", "Item added."));
80 80
             });
81 81
         ]]>
82 82
     </program>

+ 92
- 0
config/Windows/roastspec.xml View File

@@ -0,0 +1,92 @@
1
+<window id="roastspec">
2
+    <layout type="horizontal">
3
+        <layout type="vertical">
4
+            <layout type="horizontal">
5
+                <label>Coffee:</label>
6
+                <sqldrop data="0" display="1" showdata="false" id="currentitems">
7
+                    <query>SELECT id, name FROM items WHERE id IN (SELECT item FROM current_items) ORDER BY name</query>
8
+                </sqldrop>
9
+            </layout>
10
+            <layout type="horizontal">
11
+                <label>Expected % weight loss:</label>
12
+                <line validator="numeric" id="expectedloss" />
13
+            </layout>
14
+            <layout type="horizontal">
15
+                <label>Tolerance</label>
16
+                <line validator="numeric" id="tolerance" />
17
+            </layout>
18
+            <label>Specification Notes:</label>
19
+            <textarea id="notes" />
20
+            <layout type="horizontal">
21
+                <stretch />
22
+                <button id="save" type="push" name="Save" />
23
+            </layout>
24
+        </layout>
25
+    </layout>
26
+    <program>
27
+        <![CDATA[
28
+            var window = this;
29
+            this.windowTitle = TTR("roastspec", "Typica - Edit Roasting Specification");
30
+            var selector = findChildObject(this, 'currentitems');
31
+            var expected = findChildObject(this, 'expectedloss');
32
+            var tolerance = findChildObject(this, 'tolerance');
33
+            var notes = findChildObject(this, 'notes');
34
+            var savebutton = findChildObject(this, 'save');
35
+            var updateDisplay = function() {
36
+                var query = new QSqlQuery();
37
+                query.prepare("SELECT loss, tolerance, notes FROM roasting_specification WHERE item = :id1 AND time = (SELECT max(time) FROM roasting_specification WHERE item = :id2)");
38
+                query.bind(":id1", selector.currentData());
39
+                query.bind(":id2", selector.currentData());
40
+                query.exec();
41
+                if(query.next()) {
42
+                    if(query.value(0).length > 0) {
43
+                        expected.text = Number(query.value(0)) * 100;
44
+                    } else {
45
+                        expected.text = "";
46
+                    }
47
+                    if(query.value(1).length > 0) {
48
+                        tolerance.text = Number(query.value(1)) * 100;
49
+                    } else {
50
+                        tolerance.text = "";
51
+                    }
52
+                    notes.plainText = query.value(2);
53
+                } else {
54
+                    expected.text = "";
55
+                    tolerance.text = "";
56
+                    notes.plainText = "";
57
+                }                
58
+                query = query.invalidate();
59
+            };
60
+            updateDisplay();
61
+            selector['currentIndexChanged(int)'].connect(function() {
62
+                updateDisplay();
63
+            });
64
+            savebutton.clicked.connect(function() {
65
+                var query = new QSqlQuery();
66
+                var columnspec = "time, item, ";
67
+                var valuespec = "'now', :id, ";
68
+                if(expected.text.length > 0) {
69
+                    columnspec += "loss, ";
70
+                    valuespec += ":loss, ";
71
+                }
72
+                if(tolerance.text.length > 0) {
73
+                    columnspec += "tolerance, ";
74
+                    valuespec += ":tolerance, ";
75
+                }
76
+                columnspec += "notes";
77
+                valuespec += ":notes";
78
+                query.prepare("INSERT INTO roasting_specification (" + columnspec + ") VALUES (" + valuespec + ")");
79
+                query.bind(":id", selector.currentData());
80
+                if(expected.text.length > 0) {
81
+                    query.bind(":loss", Number(expected.text) / 100);
82
+                }
83
+                if(tolerance.text.length > 0) {
84
+                    query.bind(":tolerance", Number(tolerance.text) / 100);
85
+                }
86
+                query.bind(":notes", notes.plainText);
87
+                query.exec();
88
+                window.close();
89
+            });
90
+        ]]>
91
+    </program>
92
+</window>

+ 1
- 1
config/Windows/setsampleparameters.xml View File

@@ -13,7 +13,7 @@
13 13
         print("Program loaded");
14 14
         var window = this;
15 15
         var instructions = findChildObject(this, 'instructions');
16
-        instructions.plainText = "%1 will be replaced with a sample number.";
16
+        instructions.plainText = TTR("sampleParameters", "%1 will be replaced with a sample number.");
17 17
         instructions.readOnly = true;
18 18
         var reset = findChildObject(this, 'reset');
19 19
         reset.clicked.connect(function() {

+ 24
- 24
config/config.xml View File

@@ -8,7 +8,6 @@
8 8
     <include src="Windows/navigation.xml" />
9 9
     <include src="Windows/success.xml" />
10 10
     <include src="Windows/roastmanager.xml" />
11
-    <include src="Windows/history.xml" />
12 11
     <include src="Windows/greeninventory.xml" />
13 12
     <include src="Windows/newroaster.xml" />
14 13
     <include src="Windows/batchdetailsnew.xml" />
@@ -23,27 +22,28 @@
23 22
     <include src="Windows/cuppingsessionsummary.xml" />
24 23
     <include src="Windows/cuppingsummary.xml" />
25 24
     <include src="Windows/purchase.xml" />
26
-	<include src="Windows/invoicelist.xml" />
27
-	<include src="Windows/invoiceinfo.xml" />
28
-	<include src="Windows/editinvoice.xml" />
29
-	<include src="Windows/editinvoiceitem.xml" />
30
-	<include src="Windows/editfee.xml" />
31
-	<include src="Windows/optime.xml" />
32
-	<include src="Windows/greensales.xml" />
33
-	<include src="Windows/profilehistory.xml" />
34
-	<include src="Windows/editbatchdetails.xml" />
35
-	<include src="Windows/newsamplebatch.xml" />
36
-	<program>
37
-		<![CDATA[
38
-		Windows = new Object();
39
-		var loggingWindow;
40
-		var currentBatchInfo;
41
-		var navwindow = createWindow("navwindow");
42
-		var aadapt;
43
-		var badapt;
44
-		var azero;
45
-		var bzero;
46
-		navwindow.windowTitle = "Typica - Choose Your Path";
47
-		]]>
48
-	</program>
25
+    <include src="Windows/invoiceinfo.xml" />
26
+    <include src="Windows/editinvoice.xml" />
27
+    <include src="Windows/editinvoiceitem.xml" />
28
+    <include src="Windows/editfee.xml" />
29
+    <include src="Windows/optime.xml" />
30
+    <include src="Windows/greensales.xml" />
31
+    <include src="Windows/profilehistory.xml" />
32
+    <include src="Windows/editbatchdetails.xml" />
33
+    <include src="Windows/newsamplebatch.xml" />
34
+    <include src="Windows/editreminder.xml" />
35
+    <include src="Windows/roastspec.xml" />
36
+    <program>
37
+        <![CDATA[
38
+            Windows = new Object();
39
+            var loggingWindow;
40
+            var currentBatchInfo;
41
+            var navwindow = createWindow("navwindow");
42
+            var aadapt;
43
+            var badapt;
44
+            var azero;
45
+            var bzero;
46
+            navwindow.windowTitle = TTR("config", "Typica - Choose Your Path");
47
+        ]]>
48
+    </program>
49 49
 </application>

+ 0
- 1
docs/documentation.html View File

@@ -16,7 +16,6 @@
16 16
 					<a class="tab" href="downloads.html" >Downloads</a>
17 17
 					<a class="tab active" >Documentation</a>
18 18
 					<a class="tab" href="screenshots.html" >Screenshots and Videos</a>
19
-					<a class="tab" href="involvement.html" >Get Involved</a>
20 19
 					<a href="http://appliedcoffeetechnology.tumblr.com/tagged/Typica" class="tab">Blog</a>
21 20
 				</div>
22 21
 			</div>

+ 4
- 8
docs/documentation/part2.html View File

@@ -16,7 +16,6 @@
16 16
 					<a class="tab" href="../downloads.html" >Downloads</a>
17 17
 					<a class="tab active" href="../documentation.html" >Documentation</a>
18 18
 					<a class="tab" href="../screenshots.html" >Screenshots and Videos</a>
19
-					<a class="tab" href="../involvement.html" >Get Involved</a>
20 19
 					<a href="http://appliedcoffeetechnology.tumblr.com/tagged/Typica" class="tab">Blog</a>
21 20
 				</div>
22 21
 			</div>
@@ -27,24 +26,21 @@
27 26
 				<ul>
28 27
 					<li><a href="part2/application.html">Opening the Application</a></li>
29 28
 					<ul>
30
-						<li><a href="part2/database-connection.html">Database Connection Settings</a></li>
31
-						<li><a href="part2/open-configuration.html">Open Configuration File</a></li>
29
+						<li><a href="windowreference/databaseconnection.html">Connecting to a database</a></li>
30
+						<li><a href="windowreference/openconfigurationfile">Open Configuration File</a></li>
32 31
 						<ul>
33 32
 							<li><a href="part2/argument-c.html">Eliminating the Configuration Prompt</a></li>
34 33
 						</ul>
35
-						<li><a>Choose Your Path</a></li>
34
+						<li><a href="windowreference/navigation.html">Main navigation window</a></li>
36 35
 					</ul>
37 36
 					<li><a>Configure Roasters</a></li>
38
-					<li><a>Roast Coffee</a></li>
37
+					<li><a href="windowreference/logging.html">The Logging View</a></li>
39 38
 					<li><a>Purchase Green Coffee</a></li>
40 39
 					<li><a>Manage Roasted Coffee Items</a></li>
41 40
 					<li><a>Update Inventory</a></li>
42
-					<li><a>Batch Log</a></li>
43 41
 					<li><a>Cupping</a></li>
44 42
 					<li><a>Import Target Roast Profiles</a></li>
45
-					<li><a>Invoice List</a></li>
46 43
 					<li><a>Enter Green Coffee Sales</a></li>
47
-					<li><a>Reports</a></li>
48 44
 				</ul>
49 45
 			</div>
50 46
 		</div>

+ 79
- 0
docs/documentation/windowreference/databaseconnection.html View File

@@ -0,0 +1,79 @@
1
+<html>
2
+    <head>
3
+        <title>Typica - Connecting to a database</title>
4
+        <link rel="stylesheet" type="text/css" href="../../style.css">
5
+    </head>
6
+    <body>
7
+        <div id="page">
8
+            <div id="topmatter">
9
+                <div id="topbanner">
10
+                    <img src="../../logo96.png" height="96px" width="96px" alt="Typica logo" />
11
+                    <h1>Typica</h1>
12
+                    <h2>Data for Coffee Roasters</h2>
13
+                </div>
14
+                <div id="menu">
15
+                    <a class="tab" href="../../index.html">Project Home</a>
16
+                    <a class="tab" href="../../downloads.html" >Downloads</a>
17
+                    <a class="tab active" >Documentation</a>
18
+                    <a class="tab" href="../../screenshots.html" >Screenshots and Videos</a>
19
+                    <a href="http://appliedcoffeetechnology.tumblr.com/tagged/Typica" class="tab">Blog</a>
20
+                </div>
21
+            </div>
22
+            <div id="maintext">
23
+                <h1>Connecting to a database</h1>
24
+                <p>When opening Typica, if you have not previously connected to
25
+                a database, you will be prompted for information needed to
26
+                connect to that database.</p>
27
+
28
+                <img src="databaseconnection.png"/>
29
+                
30
+                <p>If you do not want to connect to a database, you can click
31
+                the Cancel button, but doing so means that most of the features
32
+                of Typica will not work. Clicking the Connect button will cause
33
+                Typica to attempt to connect to the database with the
34
+                information provided in the fields. An error message will be
35
+                presented if that connection attempt fails.</p>
36
+                
37
+                <p>After either successfully connecting to a database or
38
+                cancelling, you will be prompted to
39
+                <a href="openconfigurationfile.html">open a configuration
40
+                file.</a></p>
41
+                
42
+                <h2>Field descriptions</h2>
43
+                
44
+                <h3>Database driver</h3>
45
+                <p>Currently the only database supported is PostgreSQL.</p>
46
+                
47
+                <h3>Host name</h3>
48
+                <p>This identifies the computer the database is installed on.
49
+                If the database is on the same computer as Typica, this can be
50
+                <tt>localhost</tt> otherwise it will most likely be an IP
51
+                address for another computer on your network.</p>
52
+                
53
+                <h3>Port number</h3>
54
+                <p>PostgreSQL normally communicates on port 5432 but if you
55
+                have PostgreSQL configured to communicate over a different port
56
+                this field should be changed to match.</p>
57
+                
58
+                <h3>Database name</h3>
59
+                <p>During installation, the PostgreSQL installer creates a
60
+                database called <tt>postgres</tt>. If you've created a new
61
+                database for use with Typica, its name should be used.</p>
62
+                
63
+                <h3>User name</h3>
64
+                <p>During installation, the PostgreSQL installer creates a
65
+                database user called <tt>postgres</tt>. If you don't want to
66
+                create separate users, this account can be used. Typica keeps
67
+                track of which database user performed many data entry tasks so
68
+                it is recommended to create new users for everybody who will be
69
+                using Typica.</p>
70
+                
71
+                <h3>Password</h3>
72
+                <p>This is the password previously set for the database user.
73
+                If you are using the <tt>postgres</tt> user, this was set
74
+                during PostgreSQL installation.</p>
75
+
76
+            </div>
77
+        </div>
78
+    </body>
79
+</html>

BIN
docs/documentation/windowreference/databaseconnection.png View File


BIN
docs/documentation/windowreference/databasemenu.png View File


BIN
docs/documentation/windowreference/inventoryreports.png View File


+ 202
- 0
docs/documentation/windowreference/logging.html View File

@@ -0,0 +1,202 @@
1
+<html>
2
+    <head>
3
+        <title>Typica - The Logging View</title>
4
+        <link rel="stylesheet" type="text/css" href="../../style.css">
5
+    </head>
6
+    <body>
7
+        <div id="page">
8
+            <div id="topmatter">
9
+                <div id="topbanner">
10
+                    <img src="../../logo96.png" height="96px" width="96px" alt="Typica logo" />
11
+                    <h1>Typica</h1>
12
+                    <h2>Data for Coffee Roasters</h2>
13
+                </div>
14
+                <div id="menu">
15
+                    <a class="tab" href="../../index.html">Project Home</a>
16
+                    <a class="tab" href="../../downloads.html" >Downloads</a>
17
+                    <a class="tab active" >Documentation</a>
18
+                    <a class="tab" href="../../screenshots.html" >Screenshots and Videos</a>
19
+                    <a href="http://appliedcoffeetechnology.tumblr.com/tagged/Typica" class="tab">Blog</a>
20
+                </div>
21
+            </div>
22
+            <div id="maintext">
23
+                <h1>The Logging View</h1>
24
+                <p>The appearance of the logging view depends on how the selected
25
+                roaster was configured, but there are a few main areas where
26
+                different interface items are located.</p>
27
+
28
+                <img src="logging.png"/>
29
+                
30
+                <p>The window title will change to reflect a loaded target roast
31
+                profile and can provide the name of the coffee being roasted.</p>
32
+
33
+                <p>The menu bar provides access to a number of operations such
34
+                as saving or loading data on disk, entering new batch information,
35
+                clearing recorded data, changing how data is displayed, and more.
36
+                Note that on a Mac the menu bar will be at the top of the screen
37
+                and not within the window as is normal on that platform.</p>
38
+
39
+                <p>Continuing down the window is the indicator panel. This is
40
+                where current temperature indicators, rate of change information,
41
+                and timers will be displayed. The number, type, and order of
42
+                indicators depends on the roaster configuration. There are
43
+                splitter handles between each indicator which can be used to
44
+                control the width of each indicator. The last indicator should
45
+                always be the batch timer. If you don't see this, there should
46
+                be a splitter handle to the right of the last visible indicator
47
+                which can be dragged left to reveal additional indicators. A
48
+                splitter handle at the bottom can be used to control the height
49
+                of all indicators.</p>
50
+
51
+                <p>Next is a row of buttons. At minimum this will include buttons
52
+                for starting and stopping the batch, but a number of other
53
+                controls for inserting notes in the log can also be configured
54
+                to appear here. Under these buttons is another splitter handle
55
+                that can be used to control the height of to table and graph
56
+                below.</p>
57
+
58
+                <p>Below this and on the left is a table view showing times,
59
+                temperatures, and notes for both a target profile if one is
60
+                loaded and the current batch if one is being recorded.</p>
61
+
62
+                <p>To the right of the table view is a graph showing both
63
+                loaded target profiles, if any, and current batch data.</p>
64
+
65
+                <h2>Buttons</h2>
66
+
67
+                <h3>Start Batch</h3>
68
+                <p>This button starts recording roasting data. If the New Batch
69
+                or New Sample Batch windows were used, the roasting data can be
70
+                associated with a batch in the database. Clicking this will
71
+                also start the batch timer from 00:00, start any other timers
72
+                that have been configured to start from the start of the batch,
73
+                and create any annotations that have been configured for entry
74
+                at the start of the batch if any.</p>
75
+
76
+                <h3>Stop Batch</h3>
77
+                <p>This button stops the batch timer, stops recording roasting
78
+                data, and if the New Batch or New Sample Batch windows were
79
+                used, the window with data associated with the finished batch
80
+                will be raised.</p>
81
+
82
+                <h3>Additional Buttons</h3>
83
+                <p>Additional buttons and controls will appear if these have
84
+                been configured.</p>
85
+
86
+                <h2>The File Menu</h2>
87
+
88
+                <img src="loggingfile.png" />
89
+
90
+                <h3>Open...</h3>
91
+                <p>This can be used to load roasting data previously saved to
92
+                disk. This is mainly useful when receiving roasting data that
93
+                has been shared from another roaster. In normal use it is much
94
+                better to use information saved to the database instead of
95
+                managing files manually.</p>
96
+
97
+                <h3>Save...</h3>
98
+                <p>This can be used to save roasting data to disk. This is
99
+                mainly useful to share roasting data with another person using
100
+                Typica. For other uses it is much better to use the New Batch
101
+                and New Sample Batch windows to have roasting data saved to the
102
+                database.</p>
103
+
104
+                <h3>Print...</h3>
105
+                <p>This can be used to print roasting data. The print window
106
+                will appear, allowing the information printed to be
107
+                customized.</p>
108
+
109
+                <h3>Export CSV...</h3>
110
+                <p>This can be used to export roasting data as a CSV file which
111
+                can be opened in all popular spreadsheet applications or used
112
+                with a wide variety of other tools. If this is something that
113
+                you frequently require, it may be a good idea to reach out to
114
+                the author with your use case and see if there is a way to do
115
+                what you want within Typica or if Typica should be extended to
116
+                support your use case.</p>
117
+
118
+                <h3>Export XHTML+SVG</h3>
119
+                <p>This can be used to produce the same information that can be
120
+                printed, but produces that as an XHTML+SVG document.</p>
121
+
122
+                <h3>Quit</h3>
123
+                <p>On some platforms this might be called Exit and on the Mac
124
+                this will be moved under the Typica menu. It is used to quit
125
+                Typica.</p>
126
+                
127
+                <h2>The Batch Menu</h2>
128
+                
129
+                <img src="loggingbatch.png" />
130
+                
131
+                <h3>New Batch...</h3>
132
+                <p>This is the preferred way to enter the details of a
133
+                production roast such that recorded roasting data can be
134
+                associated with a batch and green coffee inventory can be
135
+                adjusted. Selecting this will open the New Batch window.</p>
136
+                
137
+                <h3>New Sample Batch...</h3>
138
+                <p>When roasting a green coffee not in inventory such as
139
+                pre-purchase samples, you can use this menu item to open the
140
+                New Sample Batch window and associate the roasting data with
141
+                additional information about the coffee.</p>
142
+                
143
+                <h3>Load Additional Profiles...</h3>
144
+                <p>If you do not have a target roast profile for the roasted
145
+                coffee item you are producing but have a batch that you would
146
+                like to have available for reference previously saved in the
147
+                database, you can use this menu item to load that information
148
+                as a target roast profile.</p>
149
+                
150
+                <h2>The Log Menu</h2>
151
+                
152
+                <img src="logginglog.png" />
153
+                
154
+                <h3>Display Celsius</h3>
155
+                <p>This menu item can be used to change displayed temperature
156
+                measurements to Celsius.</p>
157
+                
158
+                <h3>Display Fahrenheit</h3>
159
+                <p>This menu item can be used to change displayed temperature
160
+                measurements to Fahrenheit.</p>
161
+                
162
+                <h3>New Sample Parameters</h3>
163
+                <p>If a Counting Button has been configured, this menu item
164
+                will be available to change the annotation text or reset the
165
+                number used in annotations when that button is activated.</p>
166
+                
167
+                <h3>Clear Log</h3>
168
+                <p>This menu item removes all information currently visible in
169
+                the table and graph views.</p>
170
+                
171
+                <h3>Millisecond View</h3>
172
+                <h3>1 Second View</h3>
173
+                <h3>5 Second View</h3>
174
+                <h3>10 Second View</h3>
175
+                <h3>15 Second View</h3>
176
+                <h3>30 Second View</h3>
177
+                <h3>1 Minute View</h3>
178
+                
179
+                <p>These items affect the table view. The millisecond view will
180
+                show every measurement in the table while the other views will
181
+                limit the entries shown to one per indicated interval with the
182
+                exception of times associated with an annotation which will
183
+                always be shown regardless of the selected view.</p>
184
+                
185
+                <h3>Manual Entry</h3>
186
+                <p>This can be used for entering roasting data collected when
187
+                not connected to a roaster. The utility of this feature is
188
+                somewhat limited and generally not worth using.</p>
189
+                
190
+                <h2> The Graph Menu</h2>
191
+                
192
+                <img src="logginggraph.png" />
193
+                
194
+                <h3>Reset Translation</h3>
195
+                <p>If the roaster in use has been configured to use roast
196
+                profile translation, this item can be used to reset any
197
+                currently applied translation transformation in the graph.</p>
198
+                
199
+            </div>
200
+        </div>
201
+    </body>
202
+</html>

BIN
docs/documentation/windowreference/logging.png View File


BIN
docs/documentation/windowreference/loggingbatch.png View File


BIN
docs/documentation/windowreference/loggingfile.png View File


BIN
docs/documentation/windowreference/logginggraph.png View File


BIN
docs/documentation/windowreference/logginglog.png View File


+ 156
- 0
docs/documentation/windowreference/navigation.html View File

@@ -0,0 +1,156 @@
1
+<html>
2
+    <head>
3
+        <title>Typica - Choose Your Path</title>
4
+        <link rel="stylesheet" type="text/css" href="../../style.css">
5
+    </head>
6
+    <body>
7
+        <div id="page">
8
+            <div id="topmatter">
9
+                <div id="topbanner">
10
+                    <img src="../../logo96.png" height="96px" width="96px" alt="Typica logo" />
11
+                    <h1>Typica</h1>
12
+                    <h2>Data for Coffee Roasters</h2>
13
+                </div>
14
+                <div id="menu">
15
+                    <a class="tab" href="../../index.html">Project Home</a>
16
+                    <a class="tab" href="../../downloads.html" >Downloads</a>
17
+                    <a class="tab active" >Documentation</a>
18
+                    <a class="tab" href="../../screenshots.html" >Screenshots and Videos</a>
19
+                    <a href="http://appliedcoffeetechnology.tumblr.com/tagged/Typica" class="tab">Blog</a>
20
+                </div>
21
+            </div>
22
+            <div id="maintext">
23
+                <h1>Main navigation window</h1>
24
+                <p>Most of the functionality in Typica is available through the
25
+                main navigation window.</p>
26
+
27
+                <img src="navigation.png"/>
28
+                
29
+                <h2>Buttons</h2>
30
+                
31
+                <h3>Configure Roasters</h3>
32
+                
33
+                <p>Typica can communicate with different pieces of data
34
+                acquisition hardware which must be configured before use.
35
+                Various features related to recording roasting data can be
36
+                set in the configuration
37
+                window.
38
+                
39
+                <h3>Roast Coffee</h3>
40
+                
41
+                <p>Once at least one coffee roaster has been configured, it
42
+                will be available from the selector next to the Roast Coffee
43
+                button. When you want to roast coffee, select the machine
44
+                you'll be using and click the Roast Coffee button. This will
45
+                take you to <a href="logging.html">the logging view.</a></p>
46
+                
47
+                <h3>Purchase Green Coffee</h3>
48
+                
49
+                <p>Typica tracks green coffee inventory. This button allows
50
+                you to enter the details of green coffee purchases.</p>
51
+                
52
+                <h3>Manage Roasted Coffee Items</h3>
53
+                
54
+                <p>When entering the details of a new production batch, you
55
+                will want to select a roasted coffee item. This is connected
56
+                to the green coffee(s) used, a target roast profile and
57
+                roasting specification if set. This button allows you to create
58
+                new roasted coffee items, discontinue items so that they will
59
+                not show up in lists, and bring back old items.</p>
60
+                
61
+                <h3>Edit Roasting Specification</h3>
62
+                
63
+                <p>Expected percent weight loss and other information can be
64
+                presented when roasting coffee for use in determining if a
65
+                batch matches its product specification.</p>
66
+                
67
+                <h3>Update Inventory</h3>
68
+                
69
+                <p>Inventory and loss transactions can be used to adjust
70
+                green coffee inventory.</p>
71
+                
72
+                <h3>New Cupping Session</h3>
73
+                <h3>Join Cupping Session</h3>
74
+                <h3>Summarize Cupping Session</h3>
75
+                
76
+                <p>Typica includes a set of cupping features. Some people use
77
+                this, but the author does not recommend it. Something much
78
+                better will be available in the future.</p>
79
+                
80
+                <h3>View Target Roast Profiles</h3>
81
+                
82
+                <p>Any data ever saved as a target roast profile for roasted
83
+                coffee items can be seen here.</p>
84
+                
85
+                <h3>Import Target Roast Profiles</h3>
86
+                
87
+                <p>It is possible to save batch data to disk instead of or in
88
+                addition to saving that to the database. These files can be
89
+                shared with other people using Typica. If you want to set data
90
+                from one of these files as a target roast profile for a given
91
+                roasted coffee item, this is where you do that.
92
+                
93
+                <h3>Enter Green Coffee Sales</h3>
94
+                
95
+                <p>If you sell green coffee, the inventory can be adjusted to
96
+                reflect that here.</p>
97
+                
98
+                <h2>Reports Menu</h2>
99
+                
100
+                <img src="reportsmenu.png" />
101
+                
102
+                <h3>Production</h3>
103
+                
104
+                <p>Production reports present information related to coffee
105
+                roasting activity.</p>
106
+                
107
+                <img src="productionreports.png" />
108
+                
109
+                <h3>Average Use and Cost by Origin</h3>
110
+                <h3>Previous Year Production Comparison</h3>
111
+                <h3>Cost of Green Coffee for Roasted Coffee</h3>
112
+                <h3>Daily Production Report (Detailed)</h3>
113
+                <h3>Batch Log</h3>
114
+                <h3>Previous Year Production Comparison By Month</h3>
115
+                <h3>Production Summary</h3>
116
+                <h3>Reminders</h3>
117
+                <h3>Recent Average Coffee Production</h3>
118
+                
119
+                <h3>Purchase</h3>
120
+                
121
+                <p>Purchase reports present information related to green coffee
122
+                purchases.</p>
123
+                
124
+                <img src="purchasereports.png" />
125
+                
126
+                <h3>Coffee Purchase Previous Years Comparison</h3>
127
+                <h3>Invoices</h3>
128
+                
129
+                <h3>Sales</h3>
130
+                
131
+                <p>Sales reports present information related to sales.</p>
132
+                
133
+                <img src="salesreport.png" />
134
+                
135
+                <h3>Green Coffee Sales</h3>
136
+                
137
+                <h3>Inventory</h3>
138
+                
139
+                <p>Inventory reports present information on inventory.</p>
140
+                
141
+                <img src="inventoryreports.png" />
142
+                
143
+                <h3>Inventory Change Summary</h3>
144
+                <h3>Current Inventory and Availability Projection</h3>
145
+                <h3>Item Transactions</h3>
146
+                
147
+                <h2>Database Menu</h2>
148
+                <h3>Forget Connection Details</h3>
149
+                <p>This menu item can be used to force Typica to prompt for
150
+                database connection information the next time Typica is
151
+                opened.</p>
152
+                
153
+            </div>
154
+        </div>
155
+    </body>
156
+</html>

BIN
docs/documentation/windowreference/navigation.png View File


+ 38
- 0
docs/documentation/windowreference/openconfigurationfile.html View File

@@ -0,0 +1,38 @@
1
+<html>
2
+    <head>
3
+        <title>Typica - Open Configuration File</title>
4
+        <link rel="stylesheet" type="text/css" href="../../style.css">
5
+    </head>
6
+    <body>
7
+        <div id="page">
8
+            <div id="topmatter">
9
+                <div id="topbanner">
10
+                    <img src="../../logo96.png" height="96px" width="96px" alt="Typica logo" />
11
+                    <h1>Typica</h1>
12
+                    <h2>Data for Coffee Roasters</h2>
13
+                </div>
14
+                <div id="menu">
15
+                    <a class="tab" href="../../index.html">Project Home</a>
16
+                    <a class="tab" href="../../downloads.html" >Downloads</a>
17
+                    <a class="tab active" >Documentation</a>
18
+                    <a class="tab" href="../../screenshots.html" >Screenshots and Videos</a>
19
+                    <a href="http://appliedcoffeetechnology.tumblr.com/tagged/Typica" class="tab">Blog</a>
20
+                </div>
21
+            </div>
22
+            <div id="maintext">
23
+                <h1>Open Configuration File</h1>
24
+                <p>Most of Typica's appearance and functionality is controlled
25
+                by a set of configuration files. This documentation covers an
26
+                example configuration included with Typica. When prompted to
27
+                open a configuration file, select the config.xml file included
28
+                with Typica. When switching to a new version of Typica, it is
29
+                important to use the configuration file included with the new
30
+                version and not the old one. Opening that file will open the
31
+                <a href="navigation.html">main navigation window.</a></p>
32
+
33
+                <img src="openconfigurationfile.png"/>
34
+                
35
+            </div>
36
+        </div>
37
+    </body>
38
+</html>

BIN
docs/documentation/windowreference/openconfigurationfile.png View File


BIN
docs/documentation/windowreference/productionreports.png View File


BIN
docs/documentation/windowreference/purchasereports.png View File


BIN
docs/documentation/windowreference/reportsmenu.png View File


BIN
docs/documentation/windowreference/salesreport.png View File


+ 1396
- 0
src/Translations/Typica.ts
File diff suppressed because it is too large
View File


BIN
src/Translations/Typica_de.qm View File


+ 1396
- 0
src/Translations/Typica_de.ts
File diff suppressed because it is too large
View File


+ 3
- 0
src/Typica.pro View File

@@ -8,6 +8,7 @@ QT += sql
8 8
 QT += xmlpatterns
9 9
 QT += scripttools
10 10
 QT += webkit
11
+QT += svg
11 12
 
12 13
 CONFIG += extserialport
13 14
 
@@ -43,3 +44,5 @@ RESOURCES += \
43 44
 RC_FILE = typica.rc
44 45
 ICON = resources/icons/appicons/logo.icns
45 46
 QMAKE_INFO_PLIST = resources/Info.plist
47
+
48
+CODECFORTR = UTF-8

+ 5
- 5
src/abouttypica.cpp View File

@@ -1,9 +1,9 @@
1
-/*265:*/
1
+/*275:*/
2 2
 #line 33 "./abouttypica.w"
3 3
 
4 4
 #include "abouttypica.h"
5 5
 
6
-/*266:*/
6
+/*276:*/
7 7
 #line 42 "./abouttypica.w"
8 8
 
9 9
 AboutTypica::AboutTypica():QMainWindow(NULL)
@@ -17,10 +17,10 @@ aboutFile.close();
17 17
 setCentralWidget(banner);
18 18
 }
19 19
 
20
-#line 6404 "./typica.w"
20
+#line 6592 "./typica.w"
21 21
 
22
-/*:266*/
22
+/*:276*/
23 23
 #line 36 "./abouttypica.w"
24 24
 
25 25
 
26
-/*:265*/
26
+/*:275*/

+ 2
- 2
src/abouttypica.h View File

@@ -1,4 +1,4 @@
1
-/*264:*/
1
+/*274:*/
2 2
 #line 14 "./abouttypica.w"
3 3
 
4 4
 #include <QMainWindow> 
@@ -17,4 +17,4 @@ AboutTypica();
17 17
 
18 18
 #endif
19 19
 
20
-/*:264*/
20
+/*:274*/

+ 20
- 20
src/daterangeselector.cpp View File

@@ -1,4 +1,4 @@
1
-/*656:*/
1
+/*666:*/
2 2
 #line 70 "./daterangeselector.w"
3 3
 
4 4
 #include <QCalendarWidget> 
@@ -11,7 +11,7 @@
11 11
 
12 12
 #include "daterangeselector.h"
13 13
 
14
-/*658:*/
14
+/*668:*/
15 15
 #line 115 "./daterangeselector.w"
16 16
 
17 17
 CustomDateRangePopup::CustomDateRangePopup(QWidget*parent):
@@ -55,7 +55,7 @@ outerLayout->addLayout(buttonLayout);
55 55
 setLayout(outerLayout);
56 56
 }
57 57
 
58
-/*:658*//*659:*/
58
+/*:668*//*669:*/
59 59
 #line 163 "./daterangeselector.w"
60 60
 
61 61
 void CustomDateRangePopup::hideEvent(QHideEvent*)
@@ -63,7 +63,7 @@ void CustomDateRangePopup::hideEvent(QHideEvent*)
63 63
 emit hidingPopup();
64 64
 }
65 65
 
66
-/*:659*//*660:*/
66
+/*:669*//*670:*/
67 67
 #line 172 "./daterangeselector.w"
68 68
 
69 69
 void CustomDateRangePopup::applyRange()
@@ -78,7 +78,7 @@ endDateSelector->selectedDate().toString(Qt::ISODate)));
78 78
 hide();
79 79
 }
80 80
 
81
-/*:660*//*661:*/
81
+/*:670*//*671:*/
82 82
 #line 189 "./daterangeselector.w"
83 83
 
84 84
 void CustomDateRangePopup::validateRange()
@@ -93,10 +93,10 @@ applyButton->setEnabled(true);
93 93
 }
94 94
 }
95 95
 
96
-/*:661*/
96
+/*:671*/
97 97
 #line 81 "./daterangeselector.w"
98 98
 
99
-/*662:*/
99
+/*672:*/
100 100
 #line 207 "./daterangeselector.w"
101 101
 
102 102
 DateRangeSelector::DateRangeSelector(QWidget*parent):
@@ -108,7 +108,7 @@ connect(quickSelector,SIGNAL(currentIndexChanged(int)),this,SLOT(updateRange(int
108 108
 QDate currentDate= QDate::currentDate();
109 109
 
110 110
 QHBoxLayout*layout= new QHBoxLayout;
111
-/*663:*/
111
+/*673:*/
112 112
 #line 236 "./daterangeselector.w"
113 113
 
114 114
 quickSelector->addItem("Yesterday",QVariant(QStringList()<<
@@ -188,7 +188,7 @@ quickSelector->insertSeparator(quickSelector->count());
188 188
 quickSelector->addItem("Lifetime");
189 189
 quickSelector->addItem("Custom");
190 190
 
191
-/*:663*/
191
+/*:673*/
192 192
 #line 217 "./daterangeselector.w"
193 193
 
194 194
 QToolButton*customButton= new QToolButton;
@@ -201,7 +201,7 @@ setLayout(layout);
201 201
 connect(customButton,SIGNAL(clicked()),this,SLOT(toggleCustom()));
202 202
 }
203 203
 
204
-/*:662*//*664:*/
204
+/*:672*//*674:*/
205 205
 #line 319 "./daterangeselector.w"
206 206
 
207 207
 void DateRangeSelector::updateRange(int index)
@@ -217,7 +217,7 @@ emit rangeUpdated(quickSelector->itemData(quickSelector->currentIndex()));
217 217
 }
218 218
 }
219 219
 
220
-/*:664*//*665:*/
220
+/*:674*//*675:*/
221 221
 #line 336 "./daterangeselector.w"
222 222
 
223 223
 void DateRangeSelector::popupHidden()
@@ -227,7 +227,7 @@ customRangeSelector= NULL;
227 227
 quickSelector->setCurrentIndex(lastIndex);
228 228
 }
229 229
 
230
-/*:665*//*666:*/
230
+/*:675*//*676:*/
231 231
 #line 347 "./daterangeselector.w"
232 232
 
233 233
 void DateRangeSelector::setCustomRange(QVariant range)
@@ -238,7 +238,7 @@ lastIndex= quickSelector->count()-1;
238 238
 quickSelector->setCurrentIndex(lastIndex);
239 239
 }
240 240
 
241
-/*:666*//*667:*/
241
+/*:676*//*677:*/
242 242
 #line 362 "./daterangeselector.w"
243 243
 
244 244
 void DateRangeSelector::toggleCustom()
@@ -279,7 +279,7 @@ customRangeSelector= NULL;
279 279
 }
280 280
 }
281 281
 
282
-/*:667*//*668:*/
282
+/*:677*//*678:*/
283 283
 #line 404 "./daterangeselector.w"
284 284
 
285 285
 QVariant DateRangeSelector::currentRange()
@@ -287,7 +287,7 @@ QVariant DateRangeSelector::currentRange()
287 287
 return quickSelector->itemData(lastIndex);
288 288
 }
289 289
 
290
-/*:668*//*669:*/
290
+/*:678*//*679:*/
291 291
 #line 412 "./daterangeselector.w"
292 292
 
293 293
 void DateRangeSelector::setCurrentIndex(int index)
@@ -300,7 +300,7 @@ int DateRangeSelector::currentIndex()
300 300
 return quickSelector->currentIndex();
301 301
 }
302 302
 
303
-/*:669*//*670:*/
303
+/*:679*//*680:*/
304 304
 #line 432 "./daterangeselector.w"
305 305
 
306 306
 void DateRangeSelector::setLifetimeRange(QString startDate,QString endDate)
@@ -309,7 +309,7 @@ quickSelector->setItemData(quickSelector->count()-2,
309 309
 QVariant(QStringList()<<startDate<<endDate));
310 310
 }
311 311
 
312
-/*:670*//*671:*/
312
+/*:680*//*681:*/
313 313
 #line 442 "./daterangeselector.w"
314 314
 
315 315
 void DateRangeSelector::removeIndex(int index)
@@ -317,12 +317,12 @@ void DateRangeSelector::removeIndex(int index)
317 317
 quickSelector->removeItem(index);
318 318
 }
319 319
 
320
-/*:671*/
320
+/*:681*/
321 321
 #line 82 "./daterangeselector.w"
322 322
 
323 323
 
324
-#if 0
324
+#ifdef __unix__
325 325
 #include "moc_daterangeselector.cpp"
326 326
 #endif
327 327
 
328
-/*:656*/
328
+/*:666*/

+ 4
- 4
src/daterangeselector.h View File

@@ -1,4 +1,4 @@
1
-/*655:*/
1
+/*665:*/
2 2
 #line 30 "./daterangeselector.w"
3 3
 
4 4
 
@@ -9,7 +9,7 @@
9 9
 #ifndef TypicaDateRangeSelectorHeader
10 10
 #define TypicaDateRangeSelectorHeader
11 11
 
12
-/*657:*/
12
+/*667:*/
13 13
 #line 91 "./daterangeselector.w"
14 14
 
15 15
 class CustomDateRangePopup:public QWidget
@@ -31,7 +31,7 @@ QCalendarWidget*endDateSelector;
31 31
 QPushButton*applyButton;
32 32
 };
33 33
 
34
-/*:657*/
34
+/*:667*/
35 35
 #line 39 "./daterangeselector.w"
36 36
 
37 37
 
@@ -62,4 +62,4 @@ int lastIndex;
62 62
 
63 63
 #endif
64 64
 
65
-/*:655*/
65
+/*:665*/

+ 1
- 1
src/daterangeselector.w View File

@@ -81,7 +81,7 @@ class DateRangeSelector : public QWidget
81 81
 @<CustomDateRangePopup implementation@>
82 82
 @<DateRangeSelector implementation@>
83 83
 
84
-#if 0
84
+#ifdef __unix__
85 85
 #include "moc_daterangeselector.cpp"
86 86
 #endif
87 87
 

+ 2
- 2
src/draglabel.cpp View File

@@ -1,4 +1,4 @@
1
-/*992:*/
1
+/*1002:*/
2 2
 #line 33 "./scales.w"
3 3
 
4 4
 #include "draglabel.h"
@@ -26,4 +26,4 @@ drag->exec();
26 26
 }
27 27
 }
28 28
 
29
-/*:992*/
29
+/*:1002*/

+ 2
- 2
src/draglabel.h View File

@@ -1,4 +1,4 @@
1
-/*991:*/
1
+/*1001:*/
2 2
 #line 13 "./scales.w"
3 3
 
4 4
 #ifndef TypicaDragLabelInclude
@@ -17,4 +17,4 @@ void mousePressEvent(QMouseEvent*event);
17 17
 
18 18
 #endif
19 19
 
20
-/*:991*/
20
+/*:1001*/

+ 7
- 7
src/helpmenu.cpp View File

@@ -1,11 +1,11 @@
1
-/*194:*/
1
+/*204:*/
2 2
 #line 36 "./helpmenu.w"
3 3
 
4 4
 #include "helpmenu.h"
5 5
 #include "abouttypica.h"
6 6
 #include "licensewindow.h"
7 7
 
8
-/*195:*/
8
+/*205:*/
9 9
 #line 46 "./helpmenu.w"
10 10
 
11 11
 HelpMenu::HelpMenu():QMenu()
@@ -24,7 +24,7 @@ connect(licenseAction,SIGNAL(triggered()),this,SLOT(displayLicenseWindow()));
24 24
 #endif
25 25
 }
26 26
 
27
-/*:195*//*196:*/
27
+/*:205*//*206:*/
28 28
 #line 66 "./helpmenu.w"
29 29
 
30 30
 void HelpMenu::displayAboutTypica()
@@ -33,7 +33,7 @@ AboutTypica*aboutBox= new AboutTypica;
33 33
 aboutBox->show();
34 34
 }
35 35
 
36
-/*:196*//*197:*/
36
+/*:206*//*207:*/
37 37
 #line 76 "./helpmenu.w"
38 38
 
39 39
 void HelpMenu::displayLicenseWindow()
@@ -42,11 +42,11 @@ LicenseWindow*window= new LicenseWindow;
42 42
 window->show();
43 43
 }
44 44
 
45
-#line 4615 "./typica.w"
45
+#line 4772 "./typica.w"
46 46
 
47 47
 #line 1 "./licensewindow.w"
48
-/*:197*/
48
+/*:207*/
49 49
 #line 41 "./helpmenu.w"
50 50
 
51 51
 
52
-/*:194*/
52
+/*:204*/

+ 2
- 2
src/helpmenu.h View File

@@ -1,4 +1,4 @@
1
-/*193:*/
1
+/*203:*/
2 2
 #line 16 "./helpmenu.w"
3 3
 
4 4
 #include <QMenu> 
@@ -18,4 +18,4 @@ void displayLicenseWindow();
18 18
 
19 19
 #endif
20 20
 
21
-/*:193*/
21
+/*:203*/

+ 69
- 0
src/licensewindow.cpp View File

@@ -0,0 +1,69 @@
1
+/*209:*/
2
+#line 36 "./licensewindow.w"
3
+
4
+/*213:*/
5
+#line 97 "./licensewindow.w"
6
+
7
+#include "licensewindow.h"
8
+
9
+#include <QSplitter> 
10
+#include <QListWidget> 
11
+#include <QVariant> 
12
+#include <QUrl> 
13
+
14
+#line 4774 "./typica.w"
15
+
16
+/*:213*/
17
+#line 37 "./licensewindow.w"
18
+
19
+/*210:*/
20
+#line 43 "./licensewindow.w"
21
+
22
+LicenseWindow::LicenseWindow()
23
+:QMainWindow(NULL),view(new QWebView)
24
+{
25
+QSplitter*split= new QSplitter;
26
+QListWidget*projects= new QListWidget;
27
+
28
+/*212:*/
29
+#line 79 "./licensewindow.w"
30
+
31
+QListWidgetItem*item= new QListWidgetItem("Typica",projects);
32
+item->setData(Qt::UserRole,QVariant(QUrl("qrc:/resources/html/licenses/typica.html")));
33
+projects->setCurrentItem(item);
34
+setWebView(item,NULL);
35
+item= new QListWidgetItem("d3.js",projects);
36
+item->setData(Qt::UserRole,QVariant(QUrl("qrc:/resources/html/licenses/d3.html")));
37
+item= new QListWidgetItem("Entypo",projects);
38
+item->setData(Qt::UserRole,QVariant(QUrl("qrc:/resources/html/licenses/entypo.html")));
39
+item= new QListWidgetItem("Tango Desktop Project",projects);
40
+item->setData(Qt::UserRole,QVariant(QUrl("qrc:/resources/html/licenses/tango.html")));
41
+item= new QListWidgetItem("QextSerialPort",projects);
42
+item->setData(Qt::UserRole,QVariant(QUrl("qrc:/resources/html/licenses/qextserialport.html")));
43
+item= new QListWidgetItem("Qt",projects);
44
+item->setData(Qt::UserRole,QVariant(QUrl("qrc:/resources/html/licenses/qt.html")));
45
+
46
+/*:212*/
47
+#line 50 "./licensewindow.w"
48
+
49
+connect(projects,SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
50
+this,SLOT(setWebView(QListWidgetItem*,QListWidgetItem*)));
51
+
52
+split->addWidget(projects);
53
+split->addWidget(view);
54
+setCentralWidget(split);
55
+}
56
+
57
+/*:210*//*211:*/
58
+#line 64 "./licensewindow.w"
59
+
60
+void LicenseWindow::setWebView(QListWidgetItem*current,QListWidgetItem*)
61
+{
62
+view->load(current->data(Qt::UserRole).toUrl());
63
+}
64
+
65
+/*:211*/
66
+#line 38 "./licensewindow.w"
67
+
68
+
69
+/*:209*/

+ 24
- 0
src/licensewindow.h View File

@@ -0,0 +1,24 @@
1
+/*208:*/
2
+#line 13 "./licensewindow.w"
3
+
4
+#include <QMainWindow> 
5
+#include <QListWidgetItem> 
6
+#include <QWebView> 
7
+
8
+#ifndef TypicaLicenseHeader
9
+#define TypicaLicenseHeader
10
+
11
+class LicenseWindow:public QMainWindow
12
+{
13
+Q_OBJECT
14
+public:
15
+LicenseWindow();
16
+private slots:
17
+void setWebView(QListWidgetItem*current,QListWidgetItem*);
18
+private:
19
+QWebView*view;
20
+};
21
+
22
+#endif
23
+
24
+/*:208*/

+ 6
- 3
src/moc_helpmenu.cpp View File

@@ -22,7 +22,7 @@ static const uint qt_meta_data_HelpMenu[] = {
22 22
        6,       // revision
23 23
        0,       // classname
24 24
        0,    0, // classinfo
25
-       1,   14, // methods
25
+       2,   14, // methods
26 26
        0,    0, // properties
27 27
        0,    0, // enums/sets
28 28
        0,    0, // constructors
@@ -31,12 +31,14 @@ static const uint qt_meta_data_HelpMenu[] = {
31 31
 
32 32
  // slots: signature, parameters, type, tag, flags
33 33
       10,    9,    9,    9, 0x0a,
34
+      31,    9,    9,    9, 0x0a,
34 35
 
35 36
        0        // eod
36 37
 };
37 38
 
38 39
 static const char qt_meta_stringdata_HelpMenu[] = {
39 40
     "HelpMenu\0\0displayAboutTypica()\0"
41
+    "displayLicenseWindow()\0"
40 42
 };
41 43
 
42 44
 void HelpMenu::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
@@ -46,6 +48,7 @@ void HelpMenu::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, vo
46 48
         HelpMenu *_t = static_cast<HelpMenu *>(_o);
47 49
         switch (_id) {
48 50
         case 0: _t->displayAboutTypica(); break;
51
+        case 1: _t->displayLicenseWindow(); break;
49 52
         default: ;
50 53
         }
51 54
     }
@@ -84,9 +87,9 @@ int HelpMenu::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
84 87
     if (_id < 0)
85 88
         return _id;
86 89
     if (_c == QMetaObject::InvokeMetaMethod) {
87
-        if (_id < 1)
90
+        if (_id < 2)
88 91
             qt_static_metacall(this, _c, _id, _a);
89
-        _id -= 1;
92
+        _id -= 2;
90 93
     }
91 94
     return _id;
92 95
 }

+ 15
- 6
src/moc_typica.cpp View File

@@ -3759,25 +3759,31 @@ static const uint qt_meta_data_RoasterConfWidget[] = {
3759 3759
        6,       // revision
3760 3760
        0,       // classname
3761 3761
        0,    0, // classinfo
3762
-       1,   14, // methods
3762
+       4,   14, // methods
3763 3763
        0,    0, // properties
3764 3764
        0,    0, // enums/sets
3765
-       1,   19, // constructors
3765
+       1,   34, // constructors
3766 3766
        0,       // flags
3767 3767
        0,       // signalCount
3768 3768
 
3769 3769
  // slots: signature, parameters, type, tag, flags
3770 3770
       22,   19,   18,   18, 0x08,
3771
+      49,   43,   18,   18, 0x08,
3772
+      74,   43,   18,   18, 0x08,
3773
+      98,   43,   18,   18, 0x08,
3771 3774
 
3772 3775
  // constructors: signature, parameters, type, tag, flags
3773
-      55,   43,   18,   18, 0x0e,
3776
+     138,  126,   18,   18, 0x0e,
3774 3777
 
3775 3778
        0        // eod
3776 3779
 };
3777 3780
 
3778 3781
 static const char qt_meta_stringdata_RoasterConfWidget[] = {
3779 3782
     "RoasterConfWidget\0\0id\0updateRoasterId(int)\0"
3780
-    "model,index\0RoasterConfWidget(DeviceTreeModel*,QModelIndex)\0"
3783
+    "value\0updateCapacityCheck(int)\0"
3784
+    "updateCapacity(QString)\0"
3785
+    "updateCapacityUnit(QString)\0model,index\0"
3786
+    "RoasterConfWidget(DeviceTreeModel*,QModelIndex)\0"
3781 3787
 };
3782 3788
 
3783 3789
 void RoasterConfWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
@@ -3792,6 +3798,9 @@ void RoasterConfWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, in
3792 3798
         RoasterConfWidget *_t = static_cast<RoasterConfWidget *>(_o);
3793 3799
         switch (_id) {
3794 3800
         case 0: _t->updateRoasterId((*reinterpret_cast< int(*)>(_a[1]))); break;
3801
+        case 1: _t->updateCapacityCheck((*reinterpret_cast< int(*)>(_a[1]))); break;
3802
+        case 2: _t->updateCapacity((*reinterpret_cast< const QString(*)>(_a[1]))); break;
3803
+        case 3: _t->updateCapacityUnit((*reinterpret_cast< const QString(*)>(_a[1]))); break;
3795 3804
         default: ;
3796 3805
         }
3797 3806
     }
@@ -3829,9 +3838,9 @@ int RoasterConfWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
3829 3838
     if (_id < 0)
3830 3839
         return _id;
3831 3840
     if (_c == QMetaObject::InvokeMetaMethod) {
3832
-        if (_id < 1)
3841
+        if (_id < 4)
3833 3842
             qt_static_metacall(this, _c, _id, _a);
3834
-        _id -= 1;
3843
+        _id -= 4;
3835 3844
     }
3836 3845
     return _id;
3837 3846
 }

+ 31810
- 31572
src/qrc_resources.cpp
File diff suppressed because it is too large
View File


+ 6
- 6
src/resources/Info.plist View File

@@ -1,13 +1,13 @@
1 1
 <?xml version="1.0" encoding="UTF-8"?>
2
-<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
3
-<plist version="0.9">
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4 4
 <dict>
5 5
 	<key>CFBundleIconFile</key>
6 6
 	<string>@ICON@</string>
7 7
 	<key>CFBundlePackageType</key>
8 8
 	<string>APPL</string>
9 9
 	<key>CFBundleGetInfoString</key>
10
-	<string>Typica 1.6.4</string>
10
+	<string>Typica 1.7.0</string>
11 11
 	<key>CFBundleSignature</key>
12 12
 	<string>@TYPEINFO@</string>
13 13
 	<key>CFBundleExecutable</key>
@@ -17,10 +17,10 @@
17 17
 	<key>CFBundleDisplayName</key>
18 18
 	<string>Typica</string>
19 19
 	<key>CFBundleShortVersionString</key>
20
-	<string>1.6.4</string>
20
+	<string>1.7.0</string>
21 21
 	<key>CFBundleVersion</key>
22
-	<string>1.6.4</string>
22
+	<string>1.7.0</string>
23 23
 	<key>NSHumanReadableCopyright</key>
24
-	<string>© 2007–2015 Neal Wilson</string>
24
+	<string>© 2007–2016 Neal Wilson</string>
25 25
 </dict>
26 26
 </plist>

+ 7
- 83
src/resources/html/about.html View File

@@ -10,10 +10,10 @@
10 10
 				<div class="topbanner">
11 11
 					<a href="http://www.randomfield.com/programs/typica/"><img src="../icons/appicons/logo96.png" height="96px" width="96px" alt="Typica logo" /></a>
12 12
 					<h1><a href="http://www.randomfield.com/programs/typica/">Typica</a></h1>
13
-					<h2>Version 1.6.4</h2>
13
+					<h2>Version 1.7.0</h2>
14 14
 				</div>
15 15
 			<div id="maintext">
16
-				<p>Copyright &copy; 2007&ndash;2015 Neal Evan Wilson
16
+				<p>Copyright &copy; 2007&ndash;2016 Neal Evan Wilson
17 17
 					<span class="icons">
18 18
 						<a href="mailto:roaster@wilsonscoffee.com?subject=Thanks%20for%20Typica&amp;body=Message%20initiated%20from%20Typica.">&#9993;</a>
19 19
 						<a href="https://twitter.com/N3Roaster">&#62217;</a>
@@ -22,87 +22,11 @@
22 22
 						<a href="http://www.linkedin.com/profile/view?id=179814079">&#62232;</a>
23 23
 					</span>
24 24
 				</p>
25
-				<h3>Special Thanks</h3>
26
-				<p>Ongoing development of Typica is made possible through the
27
-				generous financial contributions from the following:</p>
28
-				
29
-				<p>La Cosecha Coffee Roasters</p>
30
-				<p>Pinnacle Coffee Roasting</p>
31
-				<p>Water Street Coffee Roaster</p>
32
-				<p>Anonymous Funders</p>
33
-				
34
-				<p>Hardware for developing Phidgets 1048 support was provided
35
-				by Phidgets, Inc.</p>
36
-				
37
-				<p>Special thanks also to all who have sent bug reports and
38
-				feature requests.</p>
39
-
40
-				<div class="nbg">
41
-				<div class="nextversion">
42
-				<div class="nextbanner">
43
-					<img src="typica2logo96.png" height="96px" width="96px" alt="Typica 2.0 logo" />
44
-					<h1>Preview of the Next Episode</h3>
45
-				</div>
46
-				<p>This will likely be one of the last releases in the 1.x
47
-				series. Typica 1.0 was released in 2007 and I've learned a
48
-				lot from developing this, using it, and having conversations
49
-				with others about how they're using this. While there is the
50
-				possibility of further improvements, I believe Typica has
51
-				reached a point of maturity with a feature set appropriate for
52
-				many coffee roasting firms.</p>
53
-
54
-				<p>There is a large set of features and extensions that I have
55
-				avoided implementing because the changes would have been too
56
-				intrusive for the code base as it currently exists, but that
57
-				feature set has become too compelling for me to leave undone.
58
-				I am, therefore, attempting to spend as much time as I can on
59
-				the development of Typica 2.0. This removes many of the legacy
60
-				considerations and modernizes the tools and techniques used. I
61
-				hope that the result is a better and more broadly useful Typica
62
-				that can be maintained for another several years.</p>
63
-				
64
-				<p>While Typica is free and will continue to be made freely
65
-				available complete with all source code, there are still costs
66
-				associated with ongoing development and keeping the software
67
-				available. Some of the costs include:
68
-					<ul>
69
-						<li>Obtaining and maintaining hardware appropriate for
70
-						building and testing new releases</li>
71
-						<li>Building and maintaining test rigs for various
72
-						types of data acquisition hardware</li>
73
-						<li>Maintaining the ability to test on new operating
74
-						system releases</li>
75
-						<li>Hosting service</li>
76
-					</ul>
77
-				There is also a huge commitment of time in developing, testing,
78
-				and supporting this.</p>
79
-
80
-				<p>Financial support to date does not come close to covering
81
-				these costs, nor has it been enough that I could consider
82
-				hiring people to improve the pace and quality of ongoing
83
-				development. This is fine. I have no intention of halting work
84
-				on the project as my work has benefitted greatly from having
85
-				this software and I expect even greater benefit from the next
86
-				major release. Personally I find the new feature set exciting
87
-				to work on and am willing to cover funding shortfalls when I
88
-				can afford to do so.</p>
89
-				
90
-				<p>That said, if you're finding this software to be useful in
91
-				your business, if you've had a good support experience, or if
92
-				you'd like to help me continue to develop this software, please
93
-				consider providing some financial support to:</p>
94
-				
95
-				<p>Neal Wilson<br />
96
-				c/o Wilson's Coffee &amp; Tea<br />
97
-				3306 Washington Ave.<br />
98
-				Racine, WI 53405<br />
99
-				USA</p>
100
-				
101
-				<p>Another great way to help Typica right now is to reach out
102
-				to your professional colleagues and let them know that you're
103
-				using Typica and what you like about it.</p>
104
-				</div>
105
-				</div>
25
+				<p>German Translation: Mario Champignon
26
+                                    <span class="icons">
27
+                                        <a href="mailto:mario_champignon@hotmail.com">&#9993;</a>
28
+                                    </span>
29
+                                </p>
106 30
 				
107 31
 				<h3>License Information</h3>
108 32
 				<p>Permission is hereby granted, free of charge, to any person

+ 6
- 6
src/scale.cpp View File

@@ -1,4 +1,4 @@
1
-/*998:*/
1
+/*1008:*/
2 2
 #line 131 "./scales.w"
3 3
 
4 4
 #include "scale.h"
@@ -10,7 +10,7 @@ QextSerialPort(port,QextSerialPort::EventDriven)
10 10
 connect(this,SIGNAL(readyRead()),this,SLOT(dataAvailable()));
11 11
 }
12 12
 
13
-/*:998*//*999:*/
13
+/*:1008*//*1009:*/
14 14
 #line 149 "./scales.w"
15 15
 
16 16
 void SerialScale::dataAvailable()
@@ -24,7 +24,7 @@ responseBuffer.clear();
24 24
 }
25 25
 else
26 26
 {
27
-/*1000:*/
27
+/*1010:*/
28 28
 #line 189 "./scales.w"
29 29
 
30 30
 QStringList responseParts= QString(responseBuffer.simplified()).split(' ');
@@ -53,7 +53,7 @@ unit= Units::Ounce;
53 53
 }
54 54
 emit newMeasurement(weight,unit);
55 55
 
56
-/*:1000*/
56
+/*:1010*/
57 57
 #line 161 "./scales.w"
58 58
 
59 59
 responseBuffer.clear();
@@ -61,7 +61,7 @@ responseBuffer.clear();
61 61
 }
62 62
 }
63 63
 
64
-/*:999*//*1001:*/
64
+/*:1009*//*1011:*/
65 65
 #line 220 "./scales.w"
66 66
 
67 67
 void SerialScale::tare()
@@ -74,4 +74,4 @@ void SerialScale::weigh()
74 74
 write("!KP\x0D");
75 75
 }
76 76
 
77
-/*:1001*/
77
+/*:1011*/

+ 2
- 2
src/scale.h View File

@@ -1,4 +1,4 @@
1
-/*997:*/
1
+/*1007:*/
2 2
 #line 103 "./scales.w"
3 3
 
4 4
 #ifndef TypicaScaleInclude
@@ -25,4 +25,4 @@ QByteArray responseBuffer;
25 25
 
26 26
 #endif
27 27
 
28
-/*:997*/
28
+/*:1007*/

+ 2697
- 2453
src/typica.cpp
File diff suppressed because it is too large
View File


+ 6
- 6
src/typica.rc View File

@@ -1,7 +1,7 @@
1 1
 #include <winver.h>
2 2
 VS_VERSION_INFO	VERSIONINFO
3
-FILEVERSION 	1,6,4,0
4
-PRODUCTVERSION 	1,6,4,0
3
+FILEVERSION 	1,7,0,0
4
+PRODUCTVERSION 	1,7,0,0
5 5
 FILEFLAGSMASK	0x3fL
6 6
 #ifdef _DEBUG
7 7
 	FILEFLAGS	VS_FF_DEBUG
@@ -16,13 +16,13 @@ BEGIN
16 16
 		BLOCK "040904b0"
17 17
 		BEGIN
18 18
 			VALUE "CompanyName", "Wilson's Coffee & Tea\0"
19
-			VALUE "FileDescription", "Typica 1.6.4\0"
20
-			VALUE "FileVersion", "1.6.4\0"
19
+			VALUE "FileDescription", "Typica 1.7.0\0"
20
+			VALUE "FileVersion", "1.7.0\0"
21 21
 			VALUE "InternalName", "Typica\0"
22
-			VALUE "LegalCopyright", "Copyright 2007-2015 Neal Evan Wilson\0"
22
+			VALUE "LegalCopyright", "Copyright 2007-2016 Neal Evan Wilson\0"
23 23
 			VALUE "OriginalFilename", "Typica.exe\0"
24 24
 			VALUE "ProductName", "Typica\0"
25
-			VALUE "ProductVersion", "1.6.4\0"
25
+			VALUE "ProductVersion", "1.7.0\0"
26 26
 		END
27 27
 	END
28 28
 	BLOCK "VarFileInfo"

+ 274
- 25
src/typica.w View File

@@ -22,8 +22,8 @@
22 22
 \mark{\noexpand\nullsec0{A Note on Notation}}
23 23
 \def\pn{Typica}
24 24
 \def\filebase{typica}
25
-\def\version{1.6.4 \number\year-\number\month-\number\day}
26
-\def\years{2007--2015}
25
+\def\version{1.7.0 \number\year-\number\month-\number\day}
26
+\def\years{2007--2016}
27 27
 \def\title{\pn{} (Version \version)}
28 28
 \newskip\dangerskipb
29 29
 \newskip\dangerskip
@@ -623,6 +623,7 @@ various Qt modules.
623 623
 #include <QtDebug>
624 624
 #include <QtXmlPatterns>
625 625
 #include <QtWebKit>
626
+#include <QtSvg>
626 627
 
627 628
 @ New code is being written in separate files in a long term effort to improve
628 629
 organization of the code. The result of this is that some additional headers
@@ -921,6 +922,53 @@ QScriptValue QWidget_activateWindow(QScriptContext *context,
921 922
     return QScriptValue();
922 923
 }
923 924
 
925
+@* Scripting QMessageBox.
926
+
927
+\noindent Some features require that \pn{} pauses an operation until further
928
+information can be obtained. An example of this is discretionary validation
929
+where input is checked and if it seems unlikely but not impossible to be
930
+correct a dialog should come up asking if that input is correct. If it is not,
931
+the operation should be cancelled and the person using \pn{} should be allowed
932
+to correct the information and try again.
933
+
934
+For this use case, it is not necessary to fully expose the |QMessageBox| class.
935
+Instead, it is enough to provide a function that will raise an appropriate
936
+message and return the selected action.
937
+
938
+@<Function prototypes for scripting@>=
939
+QScriptValue displayWarning(QScriptContext *context, QScriptEngine *engine);
940
+QScriptValue displayError(QScriptContext *context, QScriptEngine *engine);
941
+
942
+@ This function is exposed to the host environment.
943
+
944
+@<Set up the scripting engine@>=
945
+constructor = engine->newFunction(displayWarning);
946
+engine->globalObject().setProperty("displayWarning", constructor);
947
+constructor = engine->newFunction(displayError);
948
+engine->globalObject().setProperty("displayError", constructor);
949
+
950
+@ The function takes some arguments.
951
+
952
+@<Functions for scripting@>=
953
+QScriptValue displayWarning(QScriptContext *context, QScriptEngine *)
954
+{
955
+    QMessageBox::StandardButton selection = QMessageBox::warning(NULL,
956
+        argument<QString>(0, context),
957
+        argument<QString>(1, context),
958
+        QMessageBox::Ok | QMessageBox::Cancel);
959
+    if(selection == QMessageBox::Ok) {
960
+        return QScriptValue(true);
961
+    }
962
+    return QScriptValue(false);
963
+}
964
+
965
+QScriptValue displayError(QScriptContext *context, QScriptEngine *)
966
+{
967
+    QMessageBox::critical(NULL, argument<QString>(0, context),
968
+                          argument<QString>(1, context));
969
+    return QScriptValue();
970
+}
971
+
924 972
 @* Scripting QMainWindow.
925 973
 
926 974
 \noindent Rather than directly exposing |QMainWindow| to the scripting engine,
@@ -1363,6 +1411,93 @@ void setQLabelProperties(QScriptValue value, QScriptEngine *engine)
1363 1411
     setQFrameProperties(value, engine);
1364 1412
 }
1365 1413
 
1414
+@* Scripting QSvgWidget.
1415
+
1416
+\noindent Sometimes it is useful to provide a space for simple drawings without
1417
+the need for all of the other capabilities of a web view. This was introduced
1418
+as a way to draw box plots to help guide the creation of roast specifications.
1419
+
1420
+@<Function prototypes for scripting@>=
1421
+void setQSvgWidgetProperties(QScriptValue value, QScriptEngine *engine);
1422
+QScriptValue constructQSvgWidget(QScriptContext *context,
1423
+                                 QScriptEngine *engine);
1424
+QScriptValue QSvgWidget_loadDevice(QScriptContext *context,
1425
+                                   QScriptEngine *engine);
1426
+void addSvgWidgetToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
1427
+                          QStack<QLayout *> *layoutStack);
1428
+
1429
+@ The constructor must be passed to the scripting engine.
1430
+
1431
+@<Set up the scripting engine@>=
1432
+constructor = engine->newFunction(constructQSvgWidget);
1433
+value = engine->newQMetaObject(&QSvgWidget::staticMetaObject, constructor);
1434
+engine->globalObject().setProperty("QSvgWidget", value);
1435
+
1436
+@ The constructor is trivial.
1437
+
1438
+@<Functions for scripting@>=
1439
+QScriptValue constructQSvgWidget(QScriptContext *,
1440
+                                 QScriptEngine *engine)
1441
+{
1442
+    QScriptValue object = engine->newQObject(new QSvgWidget);
1443
+    setQSvgWidgetProperties(object, engine);
1444
+    return object;
1445
+}
1446
+
1447
+@ A property is added that allows loading data from a |QIODevice|.
1448
+
1449
+@<Functions for scripting@>=
1450
+void setQSvgWidgetProperties(QScriptValue value, QScriptEngine *engine)
1451
+{
1452
+    setQWidgetProperties(value, engine);
1453
+    value.setProperty("loadDevice",
1454
+                      engine->newFunction(QSvgWidget_loadDevice));
1455
+}
1456
+
1457
+QScriptValue QSvgWidget_loadDevice(QScriptContext *context, QScriptEngine *)
1458
+{
1459
+    if(context->argumentCount() == 1)
1460
+    {
1461
+        QSvgWidget *self = getself<@[QSvgWidget *@]>(context);
1462
+        QIODevice *device = argument<QIODevice *>(0, context);
1463
+        device->reset();
1464
+        QByteArray data = device->readAll();
1465
+        self->load(data);
1466
+    }
1467
+    else
1468
+    {
1469
+        context->throwError("Incorrect number of arguments passed to "@|
1470
+                            "QSvgWidget::loadData(). This method takes one "@|
1471
+                            "QIODevice as an argument.");
1472
+    }
1473
+    return QScriptValue();
1474
+}
1475
+
1476
+@ Additional work is needed to allow including this from the XML description of
1477
+a window.
1478
+
1479
+@<Additional box layout elements@>=
1480
+else if(currentElement.tagName() == "svgwidget")
1481
+{
1482
+    addSvgWidgetToLayout(currentElement, widgetStack, layoutStack);
1483
+}
1484
+
1485
+@ The function used to create this follows the usual pattern.
1486
+
1487
+@<Functions for scripting@>=
1488
+void addSvgWidgetToLayout(QDomElement element, QStack<QWidget *> *,
1489
+                          QStack<QLayout *> *layoutStack)
1490
+{
1491
+    QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
1492
+    QSvgWidget *widget = new QSvgWidget;
1493
+    layout->addWidget(widget);
1494
+    QString id = element.attribute("id");
1495
+    if(!id.isEmpty())
1496
+    {
1497
+        widget->setObjectName(id);
1498
+    }
1499
+}
1500
+
1366 1501
 @* Scripting QLineEdit.
1367 1502
 
1368 1503
 \noindent Similarly, we may want to allow line edits in interfaces defined
@@ -4014,6 +4149,7 @@ QScriptValue annotationFromRecord(QScriptContext *context,
4014 4149
                                   QScriptEngine *engine);
4015 4150
 QScriptValue setTabOrder(QScriptContext *context, QScriptEngine *engine);
4016 4151
 QScriptValue saveFileFromDatabase(QScriptContext *context, QScriptEngine *engine);
4152
+QScriptValue scriptTr(QScriptContext *context, QScriptEngine *engine);
4017 4153
 
4018 4154
 @ These functions are passed to the scripting engine.
4019 4155
 
@@ -4029,6 +4165,7 @@ engine->globalObject().setProperty("setTabOrder",
4029 4165
                                    engine->newFunction(setTabOrder));
4030 4166
 engine->globalObject().setProperty("saveFileFromDatabase",
4031 4167
                                    engine->newFunction(saveFileFromDatabase));
4168
+engine->globalObject().setProperty("TTR", engine->newFunction(scriptTr));
4032 4169
 
4033 4170
 @ These functions are not part of an object. They expect a string specifying
4034 4171
 the path to a file and return a string with either the name of the file without
@@ -4155,6 +4292,16 @@ QScriptValue setTabOrder(QScriptContext *context, QScriptEngine *)
4155 4292
     return QScriptValue();
4156 4293
 }
4157 4294
 
4295
+@ This function is used to allow text that must be placed in scripts to be
4296
+translated into other languages.
4297
+
4298
+@<Functions for scripting@>=
4299
+QScriptValue scriptTr(QScriptContext *context, QScriptEngine *)
4300
+{
4301
+    return QScriptValue(QCoreApplication::translate(
4302
+        "configuration",
4303
+        argument<QString>(1, context).toUtf8().data()));
4304
+}
4158 4305
 
4159 4306
 @** Application Configuration.
4160 4307
 
@@ -4204,6 +4351,13 @@ if(!filename.isEmpty())
4204 4351
     QFile file(filename);
4205 4352
     QFileInfo info(filename);
4206 4353
     directory = info.dir();
4354
+    QTextCodec::setCodecForTr(QTextCodec::codecForName("utf-8"));
4355
+    QTranslator *configtr = new QTranslator;
4356
+    if(configtr->load(QString("config.%1").arg(QLocale::system().name()),
4357
+                     QString("%1/Translations").arg(directory.canonicalPath())))
4358
+    {
4359
+        QCoreApplication::installTranslator(configtr);
4360
+    }
4207 4361
     settings.setValue("config", directory.path());
4208 4362
     if(file.open(QIODevice::ReadOnly))
4209 4363
     {
@@ -4528,6 +4682,7 @@ while(i < windowChildren.count())
4528 4682
         }
4529 4683
         else if(element.tagName() == "layout")
4530 4684
         {
4685
+            element.setAttribute("trcontext", "configuration");
4531 4686
             addLayoutToWidget(element, &widgetStack, &layoutStack);
4532 4687
         }
4533 4688
         else if(element.tagName() == "menu")
@@ -4560,7 +4715,8 @@ bar->setParent(window);
4560 4715
 bar->setObjectName("menuBar");
4561 4716
 if(element.hasAttribute("name"))
4562 4717
 {
4563
-    QMenu *menu = bar->addMenu(element.attribute("name"));
4718
+    QMenu *menu = bar->addMenu(QCoreApplication::translate("configuration",
4719
+                                                           element.attribute("name").toUtf8().data()));
4564 4720
     menu->setParent(bar);
4565 4721
     if(element.hasAttribute("type"))
4566 4722
     {
@@ -4592,7 +4748,8 @@ while(j < menuItems.count())
4592 4748
         QDomElement itemElement = item.toElement();
4593 4749
         if(itemElement.tagName() == "item")
4594 4750
         {
4595
-            QAction *itemAction = new QAction(itemElement.text(), menu);
4751
+            QAction *itemAction = new QAction(QCoreApplication::translate("configuration",
4752
+                                              itemElement.text().toUtf8().data()), menu);
4596 4753
             if(itemElement.hasAttribute("id"))
4597 4754
             {
4598 4755
                 itemAction->setObjectName(itemElement.attribute("id"));
@@ -4712,6 +4869,7 @@ void populateStackedLayout(QDomElement element, QStack<QWidget *> *widgetStack,
4712 4869
                 QWidget *widget = new QWidget;
4713 4870
                 layout->addWidget(widget);
4714 4871
                 widgetStack->push(widget);
4872
+                currentElement.setAttribute("trcontext", "configuration");
4715 4873
                 populateWidget(currentElement, widgetStack, layoutStack);
4716 4874
                 widgetStack->pop();
4717 4875
             }
@@ -4822,6 +4980,7 @@ for(int j = 0; j < rowChildren.count(); j++)
4822 4980
             QHBoxLayout *cell = new QHBoxLayout;
4823 4981
             layout->addLayout(cell, row, column, vspan, hspan);
4824 4982
             layoutStack->push(cell);
4983
+            columnElement.setAttribute("trcontext", "configuration");
4825 4984
             populateBoxLayout(columnElement, widgetStack, layoutStack);
4826 4985
             layoutStack->pop();
4827 4986
         }
@@ -4844,6 +5003,7 @@ void populateBoxLayout(QDomElement element, QStack<QWidget *> *widgetStack,
4844 5003
         if(current.isElement())
4845 5004
         {
4846 5005
             currentElement = current.toElement();
5006
+            currentElement.setAttribute("trcontext", "configuration");
4847 5007
             if(currentElement.tagName() == "button")
4848 5008
             {
4849 5009
                 addButtonToLayout(currentElement, widgetStack, layoutStack);
@@ -4869,7 +5029,9 @@ void populateBoxLayout(QDomElement element, QStack<QWidget *> *widgetStack,
4869 5029
             {
4870 5030
                 QBoxLayout *layout =
4871 5031
                     qobject_cast<QBoxLayout *>(layoutStack->top());
4872
-                QLabel *label = new QLabel(currentElement.text());
5032
+                QLabel *label = new QLabel(QCoreApplication::translate(
5033
+                    "configuration",
5034
+                    currentElement.text().toUtf8().data()));
4873 5035
                 layout->addWidget(label);
4874 5036
             }
4875 5037
             else if(currentElement.tagName() == "lcdtemperature")
@@ -5013,6 +5175,7 @@ void populateSplitter(QDomElement element, QStack<QWidget *> *widgetStack,@|
5013 5175
         if(current.isElement())
5014 5176
         {
5015 5177
             currentElement = current.toElement();
5178
+            currentElement.setAttribute("trcontext", "configuration");
5016 5179
             if(currentElement.tagName() == "decoration")
5017 5180
             {
5018 5181
                 addDecorationToSplitter(currentElement, widgetStack,
@@ -5158,7 +5321,9 @@ void addDecorationToSplitter(QDomElement element,
5158 5321
 labeled.
5159 5322
 
5160 5323
 @<Set up decoration@>=
5161
-QString labelText = element.attribute("name");
5324
+QString labelText =
5325
+    QCoreApplication::translate("configuration",
5326
+    element.attribute("name").toUtf8().data());
5162 5327
 Qt::Orientations@, orientation = Qt::Horizontal;
5163 5328
 if(element.hasAttribute("type"))
5164 5329
 {
@@ -5254,6 +5419,7 @@ void populateWidget(QDomElement element, QStack<QWidget *> *widgetStack,@|
5254 5419
             currentElement = current.toElement();
5255 5420
             if(currentElement.tagName() == "layout")
5256 5421
             {
5422
+                currentElement.setAttribute("trcontext", "configuration");
5257 5423
                 addLayoutToWidget(currentElement, widgetStack, layoutStack);
5258 5424
             }
5259 5425
         }
@@ -5269,7 +5435,9 @@ void addButtonToLayout(QDomElement element, QStack<QWidget *> *,@|
5269 5435
                        QStack<QLayout *> *layoutStack)
5270 5436
 {
5271 5437
     QAbstractButton *button = NULL;
5272
-    QString text = element.attribute("name");
5438
+    QString text =
5439
+        QCoreApplication::translate("configuration",
5440
+                                    element.attribute("name").toUtf8().data());
5273 5441
     if(element.hasAttribute("type"))
5274 5442
     {
5275 5443
         QString type = element.attribute("type");
@@ -5323,11 +5491,15 @@ void addSpinBoxToLayout(QDomElement element, QStack<QWidget *> *,@|
5323 5491
     AnnotationSpinBox *box = new AnnotationSpinBox("", "", NULL);
5324 5492
     if(element.hasAttribute("pretext"))
5325 5493
     {
5326
-        box->setPretext(element.attribute("pretext"));
5494
+        box->setPretext(QCoreApplication::translate(
5495
+                        "configuration",
5496
+                        element.attribute("pretext").toUtf8().data()));
5327 5497
     }
5328 5498
     if(element.hasAttribute("posttext"))
5329 5499
     {
5330
-        box->setPosttext(element.attribute("posttext"));
5500
+        box->setPosttext(QCoreApplication::translate(
5501
+                         "configuration",
5502
+                         element.attribute("posttext").toUtf8().data()));
5331 5503
     }
5332 5504
     if(element.hasAttribute("series"))
5333 5505
     {
@@ -5377,10 +5549,6 @@ void addZoomLogToSplitter(QDomElement element, QStack<QWidget *> *widgetStack,
5377 5549
                           QStack<QLayout *> *)
5378 5550
 {
5379 5551
     ZoomLog *widget = new ZoomLog;
5380
-    if(!widget)
5381
-    {
5382
-        qDebug() << "Error constructing widget!";
5383
-    }
5384 5552
     if(element.hasAttribute("id"))
5385 5553
     {
5386 5554
         widget->setObjectName(element.attribute("id"));
@@ -5399,7 +5567,10 @@ void addZoomLogToSplitter(QDomElement element, QStack<QWidget *> *widgetStack,
5399 5567
                 currentElement = current.toElement();
5400 5568
                 if(currentElement.tagName() == "column")
5401 5569
                 {
5402
-                    QString text = currentElement.text();
5570
+                    QString text =
5571
+                        QCoreApplication::translate(
5572
+                            "configuration",
5573
+                            currentElement.text().toUtf8().data());
5403 5574
                     widget->setHeaderData(column, text);
5404 5575
                     column++;
5405 5576
                 }
@@ -5543,8 +5714,11 @@ void addSaltToLayout(QDomElement element, QStack<QWidget *> *,@|
5543 5714
                 {
5544 5715
                     if(currentElement.hasAttribute("name"))
5545 5716
                     {
5546
-                        model->setHeaderData(currentColumn, Qt::Horizontal,
5547
-                                             currentElement.attribute("name"));
5717
+                        model->setHeaderData(currentColumn,
5718
+                            Qt::Horizontal,
5719
+                            QCoreApplication::translate(
5720
+                                "configuration",
5721
+                                currentElement.attribute("name").toUtf8().data()));
5548 5722
                     }
5549 5723
                     if(currentElement.hasAttribute("delegate"))
5550 5724
                     {
@@ -5925,6 +6099,8 @@ void setQDateTimeEditProperties(QScriptValue value, QScriptEngine *engine)
5925 6099
     value.setProperty("day", engine->newFunction(QDateTimeEdit_day));
5926 6100
     value.setProperty("month", engine->newFunction(QDateTimeEdit_month));
5927 6101
     value.setProperty("year", engine->newFunction(QDateTimeEdit_year));
6102
+    value.setProperty("setToCurrentTime",
6103
+                      engine->newFunction(QDateTimeEdit_setToCurrentTime));
5928 6104
 }
5929 6105
 
5930 6106
 @ Certain operations on a |QDateEdit| are easier with a few convenience
@@ -5969,6 +6145,13 @@ QScriptValue QDateTimeEdit_year(QScriptContext *context, QScriptEngine *)
5969 6145
     return QScriptValue(self->date().year());
5970 6146
 }
5971 6147
 
6148
+QScriptValue QDateTimeEdit_setToCurrentTime(QScriptContext *context, QScriptEngine *)
6149
+{
6150
+    QDateTimeEdit *self = getself<QDateTimeEdit *>(context);
6151
+    self->setDateTime(QDateTime::currentDateTime());
6152
+    return QScriptValue();
6153
+}
6154
+
5972 6155
 @ A few function prototypes are needed for this.
5973 6156
 
5974 6157
 @<Function prototypes for scripting@>=
@@ -5980,6 +6163,7 @@ QScriptValue QDateTimeEdit_day(QScriptContext *context, QScriptEngine *engine);
5980 6163
 QScriptValue QDateTimeEdit_month(QScriptContext *context,
5981 6164
                                  QScriptEngine *engine);
5982 6165
 QScriptValue QDateTimeEdit_year(QScriptContext *context, QScriptEngine *engine);
6166
+QScriptValue QDateTimeEdit_setToCurrentTime(QScriptContext *context, QScriptEngine *engine);
5983 6167
 
5984 6168
 @ In order to get to objects created from the XML description, it is necessary
5985 6169
 to provide a function that can be called to retrieve children of a given widget.
@@ -6116,6 +6300,10 @@ else if(className == "QLineEdit")
6116 6300
 {
6117 6301
     setQLineEditProperties(value, engine);
6118 6302
 }
6303
+else if(className == "QSvgWidget")
6304
+{
6305
+    setQSvgWidgetProperties(value, engine);
6306
+}
6119 6307
 
6120 6308
 @ In the list of classes, the SaltTable entry is for a class which does not
6121 6309
 strictly exist on its own. It is, however, useful to provide some custom
@@ -12437,15 +12625,15 @@ text can be replaced with translated text based on the user'@q'@>s locale. For m
12437 12625
 details, see the Qt Linguist manual.
12438 12626
 
12439 12627
 @<Load translation objects@>=
12440
-QTranslator base;
12441
-if(base.load(QString("qt_%1").arg(QLocale::system().name())))
12628
+QTranslator *base = new QTranslator;
12629
+if(base->load(QString("qt_%1").arg(QLocale::system().name()), QString("%1/Translations").arg(QCoreApplication::applicationDirPath())))
12442 12630
 {
12443
-    installTranslator(&base);
12631
+    installTranslator(base);
12444 12632
 }
12445
-QTranslator app;
12446
-if(app.load(QString("%1_%2").arg("Typica").arg(QLocale::system().name())))
12633
+QTranslator *app = new QTranslator;
12634
+if(app->load(QString("%1_%2").arg("Typica").arg(QLocale::system().name()), QString("%1/Translations").arg(QCoreApplication::applicationDirPath())))
12447 12635
 {
12448
-    installTranslator(&app);
12636
+    installTranslator(app);
12449 12637
 }
12450 12638
 
12451 12639
 @ We also want to be able to access the application instance from within the
@@ -13286,6 +13474,7 @@ class SqlConnectionSetup : public QDialog@/
13286 13474
         QFormLayout *formLayout;
13287 13475
         QComboBox *driver;
13288 13476
         QLineEdit *hostname;
13477
+        QLineEdit *portnumber;
13289 13478
         QLineEdit *dbname;
13290 13479
         QLineEdit *user;
13291 13480
         QLineEdit *password;
@@ -13300,6 +13489,7 @@ class SqlConnectionSetup : public QDialog@/
13300 13489
 @<SqlConnectionSetup implementation@>=
13301 13490
 SqlConnectionSetup::SqlConnectionSetup() :
13302 13491
     formLayout(new QFormLayout), driver(new QComboBox), hostname(new QLineEdit),
13492
+    portnumber(new QLineEdit),
13303 13493
     dbname(new QLineEdit), user(new QLineEdit), password(new QLineEdit),
13304 13494
     layout(new QVBoxLayout), buttons(new QHBoxLayout),
13305 13495
     cancelButton(new QPushButton(tr("Cancel"))),
@@ -13308,6 +13498,8 @@ SqlConnectionSetup::SqlConnectionSetup() :
13308 13498
     driver->addItem("PostgreSQL", "QPSQL");
13309 13499
     formLayout->addRow(tr("Database driver:"), driver);
13310 13500
     formLayout->addRow(tr("Host name:"), hostname);
13501
+    formLayout->addRow(tr("Port number:"), portnumber);
13502
+    portnumber->setText("5432");
13311 13503
     formLayout->addRow(tr("Database name:"), dbname);
13312 13504
     formLayout->addRow(tr("User name:"), user);
13313 13505
     password->setEchoMode(QLineEdit::Password);
@@ -13319,6 +13511,7 @@ SqlConnectionSetup::SqlConnectionSetup() :
13319 13511
     buttons->addWidget(connectButton);
13320 13512
     layout->addLayout(buttons);
13321 13513
     connect(connectButton, SIGNAL(clicked(bool)), this, SLOT(testConnection()));
13514
+    connectButton->setDefault(true);
13322 13515
     setLayout(layout);
13323 13516
     setModal(true);
13324 13517
 }
@@ -13339,6 +13532,7 @@ void SqlConnectionSetup::testConnection()
13339 13532
                                   toString());
13340 13533
     database.setConnectOptions("application_name=Typica");
13341 13534
     database.setHostName(hostname->text());
13535
+    database.setPort(portnumber->text().toInt());
13342 13536
     database.setDatabaseName(dbname->text());
13343 13537
     database.setUserName(user->text());
13344 13538
     database.setPassword(password->text());
@@ -13349,6 +13543,7 @@ void SqlConnectionSetup::testConnection()
13349 13543
         settings.setValue("database/driver",
13350 13544
                           driver->itemData(driver->currentIndex()).toString());
13351 13545
         settings.setValue("database/hostname", hostname->text());
13546
+        settings.setValue("database/portnumber", portnumber->text());
13352 13547
         settings.setValue("database/dbname", dbname->text());
13353 13548
         settings.setValue("database/user", user->text());
13354 13549
         settings.setValue("database/password", password->text());
@@ -13381,6 +13576,7 @@ QSqlDatabase database =
13381 13576
     QSqlDatabase::addDatabase(settings.value("database/driver").toString());
13382 13577
 database.setConnectOptions("application_name=Typica");
13383 13578
 database.setHostName(settings.value("database/hostname").toString());
13579
+database.setPort(settings.value("database/portnumber", 5432).toInt());
13384 13580
 database.setDatabaseName(settings.value("database/dbname").toString());
13385 13581
 database.setUserName(settings.value("database/user").toString());
13386 13582
 database.setPassword(settings.value("database/password").toString());
@@ -13728,11 +13924,13 @@ if(file.open(QIODevice::ReadOnly))
13728 13924
     QDomDocument document;
13729 13925
     document.setContent(&file, true);
13730 13926
     QDomElement root = document.documentElement();
13927
+    QString translationContext = root.attribute("id");
13731 13928
     QDomNode titleNode = root.elementsByTagName("reporttitle").at(0);
13732 13929
     if(!titleNode.isNull())
13733 13930
     {
13734 13931
         QDomElement titleElement = titleNode.toElement();
13735
-        QString title = titleElement.text();
13932
+        QString title = QCoreApplication::translate("configuration",
13933
+                                                    titleElement.text().toUtf8().data());
13736 13934
         if(!title.isEmpty())
13737 13935
         {
13738 13936
             QStringList hierarchy = title.split(":->");
@@ -15993,6 +16191,9 @@ class RoasterConfWidget : public BasicDeviceConfigurationWidget
15993 16191
                                             const QModelIndex &index);
15994 16192
     @[private slots@]:@/
15995 16193
         void updateRoasterId(int id);
16194
+        void updateCapacityCheck(int value);
16195
+        void updateCapacity(const QString &value);
16196
+        void updateCapacityUnit(const QString &value);
15996 16197
 };
15997 16198
 
15998 16199
 @ Aside from the ID number used to identify the roaster in the database we also
@@ -16074,7 +16275,26 @@ RoasterConfWidget::RoasterConfWidget(DeviceTreeModel *model, const QModelIndex &
16074 16275
     idLayout->addWidget(idLabel);
16075 16276
     QSpinBox *id = new QSpinBox;
16076 16277
     idLayout->addWidget(id);
16278
+    idLayout->addStretch();
16077 16279
     layout->addLayout(idLayout);
16280
+    QHBoxLayout *capacityLayout = new QHBoxLayout;
16281
+    QCheckBox *capacityCheckEnabled = new QCheckBox(tr("Maximum batch size:"));
16282
+    QDoubleSpinBox *capacity = new QDoubleSpinBox;
16283
+    capacity->setMinimum(0.0);
16284
+    capacity->setDecimals(3);
16285
+    capacity->setMaximum(999999.999);
16286
+    QComboBox *capacityUnit = new QComboBox;
16287
+    capacityUnit->addItem("g");
16288
+    capacityUnit->addItem("Kg");
16289
+    capacityUnit->addItem("oz");
16290
+    capacityUnit->addItem("Lb");
16291
+    capacityUnit->setCurrentIndex(3);
16292
+    capacityLayout->addWidget(capacityCheckEnabled);
16293
+    capacityLayout->addWidget(capacity);
16294
+    capacityLayout->addWidget(capacityUnit);
16295
+    capacityLayout->addStretch();
16296
+    layout->addLayout(capacityLayout);
16297
+    layout->addStretch();
16078 16298
     @<Get device configuration data for current node@>@;
16079 16299
     for(int i = 0; i < configData.size(); i++)
16080 16300
     {
@@ -16082,11 +16302,25 @@ RoasterConfWidget::RoasterConfWidget(DeviceTreeModel *model, const QModelIndex &
16082 16302
         if(node.attribute("name") == "databaseid")
16083 16303
         {
16084 16304
             id->setValue(node.attribute("value").toInt());
16085
-            break;
16305
+        }
16306
+        else if(node.attribute("name") == "checkcapacity")
16307
+        {
16308
+            capacityCheckEnabled->setChecked(node.attribute("value") == "true");
16309
+        }
16310
+        else if(node.attribute("name") == "capacity")
16311
+        {
16312
+            capacity->setValue(node.attribute("value").toDouble());
16313
+        }
16314
+        else if(node.attribute("name") == "capacityunit")
16315
+        {
16316
+            capacityUnit->setCurrentIndex(capacityUnit->findText(node.attribute("value")));
16086 16317
         }
16087 16318
     }
16088 16319
     updateRoasterId(id->value());
16089 16320
     connect(id, SIGNAL(valueChanged(int)), this, SLOT(updateRoasterId(int)));
16321
+    connect(capacityCheckEnabled, SIGNAL(stateChanged(int)), this, SLOT(updateCapacityCheck(int)));
16322
+    connect(capacity, SIGNAL(valueChanged(QString)), this, SLOT(updateCapacity(QString)));
16323
+    connect(capacityUnit, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateCapacityUnit(QString)));
16090 16324
     setLayout(layout);
16091 16325
 }
16092 16326
 
@@ -16103,7 +16337,7 @@ QDomElement referenceElement =
16103 16337
 QDomNodeList configData = referenceElement.elementsByTagName("attribute");
16104 16338
 QDomElement node;
16105 16339
 
16106
-@ We need to propagate changes to the ID number field to the device
16340
+@ We need to propagate changes to the configuration fields to the device
16107 16341
 configuration document. The |updateAttribute()| method in the base class
16108 16342
 makes this trivial.
16109 16343
 
@@ -16113,6 +16347,21 @@ void RoasterConfWidget::updateRoasterId(int id)
16113 16347
     updateAttribute("databaseid", QString("%1").arg(id));
16114 16348
 }
16115 16349
 
16350
+void RoasterConfWidget::updateCapacityCheck(int value)
16351
+{
16352
+    updateAttribute("checkcapacity", value == Qt::Checked ? "true" : "false");
16353
+}
16354
+
16355
+void RoasterConfWidget::updateCapacity(const QString &value)
16356
+{
16357
+    updateAttribute("capacity", value);
16358
+}
16359
+
16360
+void RoasterConfWidget::updateCapacityUnit(const QString &value)
16361
+{
16362
+    updateAttribute("capacityunit", value);
16363
+}
16364
+
16116 16365
 @ Finally we must register the configuration widget so that it can be
16117 16366
 instantiated at the appropriate time.
16118 16367
 

+ 6
- 6
src/units.cpp View File

@@ -1,10 +1,10 @@
1
-/*269:*/
1
+/*279:*/
2 2
 #line 42 "./units.w"
3 3
 
4 4
 #include "units.h"
5 5
 #include <QtDebug> 
6 6
 
7
-/*:269*//*270:*/
7
+/*:279*//*280:*/
8 8
 #line 53 "./units.w"
9 9
 
10 10
 bool Units::isTemperatureUnit(Unit unit)
@@ -15,7 +15,7 @@ unit==Kelvin||
15 15
 unit==Rankine);
16 16
 }
17 17
 
18
-/*:270*//*271:*/
18
+/*:280*//*281:*/
19 19
 #line 71 "./units.w"
20 20
 
21 21
 double Units::convertTemperature(double value,Unit fromUnit,Unit toUnit)
@@ -113,7 +113,7 @@ break;
113 113
 return 0;
114 114
 }
115 115
 
116
-/*:271*//*272:*/
116
+/*:281*//*282:*/
117 117
 #line 169 "./units.w"
118 118
 
119 119
 double Units::convertRelativeTemperature(double value,Unit fromUnit,Unit toUnit)
@@ -211,7 +211,7 @@ break;
211 211
 return 0;
212 212
 }
213 213
 
214
-/*:272*//*273:*/
214
+/*:282*//*283:*/
215 215
 #line 267 "./units.w"
216 216
 
217 217
 double Units::convertWeight(double value,Unit fromUnit,Unit toUnit)
@@ -316,4 +316,4 @@ unit==Ounce||
316 316
 unit==Gram);
317 317
 }
318 318
 
319
-/*:273*/
319
+/*:283*/

+ 2
- 2
src/units.h View File

@@ -1,4 +1,4 @@
1
-/*268:*/
1
+/*278:*/
2 2
 #line 8 "./units.w"
3 3
 
4 4
 #include <QObject> 
@@ -32,4 +32,4 @@ static bool isWeightUnit(Unit unit);
32 32
 
33 33
 #endif
34 34
 
35
-/*:268*/
35
+/*:278*/

+ 5
- 5
src/webelement.cpp View File

@@ -1,9 +1,9 @@
1
-/*563:*/
1
+/*573:*/
2 2
 #line 368 "./webview.w"
3 3
 
4 4
 #include "webelement.h"
5 5
 
6
-/*561:*/
6
+/*571:*/
7 7
 #line 311 "./webview.w"
8 8
 
9 9
 TypicaWebElement::TypicaWebElement(QWebElement element):e(element)
@@ -11,7 +11,7 @@ TypicaWebElement::TypicaWebElement(QWebElement element):e(element)
11 11
 
12 12
 }
13 13
 
14
-/*:561*//*562:*/
14
+/*:571*//*572:*/
15 15
 #line 320 "./webview.w"
16 16
 
17 17
 void TypicaWebElement::appendInside(const QString&markup)
@@ -59,8 +59,8 @@ void TypicaWebElement::setPlainText(const QString&text)
59 59
 e.setPlainText(text);
60 60
 }
61 61
 
62
-/*:562*/
62
+/*:572*/
63 63
 #line 371 "./webview.w"
64 64
 
65 65
 
66
-/*:563*/
66
+/*:573*/

+ 2
- 2
src/webelement.h View File

@@ -1,4 +1,4 @@
1
-/*556:*/
1
+/*566:*/
2 2
 #line 248 "./webview.w"
3 3
 
4 4
 #include <QWebElement> 
@@ -27,4 +27,4 @@ QWebElement e;
27 27
 
28 28
 #endif
29 29
 
30
-/*:556*/
30
+/*:566*/

+ 11
- 11
src/webview.cpp View File

@@ -1,9 +1,9 @@
1
-/*541:*/
1
+/*551:*/
2 2
 #line 50 "./webview.w"
3 3
 
4 4
 #include "webview.h"
5 5
 
6
-/*542:*/
6
+/*552:*/
7 7
 #line 57 "./webview.w"
8 8
 
9 9
 TypicaWebView::TypicaWebView():QWebView()
@@ -12,7 +12,7 @@ page()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks);
12 12
 connect(page(),SIGNAL(linkClicked(QUrl)),this,SLOT(linkDelegate(QUrl)));
13 13
 }
14 14
 
15
-/*:542*//*543:*/
15
+/*:552*//*553:*/
16 16
 #line 73 "./webview.w"
17 17
 
18 18
 void TypicaWebView::linkDelegate(const QUrl&url)
@@ -20,7 +20,7 @@ void TypicaWebView::linkDelegate(const QUrl&url)
20 20
 if(url.scheme()=="typica")
21 21
 {
22 22
 QString address(url.toEncoded());
23
-/*544:*/
23
+/*554:*/
24 24
 #line 91 "./webview.w"
25 25
 
26 26
 if(address=="typica://aboutqt")
@@ -29,10 +29,10 @@ QMessageBox::aboutQt(this);
29 29
 return;
30 30
 }
31 31
 
32
-/*:544*/
32
+/*:554*/
33 33
 #line 79 "./webview.w"
34 34
 
35
-/*545:*/
35
+/*555:*/
36 36
 #line 100 "./webview.w"
37 37
 
38 38
 if(address.startsWith("typica://script/"))
@@ -41,7 +41,7 @@ emit scriptLinkClicked(address.remove(0,16));
41 41
 return;
42 42
 }
43 43
 
44
-/*:545*/
44
+/*:555*/
45 45
 #line 80 "./webview.w"
46 46
 
47 47
 }
@@ -51,7 +51,7 @@ QDesktopServices::openUrl(url);
51 51
 }
52 52
 }
53 53
 
54
-/*:543*//*546:*/
54
+/*:553*//*556:*/
55 55
 #line 112 "./webview.w"
56 56
 
57 57
 void TypicaWebView::load(const QString&url)
@@ -88,7 +88,7 @@ QString TypicaWebView::saveXml()
88 88
 return page()->currentFrame()->documentElement().toOuterXml();
89 89
 }
90 90
 
91
-/*:546*//*552:*/
91
+/*:556*//*562:*/
92 92
 #line 205 "./webview.w"
93 93
 
94 94
 QWebElement TypicaWebView::documentElement()
@@ -101,8 +101,8 @@ QWebElement TypicaWebView::findFirstElement(const QString&selector)
101 101
 return page()->mainFrame()->findFirstElement(selector);
102 102
 }
103 103
 
104
-/*:552*/
104
+/*:562*/
105 105
 #line 53 "./webview.w"
106 106
 
107 107
 
108
-/*:541*/
108
+/*:551*/

+ 2
- 2
src/webview.h View File

@@ -1,4 +1,4 @@
1
-/*540:*/
1
+/*550:*/
2 2
 #line 14 "./webview.w"
3 3
 
4 4
 #include <QWebView> 
@@ -34,4 +34,4 @@ void linkDelegate(const QUrl&url);
34 34
 
35 35
 #endif
36 36
 
37
-/*:540*/
37
+/*:550*/

+ 11
- 31
web/output/download-mac-latest.html View File

@@ -16,26 +16,10 @@
16 16
 	<div id="maintext">
17 17
 <h1>Thank you for downloading Typica.</h1>
18 18
 <p>Your download from:</p>
19
-<p><a href="http://www.randomfield.com/programs/typica/1.6/Typica-1.6.4.dmg">http://www.randomfield.com/programs/typica/1.6/Typica-1.6.4.dmg</a></p>
19
+<p><a href="http://www.randomfield.com/programs/typica/1.7/Typica-1.7.dmg">http://www.randomfield.com/programs/typica/1.7/Typica-1.7.dmg</a></p>
20 20
 <p>should begin automatically. If it does not,
21 21
 please click the above link.</p>
22 22
 <h2>Please Help Support Ongoing Development</h2>
23
-<p>This will likely be one of the last releases in the 1.x
24
-series. Typica 1.0 was released in 2007 and I've learned a
25
-lot from developing this, using it, and having conversations
26
-with others about how they're using this. While there is the
27
-possibility of further improvements, I believe Typica has
28
-reached a point of maturity with a feature set appropriate for
29
-many coffee roasting firms.</p>
30
-<p>There is a large set of features and extensions that I have
31
-avoided implementing because the changes would have been too
32
-intrusive for the code base as it currently exists, but that
33
-feature set has become too compelling for me to leave undone.
34
-I am, therefore, attempting to spend as much time as I can on
35
-the development of Typica 2.0. This removes many of the legacy
36
-considerations and modernizes the tools and techniques used. I
37
-hope that the result is a better and more broadly useful Typica
38
-that can be maintained for another several years.</p>
39 23
 <p>While Typica is free and will continue to be made freely
40 24
 available complete with all source code, there are still costs
41 25
 associated with ongoing development and keeping the software
@@ -51,27 +35,23 @@ available. Some of the costs include:
51 35
 	</ul>
52 36
 There is also a huge commitment of time in developing, testing,
53 37
 and supporting this.</p>
54
-<p>Financial support to date does not come close to covering
55
-these costs, nor has it been enough that I could consider
56
-hiring people to improve the pace and quality of ongoing
57
-development. This is fine. I have no intention of halting work
58
-on the project as my work has benefitted greatly from having
59
-this software and I expect even greater benefit from the next
60
-major release. Personally I find the new feature set exciting
61
-to work on and am willing to cover funding shortfalls when I
62
-can afford to do so.</p>
63
-<p>That said, if you're finding this software to be useful in
64
-your business, if you've had a good support experience, or if
38
+<p>While Typica is for many uses a mature and reliable program,
39
+there are still a number of areas where the program can be
40
+improved and additional areas where I think it makes a lot of
41
+sense to extend the program. The limited funding for this work
42
+does slow progress. If you're finding this software to be useful
43
+in your business, if you've had a good support experience, or if
65 44
 you'd like to help me continue to develop this software, please
66
-consider providing some financial support to:</p>
45
+consider providing some financial support. Checks can be sent to:
67 46
 <p>Neal Wilson<br />
68 47
 c/o Wilson's Coffee &amp; Tea<br />
69 48
 3306 Washington Ave.<br />
70 49
 Racine, WI 53405<br />
71 50
 USA</p>
51
+<p>or you can visit my <a href="http://youtube.com/users/N3Roaster">YouTube
52
+channel</a> and click the blue Support button on the right.</p>
72 53
 <p>Another great way to help Typica right now is to reach out
73 54
 to your professional colleagues and let them know that you're
74 55
 using Typica and what you like about it.</p>
75
-<p>Please note that I am unable to accept money through PayPal.</p>
76
-<iframe width="1" height="1" frameborder="0" src="http://www.randomfield.com/programs/typica/1.6/Typica-1.6.4.dmg"></iframe>
56
+<iframe width="1" height="1" frameborder="0" src="http://www.randomfield.com/programs/typica/1.7/Typica-1.7.dmg"></iframe>
77 57
 </div></div></body></html>

+ 11
- 31
web/output/download-windows-latest.html View File

@@ -16,26 +16,10 @@
16 16
 	<div id="maintext">
17 17
 <h1>Thank you for downloading Typica.</h1>
18 18
 <p>Your download from:</p>
19
-<p><a href="http://www.randomfield.com/programs/typica/1.6/Typica-1.6.4.zip">http://www.randomfield.com/programs/typica/1.6/Typica-1.6.4.zip</a></p>
19
+<p><a href="http://www.randomfield.com/programs/typica/1.7/Typica-1.7.zip">http://www.randomfield.com/programs/typica/1.7/Typica-1.7.zip</a></p>
20 20
 <p>should begin automatically. If it does not,
21 21
 please click the above link.</p>
22 22
 <h2>Please Help Support Ongoing Development</h2>
23
-<p>This will likely be one of the last releases in the 1.x
24
-series. Typica 1.0 was released in 2007 and I've learned a
25
-lot from developing this, using it, and having conversations
26
-with others about how they're using this. While there is the
27
-possibility of further improvements, I believe Typica has
28
-reached a point of maturity with a feature set appropriate for
29
-many coffee roasting firms.</p>
30
-<p>There is a large set of features and extensions that I have
31
-avoided implementing because the changes would have been too
32
-intrusive for the code base as it currently exists, but that
33
-feature set has become too compelling for me to leave undone.
34
-I am, therefore, attempting to spend as much time as I can on
35
-the development of Typica 2.0. This removes many of the legacy
36
-considerations and modernizes the tools and techniques used. I
37
-hope that the result is a better and more broadly useful Typica
38
-that can be maintained for another several years.</p>
39 23
 <p>While Typica is free and will continue to be made freely
40 24
 available complete with all source code, there are still costs
41 25
 associated with ongoing development and keeping the software
@@ -51,27 +35,23 @@ available. Some of the costs include:
51 35
 	</ul>
52 36
 There is also a huge commitment of time in developing, testing,
53 37
 and supporting this.</p>
54
-<p>Financial support to date does not come close to covering
55
-these costs, nor has it been enough that I could consider
56
-hiring people to improve the pace and quality of ongoing
57
-development. This is fine. I have no intention of halting work
58
-on the project as my work has benefitted greatly from having
59
-this software and I expect even greater benefit from the next
60
-major release. Personally I find the new feature set exciting
61
-to work on and am willing to cover funding shortfalls when I
62
-can afford to do so.</p>
63
-<p>That said, if you're finding this software to be useful in
64
-your business, if you've had a good support experience, or if
38
+<p>While Typica is for many uses a mature and reliable program,
39
+there are still a number of areas where the program can be
40
+improved and additional areas where I think it makes a lot of
41
+sense to extend the program. The limited funding for this work
42
+does slow progress. If you're finding this software to be useful
43
+in your business, if you've had a good support experience, or if
65 44
 you'd like to help me continue to develop this software, please
66
-consider providing some financial support to:</p>
45
+consider providing some financial support. Checks can be sent to:
67 46
 <p>Neal Wilson<br />
68 47
 c/o Wilson's Coffee &amp; Tea<br />
69 48
 3306 Washington Ave.<br />
70 49
 Racine, WI 53405<br />
71 50
 USA</p>
51
+<p>or you can visit my <a href="http://youtube.com/users/N3Roaster">YouTube
52
+channel</a> and click the blue Support button on the right.</p>
72 53
 <p>Another great way to help Typica right now is to reach out
73 54
 to your professional colleagues and let them know that you're
74 55
 using Typica and what you like about it.</p>
75
-<p>Please note that I am unable to accept money through PayPal.</p>
76
-<iframe width="1" height="1" frameborder="0" src="http://www.randomfield.com/programs/typica/1.6/Typica-1.6.4.zip"></iframe>
56
+<iframe width="1" height="1" frameborder="0" src="http://www.randomfield.com/programs/typica/1.7/Typica-1.7.zip"></iframe>
77 57
 </div></div></body></html>

+ 3
- 3
web/output/downloads.html View File

@@ -14,10 +14,10 @@
14 14
 	</div>
15 15
 	</div>
16 16
 	<div id="maintext">
17
-<p>The latest release of Typica is version 1.6.4, released June 7, 2015.
17
+<p>The latest release of Typica is version 1.7, released January 24, 2016.
18 18
 This is available for Microsoft Windows, and Intel/Mac OS X.</p>
19
-<p><a href="download-mac-latest.html">Typica 1.6.4 for Intel/Mac OS X</a></p>
20
-<p><a href="download-windows-latest.html">Typica 1.6.4 for Microsoft Windows</a></p>
19
+<p><a href="download-mac-latest.html">Typica 1.7 for Intel/Mac OS X</a></p>
20
+<p><a href="download-windows-latest.html">Typica 1.7 for Microsoft Windows</a></p>
21 21
 <p>The latest source code can always be found on
22 22
 <a href="https://github.com/N3Roaster/typica">GitHub</a>.</p>
23 23
 </div></div></body></html>

+ 8
- 13
web/output/index.html View File

@@ -1,11 +1,6 @@
1 1
 <html><head>
2 2
 	<title>Typica - Data for Coffee Roasters</title>
3 3
 	<link rel="stylesheet" type="text/css" href="style.css">
4
-<meta name="twitter:card" content="summary_large_image">
5
-<meta name="twitter:site" content="@N3Roaster">
6
-<meta name="twitter:title" content="Typica 1.6.4 Now Available. Free Software for Coffee Roasters">
7
-<meta name="twitter:description" content="Typica is a cross-platform application for coffee roasters with features for recording your roasts, tracking green coffee inventory, product development, improving production consistency, and more.">
8
-<meta name="twitter:image" content="http://www.randomfield.com/programs/typica/release164tc.png">
9 4
 	</head><body><div id="page"><div id="topmatter">
10 5
 	<div id="topbanner">
11 6
 	<img src="logo96.png" height="96px" width="96px" alt="Typica logo" />
@@ -25,14 +20,14 @@ inventory, product development, improving production consistency,
25 20
 and more.</p>
26 21
 <p>The program is free open source software available under the
27 22
 MIT license.</p>
28
-<h1>Typica 1.6.4 Now Available</h1>
29
-<p>The latest release of Typica includes bug fixes and minor
30
-feature updates. The most significant new feature is the
31
-ability to use three new types of timer in addition to the
32
-familiar batch timer. See the documentation for details on how to
33
-configure these or watch this new video describing each of the
34
-new timer types and why you might want to use them.</p>
35
-<iframe width="640" height="360" src="https://www.youtube.com/embed/2zt9XlQd9oY" frameborder="0" allowfullscreen></iframe>
23
+<h1>Typica 1.7 Now Available</h1>
24
+<iframe width="640" height="360" src="https://www.youtube.com/embed/U7xTefVLRfk" frameborder="0" allowfullscreen></iframe>
25
+<p>The latest release of Typica includes several new features
26
+such as support for more detailed roast specifications, ability
27
+to set reminders based on production, a new batch log with search
28
+and filter features, and more. This also marks the first release
29
+that includes a translation of the program to another language.
30
+People using a computer with German locale settings will see that.</p>
36 31
 <h1>Data Acquisition</h1>
37 32
 <p>Every batch of coffee you roast with Typica can be recorded for
38 33
 future reference. A practically unlimited number of target roast

+ 0
- 0
web/src/pages/download-mac-latest.m4 View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save