Browse Source

Merge branch 'development' into release-1.6.1

Neal Wilson 11 years ago
parent
commit
d07854f7e3

+ 35
- 44
config/Reports/auco.xml View File

65
                 output.writeStartElement("thead");
65
                 output.writeStartElement("thead");
66
                 output.writeStartElement("tr");
66
                 output.writeStartElement("tr");
67
                 output.writeStartElement("th");
67
                 output.writeStartElement("th");
68
-                output.writeAttribute("colspan", "3");
68
+                output.writeAttribute("colspan", "5");
69
                 output.writeCharacters("Regular Coffees");
69
                 output.writeCharacters("Regular Coffees");
70
                 output.writeEndElement();
70
                 output.writeEndElement();
71
                 output.writeEndElement();
71
                 output.writeEndElement();
73
                 output.writeTextElement("th", "Origin");
73
                 output.writeTextElement("th", "Origin");
74
                 output.writeTextElement("th", "Avg. Rate");
74
                 output.writeTextElement("th", "Avg. Rate");
75
                 output.writeTextElement("th", "Avg. Cost");
75
                 output.writeTextElement("th", "Avg. Cost");
76
+				output.writeTextElement("th", "Last Cost");
77
+				output.writeTextElement("th", "Last Purchase Date");
76
                 output.writeEndElement();
78
                 output.writeEndElement();
77
                 output.writeEndElement();
79
                 output.writeEndElement();
78
                 output.writeStartElement("tbody");
80
                 output.writeStartElement("tbody");
81
 				if(unitBox.currentIndex == 0) {
83
 				if(unitBox.currentIndex == 0) {
82
 					conversion = 2.2;
84
 					conversion = 2.2;
83
 				}
85
 				}
84
-                switch(sortBox.currentIndex)
85
-                {
86
-                    case 0:
87
-						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 FROM coffee_history WHERE id IN (SELECT id FROM regular_coffees) GROUP BY origin ORDER BY origin ASC");
88
-                        break;
89
-                    case 1:
90
-						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 FROM coffee_history WHERE id IN (SELECT id FROM regular_coffees) GROUP BY origin ORDER BY origin DESC");
91
-                        break;
92
-                    case 2:
93
-						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 FROM coffee_history WHERE id IN (SELECT id FROM regular_coffees) GROUP BY origin ORDER BY rate ASC");
94
-                        break;
95
-                    case 3:
96
-						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 FROM coffee_history WHERE id IN (SELECT id FROM regular_coffees) GROUP BY origin ORDER BY rate DESC");
97
-                        break;
98
-                    case 4:
99
-						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 FROM coffee_history WHERE id IN (SELECT id FROM regular_coffees) GROUP BY origin ORDER BY cost ASC");
100
-                        break;
101
-                    case 5:
102
-						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 FROM coffee_history WHERE id IN (SELECT id FROM regular_coffees) GROUP BY origin ORDER BY cost DESC");
103
-                        break;
104
-                }
86
+				var orderClause;
87
+				switch(sortBox.currentIndex)
88
+				{
89
+					case 0:
90
+						orderClause = "origin ASC";
91
+						break;
92
+					case 1:
93
+						orderClause = "origin DESC";
94
+						break;
95
+					case 2:
96
+						orderClause = "rate ASC";
97
+						break;
98
+					case 3:
99
+						orderClause = "rate DESC";
100
+						break;
101
+					case 4:
102
+						orderClause = "cost ASC";
103
+						break;
104
+					case 5:
105
+						orderClause = "cost DESC";
106
+						break;
107
+				}
108
+				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)) FROM coffee_history WHERE id IN (SELECT id FROM regular_coffees) GROUP BY origin ORDER BY " + orderClause);
105
 				query.bind(":conversion", conversion);
109
 				query.bind(":conversion", conversion);
106
 				query.bind(":conversion2", conversion);
110
 				query.bind(":conversion2", conversion);
111
+				query.bind(":conversion3", conversion);
107
 				query.exec();
112
 				query.exec();
108
                 while(query.next())
113
                 while(query.next())
109
                 {
114
                 {
111
                     output.writeTextElement("td", query.value(0));
116
                     output.writeTextElement("td", query.value(0));
112
                     output.writeTextElement("td", query.value(1));
117
                     output.writeTextElement("td", query.value(1));
113
                     output.writeTextElement("td", query.value(2));
118
                     output.writeTextElement("td", query.value(2));
119
+					output.writeTextElement("td", query.value(3));
120
+					output.writeTextElement("td", query.value(4));
114
                     output.writeEndElement();
121
                     output.writeEndElement();
115
                 }
122
                 }
116
                 output.writeStartElement("tr");
123
                 output.writeStartElement("tr");
117
                 output.writeStartElement("th");
124
                 output.writeStartElement("th");
118
-                output.writeAttribute("colspan", "3");
125
+                output.writeAttribute("colspan", "5");
119
                 output.writeCharacters("Decaffeinated Coffees");
126
                 output.writeCharacters("Decaffeinated Coffees");
120
                 output.writeEndElement();
127
                 output.writeEndElement();
121
                 output.writeEndElement();
128
                 output.writeEndElement();
122
-                switch(sortBox.currentIndex)
123
-                {
124
-                    case 0:
125
-						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 FROM coffee_history WHERE id IN (SELECT id FROM decaf_coffees) GROUP BY origin ORDER BY origin ASC");
126
-                        break;
127
-                    case 1:
128
-						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 FROM coffee_history WHERE id IN (SELECT id FROM decaf_coffees) GROUP BY origin ORDER BY origin DESC");
129
-                        break;
130
-                    case 2:
131
-						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 FROM coffee_history WHERE id IN (SELECT id FROM decaf_coffees) GROUP BY origin ORDER BY rate ASC");
132
-                        break;
133
-                    case 3:
134
-						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 FROM coffee_history WHERE id IN (SELECT id FROM decaf_coffees) GROUP BY origin ORDER BY rate DESC");
135
-                        break;
136
-                    case 4:
137
-						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 FROM coffee_history WHERE id IN (SELECT id FROM decaf_coffees) GROUP BY origin ORDER BY cost ASC");
138
-                        break;
139
-                    case 5:
140
-						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 FROM coffee_history WHERE id IN (SELECT id FROM decaf_coffees) GROUP BY origin ORDER BY cost DESC");
141
-                        break;
142
-                }
129
+				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)) FROM coffee_history WHERE id IN (SELECT id FROM decaf_coffees) GROUP BY origin ORDER BY " + orderClause);
143
 				query.bind(":conversion", conversion);
130
 				query.bind(":conversion", conversion);
144
 				query.bind(":conversion2", conversion);
131
 				query.bind(":conversion2", conversion);
132
+				query.bind(":conversion3", conversion);
145
 				query.exec();
133
 				query.exec();
146
                 while(query.next())
134
                 while(query.next())
147
                 {
135
                 {
149
                     output.writeTextElement("td", query.value(0));
137
                     output.writeTextElement("td", query.value(0));
150
                     output.writeTextElement("td", query.value(1));
138
                     output.writeTextElement("td", query.value(1));
151
                     output.writeTextElement("td", query.value(2));
139
                     output.writeTextElement("td", query.value(2));
140
+					output.writeTextElement("td", query.value(3));
141
+					output.writeTextElement("td", query.value(4));
152
                     output.writeEndElement();
142
                     output.writeEndElement();
153
                 }
143
                 }
144
+				query = query.invalidate();
154
                 output.writeEndElement();
145
                 output.writeEndElement();
155
                 output.writeEndElement();
146
                 output.writeEndElement();
156
                 output.writeEndElement();
147
                 output.writeEndElement();

+ 1
- 1
config/Reports/chart.xml View File

144
                 output.writeAttribute("y2", "450");
144
                 output.writeAttribute("y2", "450");
145
                 output.writeAttribute("style", "stroke:rgb(0,0,0);stroke-width:1;");
145
                 output.writeAttribute("style", "stroke:rgb(0,0,0);stroke-width:1;");
146
                 output.writeEndElement();
146
                 output.writeEndElement();
147
-                var query = new QSqlQuery();
148
                 var q = "SELECT "+curEndDate+"::date - "+curStartDate+"::date";
147
                 var q = "SELECT "+curEndDate+"::date - "+curStartDate+"::date";
149
                 query.exec(q);
148
                 query.exec(q);
150
                 query.next();
149
                 query.next();
425
                         i = days - 1;
424
                         i = days - 1;
426
                     }
425
                     }
427
                 }
426
                 }
427
+				query = query.invalidate();
428
                 output.writeStartElement("rect");
428
                 output.writeStartElement("rect");
429
                 output.writeAttribute("fill", "rgb(0,0,0)");
429
                 output.writeAttribute("fill", "rgb(0,0,0)");
430
                 output.writeAttribute("x", "45");
430
                 output.writeAttribute("x", "45");

+ 49
- 31
config/Reports/cogr.xml View File

46
 						recipes.push(query.value(0));
46
 						recipes.push(query.value(0));
47
 						recipeQuantities.push(query.value(1));
47
 						recipeQuantities.push(query.value(1));
48
 					} else {
48
 					} else {
49
-						print("Error 1");
49
+						recipes.push("{-1}");
50
+						recipeQuantities.push("{-1}");
50
 					}
51
 					}
51
 				}
52
 				}
52
 				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");
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 maxes = new Array();
55
 				var maxes = new Array();
55
 				var means = new Array();
56
 				var means = new Array();
56
 				for(var i = 0; i < roastedItems.length; i++) {
57
 				for(var i = 0; i < roastedItems.length; i++) {
57
-					query.bind(":ri", Number(roastedItems[i]));
58
-					query.bind(":gi", recipes[i]);
59
-					query.exec();
60
-					if(query.next()) {
61
-						mins.push(query.value(0));
62
-						maxes.push(query.value(1));
63
-						means.push(query.value(2));
58
+					if(recipes[i] == "{-1}") {
59
+						mins.push("undefined");
60
+						maxes.push("undefined");
61
+						means.push("undefined");
64
 					} else {
62
 					} else {
65
-						print("Error 2");
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
+						}
66
 					}
73
 					}
67
 				}
74
 				}
68
 				var proportionalCosts = new Array();
75
 				var proportionalCosts = new Array();
69
 				query.prepare("SELECT cost * :proportion FROM purchase WHERE item = :id");
76
 				query.prepare("SELECT cost * :proportion FROM purchase WHERE item = :id");
70
 				for(var i = 0; i < roastedItems.length; i++) {
77
 				for(var i = 0; i < roastedItems.length; i++) {
71
-					greens = sqlToArray(recipes[i]);
72
-					weights = sqlToArray(recipeQuantities[i]);
73
-					proportions = new Array();
74
-					quantitySum = weights.reduce(function(p, c) {
75
-						return p + c;
76
-					});
77
-					for(var j = 0; j < weights.length; j++) {
78
-						proportions.push(weights[j]/quantitySum);
79
-					}
80
-					partialSum = 0;
81
-					for(var j = 0; j < greens.length; j++) {
82
-						query.bind(":proportion", proportions[j]);
83
-						query.bind(":id", greens[j]);
84
-						query.exec();
85
-						if(query.next()) {
86
-							partialSum += query.value(0);
87
-						} else {
88
-							print("Error 3");
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
 						}
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);
90
 					}
102
 					}
91
-					proportionalCosts.push(partialSum);
92
 				}
103
 				}
104
+				query = query.invalidate();
93
 				var minCosts = new Array();
105
 				var minCosts = new Array();
94
 				var maxCosts = new Array();
106
 				var maxCosts = new Array();
95
 				var meanCosts = new Array();
107
 				var meanCosts = new Array();
96
 				for(var i = 0; i < roastedItems.length; i++) {
108
 				for(var i = 0; i < roastedItems.length; i++) {
97
-					minCosts.push(proportionalCosts[i] * mins[i]);
98
-					maxCosts.push(proportionalCosts[i] * maxes[i]);
99
-					meanCosts.push(proportionalCosts[i] * means[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
+					}
100
 				}
118
 				}
101
 				output.writeStartElement("table");
119
 				output.writeStartElement("table");
102
 				output.writeAttribute("rules", "groups");
120
 				output.writeAttribute("rules", "groups");

+ 40
- 21
config/Reports/dailyproductiondetail.xml View File

135
 					output.writeEndElement();
135
 					output.writeEndElement();
136
 					var unroastedList = sqlToArray(query.value(4));
136
 					var unroastedList = sqlToArray(query.value(4));
137
 					output.writeStartElement("td");
137
 					output.writeStartElement("td");
138
+					var greensQuery = new QSqlQuery();
139
+					greensQuery.prepare("SELECT name, reference FROM coffees WHERE id = :id");
138
 					for(var i = 0;  i < unroastedList.length; i++)
140
 					for(var i = 0;  i < unroastedList.length; i++)
139
-					{
140
-						var greensQuery = new QSqlQuery();
141
-						greensQuery.prepare("SELECT name, reference FROM coffees WHERE id = :id");
141
+					{	
142
 						greensQuery.bind(":id", Number(unroastedList[i]));
142
 						greensQuery.bind(":id", Number(unroastedList[i]));
143
 						greensQuery.exec();
143
 						greensQuery.exec();
144
 						if(i != 0)
144
 						if(i != 0)
148
 						greensQuery.next();
148
 						greensQuery.next();
149
 						output.writeCDATA(greensQuery.value(0) + " (" + unroastedList[i] + ")" + (greensQuery.value(1) == "" ? "" : " ref: " + greensQuery.value(1)));
149
 						output.writeCDATA(greensQuery.value(0) + " (" + unroastedList[i] + ")" + (greensQuery.value(1) == "" ? "" : " ref: " + greensQuery.value(1)));
150
 					}
150
 					}
151
+					greensQuery = greensQuery.invalidate();
151
 					output.writeEndElement();
152
 					output.writeEndElement();
152
 					switch(unitBox.currentIndex)
153
 					switch(unitBox.currentIndex)
153
 					{
154
 					{
311
 				output.writeEndElement();
312
 				output.writeEndElement();
312
 				output.writeEndElement();
313
 				output.writeEndElement();
313
 				output.writeStartElement("tbody");
314
 				output.writeStartElement("tbody");
314
-				q = "SELECT DISTINCT item, (SELECT name FROM items WHERE id = item) AS name, (SELECT out FROM coffee_history WHERE id = item) FROM all_transactions WHERE time > '" + dateString + "' AND time < '" + dateString + "'::date + integer '1' ORDER BY name ASC";
315
+				q = "SELECT DISTINCT item, (SELECT name FROM items WHERE id = item) AS name, (SELECT out FROM coffee_history WHERE id = item) FROM all_transactions WHERE time >= '" + dateString + "' AND time < '" + dateString + "'::date + integer '1' ORDER BY name ASC";
315
 				query.exec(q)
316
 				query.exec(q)
316
 				var subQuery = new QSqlQuery();
317
 				var subQuery = new QSqlQuery();
317
 				var qq;
318
 				var qq;
318
 				while(query.next())
319
 				while(query.next())
319
 				{
320
 				{
320
-					output.writeStartElement("tr");
321
-					output.writeTextElement("td", query.value(1));
322
-					qq = "SELECT balance FROM item_history(" + query.value(0) + ") WHERE time = (SELECT max(time) FROM all_transactions WHERE time < '" + dateString + "' AND item = " + query.value(0) + ") OR time = (SELECT max(time) FROM all_transactions WHERE time < '" + dateString + "'::date + integer '1' AND item = " + query.value(0) + ") ORDER BY time ASC";
321
+					var validRow = true;
322
+					var td1 = query.value(1);
323
+					var td2 = 0;
324
+					var td3 = 0;
325
+					var td4 = 0;
326
+					var td5 = query.value(2);
327
+					qq = "SELECT balance FROM item_history(" + query.value(0) + ") WHERE time = (SELECT max(time) FROM all_transactions WHERE time <= '" + dateString + "' AND item = " + query.value(0) + ") OR time = (SELECT max(time) FROM all_transactions WHERE time < '" + dateString + "'::date + integer '1' AND item = " + query.value(0) + ") ORDER BY time ASC";
323
 					subQuery.exec(qq);
328
 					subQuery.exec(qq);
324
-					var startValue = 0;
325
-					var endValue = 0;
329
+					var startValue = "0.0";
330
+					var endValue = "0.0";
326
 					if(subQuery.next())
331
 					if(subQuery.next())
327
 					{
332
 					{
328
 						switch(unitBox.currentIndex)
333
 						switch(unitBox.currentIndex)
329
 						{
334
 						{
330
 							case 0:
335
 							case 0:
331
-								output.writeTextElement("td", (subQuery.value(0)/2.2).toFixed(subQuery.value(0).split('.').length > 1 ? subQuery.value(0).split('.')[1].length : 0));
336
+								td2 = (subQuery.value(0)/2.2).toFixed(subQuery.value(0).split('.').length > 1 ? subQuery.value(0).split('.')[1].length : 0);
332
 								break;
337
 								break;
333
 							case 1:
338
 							case 1:
334
-								output.writeTextElement("td", subQuery.value(0));
339
+								td2 = subQuery.value(0);
335
 								break;
340
 								break;
336
 						}
341
 						}
337
 						startValue = subQuery.value(0);
342
 						startValue = subQuery.value(0);
338
 					}
343
 					}
339
 					else
344
 					else
340
 					{
345
 					{
341
-						output.writeEmptyElement("td");
346
+						/* This should never happen. */
347
+						validRow = false;
342
 					}
348
 					}
343
 					if(subQuery.next())
349
 					if(subQuery.next())
344
 					{
350
 					{
345
 						switch(unitBox.currentIndex)
351
 						switch(unitBox.currentIndex)
346
 						{
352
 						{
347
 							case 0:
353
 							case 0:
348
-								output.writeTextElement("td", (subQuery.value(0)/2.2).toFixed(subQuery.value(0).split('.').length > 1 ? subQuery.value(0).split('.')[1].length : 0));
354
+								td3 = (subQuery.value(0)/2.2).toFixed(subQuery.value(0).split('.').length > 1 ? subQuery.value(0).split('.')[1].length : 0);
349
 								break;
355
 								break;
350
 							case 1:
356
 							case 1:
351
-								output.writeTextElement("td", subQuery.value(0));
357
+								td3 = subQuery.value(0);
352
 								break;
358
 								break;
353
 						}
359
 						}
354
 						endValue = subQuery.value(0);
360
 						endValue = subQuery.value(0);
355
 					}
361
 					}
356
 					else
362
 					else
357
 					{
363
 					{
358
-						output.writeEmptyElement("td");
364
+						/* If only one transaction exists for the current query
365
+						   this is a purchase transaction. Until something is
366
+						   done with the coffee, it should not appear in this
367
+						   section of the report. */
368
+						endValue = startValue;
369
+						validRow = false;
359
 					}
370
 					}
360
 					var startPrec = startValue.split('.').length > 1 ? startValue.split('.')[1].length : 0;
371
 					var startPrec = startValue.split('.').length > 1 ? startValue.split('.')[1].length : 0;
361
 					var endPrec = endValue.split('.').length > 1 ? endValue.split('.')[1].length : 0;
372
 					var endPrec = endValue.split('.').length > 1 ? endValue.split('.')[1].length : 0;
362
 					switch(unitBox.currentIndex)
373
 					switch(unitBox.currentIndex)
363
 					{
374
 					{
364
 						case 0:
375
 						case 0:
365
-							output.writeTextElement("td", (Number(endValue/2.2) - Number(startValue/2.2)).toFixed(Math.max(startPrec, endPrec)));
376
+							td4 = (Number(endValue/2.2) - Number(startValue/2.2)).toFixed(Math.max(startPrec, endPrec));
366
 							break;
377
 							break;
367
 						case 1:
378
 						case 1:
368
-							output.writeTextElement("td", (Number(endValue) - Number(startValue)).toFixed(Math.max(startPrec, endPrec)));
379
+							td4 = (Number(endValue) - Number(startValue)).toFixed(Math.max(startPrec, endPrec));
369
 							break;
380
 							break;
370
-					}					
371
-					output.writeTextElement("td", query.value(2));
372
-					output.writeEndElement();
381
+					}
382
+					if(validRow) {
383
+						output.writeStartElement("tr");
384
+						output.writeTextElement("td", td1);
385
+						output.writeTextElement("td", td2);
386
+						output.writeTextElement("td", td3);
387
+						output.writeTextElement("td", td4);
388
+						output.writeTextElement("td", td5);
389
+						output.writeEndElement();
390
+					}
373
 				}
391
 				}
392
+				query = query.invalidate();
393
+				subQuery = subQuery.invalidate();
374
 				output.writeEndElement();
394
 				output.writeEndElement();
375
 				output.writeStartElement("tfoot");
395
 				output.writeStartElement("tfoot");
376
 				
396
 				
377
 				output.writeEndElement();
397
 				output.writeEndElement();
378
 				output.writeEndElement();//End of inventory table
398
 				output.writeEndElement();//End of inventory table
379
 				
399
 				
380
-				
381
 				output.writeEndElement();
400
 				output.writeEndElement();
382
 				output.writeEndElement();
401
 				output.writeEndElement();
383
 				output.writeEndDocument();
402
 				output.writeEndDocument();

+ 59
- 0
config/Reports/fypurchase.xml View File

24
 			query.exec("SELECT EXTRACT(YEAR FROM time) FROM purchase WHERE time = (SELECT min(time) FROM purchase)");
24
 			query.exec("SELECT EXTRACT(YEAR FROM time) FROM purchase WHERE time = (SELECT min(time) FROM purchase)");
25
 			query.next();
25
 			query.next();
26
 			startDateField.setDate(query.value(0), 1, 1);
26
 			startDateField.setDate(query.value(0), 1, 1);
27
+			query = query.invalidate();
27
 			/* Set ending year to the current year. */
28
 			/* Set ending year to the current year. */
28
 			var endDateField = findChildObject(this, 'enddate');
29
 			var endDateField = findChildObject(this, 'enddate');
29
 			endDateField.setDate(endDateField.year(), 12, 31);
30
 			endDateField.setDate(endDateField.year(), 12, 31);
79
 				var sacktotal = 0;
80
 				var sacktotal = 0;
80
 				var unittotal = 0;
81
 				var unittotal = 0;
81
 				var costtotal = 0;
82
 				var costtotal = 0;
83
+				var query = new QSqlQuery();
82
 				for(var i = startDateField.year(); i <= endDateField.year(); i++)
84
 				for(var i = startDateField.year(); i <= endDateField.year(); i++)
83
 				{
85
 				{
84
 					output.writeStartElement("tr");
86
 					output.writeStartElement("tr");
89
 					query.exec();
91
 					query.exec();
90
 					query.next();
92
 					query.next();
91
 					output.writeStartElement("th");
93
 					output.writeStartElement("th");
94
+					output.writeStartElement("a");
95
+					output.writeAttribute("href", "typica://script/y" + i);
92
 					output.writeCharacters(i);
96
 					output.writeCharacters(i);
97
+					output.writeEndElement(); //a
93
 					output.writeEndElement(); //th
98
 					output.writeEndElement(); //th
94
 					output.writeTextElement("td", query.value(0));
99
 					output.writeTextElement("td", query.value(0));
95
 					output.writeTextElement("td", query.value(1));
100
 					output.writeTextElement("td", query.value(1));
99
 					costtotal += Number(query.value(2));
104
 					costtotal += Number(query.value(2));
100
 					output.writeEndElement(); //tr
105
 					output.writeEndElement(); //tr
101
 				}
106
 				}
107
+				query = query.invalidate();
102
 				output.writeEndElement(); //tbody
108
 				output.writeEndElement(); //tbody
103
 				output.writeStartElement("tfoot");
109
 				output.writeStartElement("tfoot");
104
 				output.writeTextElement("th", "Totals:");
110
 				output.writeTextElement("th", "Totals:");
121
 			endDateField.dateChanged.connect(function() {
127
 			endDateField.dateChanged.connect(function() {
122
 				refresh();
128
 				refresh();
123
 			});
129
 			});
130
+			/* Expand year data */
131
+			var expandedYears = new Array();
132
+			var expandYear = function(url) {
133
+				if(expandedYears.indexOf(url) == -1) {
134
+					expandedYears.push(url);
135
+					var element = new WebElement(view.findFirstElement("#" + url));
136
+					var year = url.slice(1,url.length);
137
+					var details = '<tr><td /><td colspan="3"><table><tr><th>Id</th><th>Invoice</th><th>Vendor</th><th>Cost</th></tr>';
138
+					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'";
139
+					var query = new QSqlQuery();
140
+					query.exec(q);
141
+					while(query.next())
142
+					{
143
+						details += '<tr><td><a href="typica://script/i' + query.value(0) + '">' + query.value(0) + '</a></td>';
144
+						details += '<td>' + query.value(1) + '</td>';
145
+						details += '<td>' + query.value(2) + '</td>';
146
+						details += '<td>' + Number(query.value(3)).toFixed(2) + '</td></tr>';
147
+					}
148
+					query = query.invalidate();
149
+					details += '</table></td></tr>'
150
+					element.appendOutside(details);
151
+				}
152
+			};
153
+			/* Open invoices */
154
+			var openInvoice = function(url) {
155
+				var arg = url.slice(1, url.length);
156
+				var info = createWindow("invoiceinfo");
157
+				info.setInvoiceID(arg);
158
+				var query = new QSqlQuery();
159
+				query.exec("SELECT time, invoice, vendor FROM invoices WHERE id = " + arg);
160
+				query.next();
161
+				var timefield = findChildObject(info, 'date');
162
+				timefield.text = query.value(0);
163
+				var vendorfield = findChildObject(info, 'vendor');
164
+				vendorfield.text = query.value(2);
165
+				var invoicefield = findChildObject(info, 'invoice');
166
+				invoicefield.text = query.value(1);
167
+				var itemtable = findChildObject(info, 'itemtable');
168
+				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");
169
+				query = query.invalidate();
170
+			};
171
+			/* Dispatch script link clicks */
172
+			view.scriptLinkClicked.connect(function(url) {
173
+				var linkType = url[0];
174
+				switch(linkType) {
175
+					case 'y':
176
+						expandYear(url);
177
+						break
178
+					case 'i':
179
+						openInvoice(url);
180
+						break;
181
+				}
182
+			});
124
 		]]>
183
 		]]>
125
 	</program>
184
 	</program>
126
 </window>
185
 </window>

+ 204
- 0
config/Reports/invchange.xml View File

1
+<window id="invchange">
2
+	<reporttitle>Inventory:->Inventory Change Summary</reporttitle>
3
+	<layout type="vertical">
4
+		<layout type="horizontal">
5
+			<label>Start Date:</label>
6
+			<calendar id="startdate" />
7
+			<label>End Date:</label>
8
+			<calendar id="enddate" />
9
+			<label>Weight Unit:</label>
10
+			<sqldrop id="unit" />
11
+			<stretch />
12
+		</layout>
13
+		<webview id="report" />
14
+	</layout>
15
+	<menu name="File">
16
+		<item id="print" shortcut="Ctrl+P">Print</item>
17
+	</menu>
18
+	<program>
19
+		<![CDATA[
20
+			this.windowTitle = "Typica - Inventory Change Summary";
21
+			var startDateField = findChildObject(this, 'startdate');
22
+			startDateField.setDate(startDateField.year(), 1, 1);
23
+			var endDateField = findChildObject(this, 'enddate');
24
+			var unitBox = findChildObject(this, 'unit');
25
+			unitBox.addItem("Kg");
26
+			unitBox.addItem("Lb");
27
+			unitBox.currentIndex = QSettings.value("script/report_unit", 1);
28
+			unitBox['currentIndexChanged(int)'].connect(function() {
29
+				QSettings.setValue("script/report_unit", unitBox.currentIndex);
30
+				refresh();
31
+			});
32
+			var view = findChildObject(this, 'report');
33
+			var printMenu = findChildObject(this, 'print');
34
+			printMenu.triggered.connect(function() {
35
+				view.print();
36
+			});
37
+			function refresh() {
38
+				var buffer = new QBuffer;
39
+				buffer.open(3);
40
+				var output = new XmlWriter(buffer);
41
+				output.writeStartDocument("1.0");
42
+				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">');
43
+				output.writeStartElement("html");
44
+				output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
45
+				output.writeStartElement("head");
46
+				output.writeTextElement("title", "Inventory Change Summary");
47
+				output.writeEndElement();
48
+				output.writeStartElement("body");
49
+				var startDate = "" + startDateField.year() + "-" + startDateField.month() + "-" + startDateField.day();
50
+				var endDate = "" + endDateField.year() + "-" + endDateField.month() + "-" + endDateField.day();
51
+				output.writeTextElement("h1", "Inventory Change Summary: " + startDate + " – " + endDate);
52
+				var conversion = 1;
53
+				if(unitBox.currentIndex == 0) {
54
+					conversion = 2.2;
55
+				}
56
+				var unitText = "Lb";
57
+				if(unitBox.currentIndex == 0) {
58
+					unitText = "Kg";
59
+				}
60
+				var query = new QSqlQuery();
61
+					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)*:c6 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 FROM q ORDER BY name";
62
+				query.prepare(q);
63
+				query.bind(":sd1", startDate);
64
+				query.bind(":sd2", startDate);
65
+				query.bind(":sd3", startDate);
66
+				query.bind(":sd4", startDate);
67
+				query.bind(":sd6", startDate);
68
+				query.bind(":sd7", startDate);
69
+				query.bind(":ed1", endDate);
70
+				query.bind(":ed2", endDate);
71
+				query.bind(":ed3", endDate);
72
+				query.bind(":ed4", endDate);
73
+				query.bind(":ed5", endDate);
74
+				query.bind(":ed6", endDate);
75
+				query.bind(":ed7", endDate);
76
+				query.bind(":c1", conversion);
77
+				query.bind(":c2", conversion);
78
+				query.bind(":c3", conversion);
79
+				query.bind(":c4", conversion);
80
+				query.bind(":c5", conversion);
81
+				query.bind(":c6", conversion);
82
+				query.bind(":c7", conversion);
83
+				query.bind(":c8", conversion);
84
+				query.bind(":c9", conversion);
85
+				query.bind(":c10", conversion);
86
+				query.bind(":c11", conversion);
87
+				query.bind(":c12", conversion);
88
+				query.bind(":c13", conversion);
89
+				query.exec();
90
+				output.writeStartElement("table");
91
+				output.writeAttribute("rules", "groups");
92
+				output.writeAttribute("cellpadding", "3px");
93
+				output.writeStartElement("thead");
94
+				output.writeStartElement("tr");
95
+				output.writeTextElement("th", "ID"); // 0
96
+				output.writeTextElement("th", "Coffee"); // 1
97
+				output.writeTextElement("th", "Reference"); // 2
98
+				output.writeTextElement("th", "Starting (" + unitText + ")"); // 3
99
+				output.writeTextElement("th", "Cost"); // 10
100
+				output.writeTextElement("th", "Purchase (" + unitText + ")"); // 4
101
+				output.writeTextElement("th", "Cost"); // 11
102
+				output.writeTextElement("th", "Use (" + unitText + ")"); // 5
103
+				output.writeTextElement("th", "Cost"); // 12
104
+				output.writeTextElement("th", "Sale (" + unitText + ")"); // 6
105
+				output.writeTextElement("th", "Cost"); // 13
106
+				output.writeTextElement("th", "Adjustment (" + unitText + ")"); // 9
107
+				output.writeTextElement("th", "Cost"); // 15
108
+				output.writeTextElement("th", "Ending (" + unitText + ")"); // 7
109
+				output.writeTextElement("th", "Cost"); // 14
110
+				output.writeEndElement();
111
+				output.writeEndElement();
112
+				output.writeStartElement("tbody");
113
+				var sum3 = 0;
114
+				var sum10 = 0;
115
+				var sum4 = 0;
116
+				var sum11 = 0;
117
+				var sum5 = 0;
118
+				var sum12 = 0;
119
+				var sum6 = 0;
120
+				var sum13 = 0;
121
+				var sum9 = 0;
122
+				var sum15 = 0;
123
+				var sum7 = 0;
124
+				var sum14 = 0;;
125
+				while(query.next())
126
+				{
127
+					output.writeStartElement("tr");
128
+					output.writeStartElement("td");
129
+					output.writeStartElement("a");
130
+					output.writeAttribute("href", "typica://script/" + query.value(0));
131
+					output.writeTextElement("span", query.value(0)); //ID
132
+					output.writeEndElement();
133
+					output.writeEndElement();
134
+					output.writeTextElement("td", query.value(1)); //Coffee
135
+					output.writeTextElement("td", query.value(2)); //Reference
136
+					output.writeTextElement("td", parseFloat(query.value(3)).toFixed(2)); //Starting Wt
137
+					output.writeTextElement("td", parseFloat(query.value(10)).toFixed(2)); //Starting Cost
138
+					output.writeTextElement("td", parseFloat(query.value(4)).toFixed(2)); //Purchase Wt
139
+					output.writeTextElement("td", parseFloat(query.value(11)).toFixed(2)); //Purchase Cost
140
+					output.writeTextElement("td", parseFloat(query.value(5)).toFixed(2)); //Use Wt
141
+					output.writeTextElement("td", parseFloat(query.value(12)).toFixed(2)); //Use Cost
142
+					output.writeTextElement("td", parseFloat(query.value(6)).toFixed(2)); //Sale Wt
143
+					output.writeTextElement("td", parseFloat(query.value(13)).toFixed(2)); //Sale Cost
144
+					output.writeTextElement("td", parseFloat(query.value(9)).toFixed(2)); //Adjustment Wt
145
+					output.writeTextElement("td", parseFloat(query.value(15)).toFixed(2)); //Adjustment Cost
146
+					output.writeTextElement("td", parseFloat(query.value(7)).toFixed(2)); //Ending Wt
147
+					output.writeTextElement("td", parseFloat(query.value(14)).toFixed(2)); //Ending Cost
148
+					output.writeEndElement();
149
+					sum3 += parseFloat(query.value(3));
150
+					sum10 += parseFloat(query.value(10));
151
+					sum4 += parseFloat(query.value(4));
152
+					sum11 += parseFloat(query.value(11));
153
+					sum5 += parseFloat(query.value(5));
154
+					sum12 += parseFloat(query.value(12));
155
+					sum6 += parseFloat(query.value(6));
156
+					sum13 += parseFloat(query.value(13));
157
+					sum9 += parseFloat(query.value(9));
158
+					sum15 += parseFloat(query.value(15));
159
+					sum7 += parseFloat(query.value(7));
160
+					sum14 += parseFloat(query.value(14));
161
+				}
162
+				output.writeEndElement(); // tbody
163
+				output.writeStartElement("tfoot");
164
+				output.writeStartElement("tr");
165
+				output.writeTextElement("td", "");
166
+				output.writeTextElement("td", "");
167
+				output.writeTextElement("th", "Total:");
168
+				output.writeTextElement("td", sum3.toFixed(2));
169
+				output.writeTextElement("td", sum10.toFixed(2));
170
+				output.writeTextElement("td", sum4.toFixed(2));
171
+				output.writeTextElement("td", sum11.toFixed(2));
172
+				output.writeTextElement("td", sum5.toFixed(2));
173
+				output.writeTextElement("td", sum12.toFixed(2));
174
+				output.writeTextElement("td", sum6.toFixed(2));
175
+				output.writeTextElement("td", sum13.toFixed(2));
176
+				output.writeTextElement("td", sum9.toFixed(2));
177
+				output.writeTextElement("td", sum15.toFixed(2));
178
+				output.writeTextElement("td", sum7.toFixed(2));
179
+				output.writeTextElement("td", sum14.toFixed(2));
180
+				output.writeEndElement(); // tr
181
+				output.writeEndElement(); // tfoot
182
+				output.writeEndElement();
183
+				output.writeEndElement();
184
+				output.writeEndElement();
185
+				output.writeEndDocument();
186
+				view.setContent(buffer);
187
+				buffer.close();
188
+				query = query.invalidate();
189
+			}
190
+			refresh();
191
+			startDateField.dateChanged.connect(function() {
192
+				refresh();
193
+			});
194
+			endDateField.dateChanged.connect(function() {
195
+				refresh();
196
+			});
197
+			view.scriptLinkClicked.connect(function(url) {
198
+				var itemReport = createReport("itemtransactions.xml");
199
+				var sIB = findChildObject(itemReport, 'item');
200
+				sIB.currentIndex = sIB.findData(url);
201
+			});
202
+		]]>
203
+	</program>
204
+</window>

+ 2
- 1
config/Reports/inventory.xml View File

174
                 output.writeTextElement("td", query.value(1));
174
                 output.writeTextElement("td", query.value(1));
175
                 output.writeTextElement("td", "");
175
                 output.writeTextElement("td", "");
176
                 output.writeTextElement("td", "");
176
                 output.writeTextElement("td", "");
177
-                output.writeEndElement();
177
+                query = query.invalidate();
178
+				output.writeEndElement();
178
                 output.writeEndElement();
179
                 output.writeEndElement();
179
                 output.writeEndElement();
180
                 output.writeEndElement();
180
                 output.writeEndElement();
181
                 output.writeEndElement();

+ 199
- 0
config/Reports/itemtransactions.xml View File

1
+<window id="item_transactions">
2
+	<reporttitle>Inventory:->Item Transactions</reporttitle>
3
+	<layout type="vertical">
4
+		<layout type="horizontal">
5
+			<label>Item:</label>
6
+			<sqldrop id="item" data="0" display="1" showdata="true">
7
+				<null />
8
+				<query>SELECT id, name FROM items WHERE category = 'Coffee: Unroasted' ORDER BY name</query>
9
+			</sqldrop>
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 = "Typica - Item Transactions";
22
+			var itemBox = findChildObject(this, 'item');
23
+			var unitBox = findChildObject(this, 'unit');
24
+			unitBox.addItem("Kg");
25
+			unitBox.addItem("Lb");
26
+			unitBox.currentIndex = QSettings.value("script/report_unit", 1);
27
+			unitBox['currentIndexChanged(int)'].connect(function() {
28
+				QSettings.setValue("script/report_unit", unitBox.currentIndex);
29
+				refresh();
30
+			});
31
+			var view = findChildObject(this, 'report');
32
+			var printMenu = findChildObject(this, 'print');
33
+			printMenu.triggered.connect(function() {
34
+				view.print();
35
+			});
36
+			itemBox['currentIndexChanged(int)'].connect(function() {
37
+				refresh();
38
+			});
39
+			function refresh() {
40
+				var buffer = new QBuffer;
41
+				buffer.open(3);
42
+				var output = new XmlWriter(buffer);
43
+				output.writeStartDocument("1.0");
44
+				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">');
45
+				output.writeStartElement("html");
46
+				output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
47
+				output.writeStartElement("head");
48
+				output.writeTextElement("title", "Item Transactions");
49
+				output.writeStartElement("style");
50
+				output.writeAttribute("type", "text/css");
51
+				output.writeCDATA("tr.PURCHASE {background-color: #77FF77}");
52
+				output.writeCDATA("tr.USE {background-color: #FFFFFF}");
53
+				output.writeCDATA("tr.INVENTORY {background-color: #7777FF}");
54
+				output.writeCDATA("tr.SALE {background-color: #FF77FF}");
55
+				output.writeCDATA("tr.LOSS {background-color: #FF7777}");
56
+				output.writeCDATA("tr.MAKE {background-color: #FFFF77}");
57
+				output.writeEndElement(); // style
58
+				
59
+				output.writeEndElement();
60
+				output.writeStartElement("body");
61
+				output.writeTextElement("h1", "Item Transactions:");
62
+				output.writeStartElement("table");
63
+				output.writeStartElement("tr");
64
+				output.writeStartElement("td");
65
+				output.writeTextElement("strong", "Item: ")
66
+				output.writeTextElement("span", itemBox.currentText);
67
+				output.writeEndElement(); // td
68
+				var query = new QSqlQuery();
69
+				query.prepare("SELECT reference, category FROM items WHERE id = :item");
70
+				query.bind(":item", itemBox.currentData());
71
+				query.exec();
72
+				if(query.next()) {
73
+					output.writeStartElement("td");
74
+					output.writeTextElement("strong", "Reference: ");
75
+					output.writeTextElement("span", query.value(0));
76
+					output.writeEndElement(); // td
77
+					output.writeStartElement("td");
78
+					output.writeTextElement("strong", "Category: ");
79
+					output.writeTextElement("span", query.value(1));
80
+					output.writeEndElement(); //td
81
+					output.writeEndElement(); //tr
82
+					query.prepare("SELECT origin, region, producer, grade, milling, drying FROM coffees WHERE id = :item");
83
+					query.bind(":item", itemBox.currentData());
84
+					query.exec();
85
+					if(query.next()) {
86
+						output.writeStartElement("tr");
87
+						output.writeStartElement("td");
88
+						output.writeTextElement("strong", "Origin: ");
89
+						output.writeTextElement("span", query.value(0));
90
+						output.writeEndElement(); // td
91
+						output.writeStartElement("td");
92
+						output.writeTextElement("strong", "Region: ");
93
+						output.writeTextElement("span", query.value(1));
94
+						output.writeEndElement(); // td
95
+						output.writeStartElement("td");
96
+						output.writeTextElement("strong", "Producer: ");
97
+						output.writeTextElement("span", query.value(2));
98
+						output.writeEndElement(); // td
99
+						output.writeEndElement(); // tr
100
+						output.writeStartElement("tr");
101
+						output.writeStartElement("td");
102
+						output.writeTextElement("strong", "Grade: ");
103
+						output.writeTextElement("span", query.value(3));
104
+						output.writeEndElement(); // td
105
+						output.writeStartElement("td");
106
+						output.writeTextElement("strong", "Milling: ");
107
+						output.writeTextElement("span", query.value(4));
108
+						output.writeEndElement(); // td
109
+						output.writeStartElement("td");
110
+						output.writeTextElement("strong", "Drying: ");
111
+						output.writeTextElement("span", query.value(5));
112
+						output.writeEndElement(); // td
113
+						output.writeEndElement(); // tr
114
+						query.prepare("SELECT decaf_method FROM decaf_coffees WHERE id = :item");
115
+						query.bind(":item", itemBox.currentData());
116
+						query.exec();
117
+						if(query.next()) {
118
+							output.writeStartElement("tr");
119
+							output.writeStartElement("td");
120
+							output.writeAttribute("colspan", "3");
121
+							output.writeTextElement("strong", "Decaffeination Method: ");
122
+							output.writeTextElement("span", query.value(0));
123
+							output.writeEndElement(); // td
124
+							output.writeEndElement(); // tr
125
+						}
126
+					}
127
+					output.writeEndElement() // table
128
+					
129
+					query.prepare("SELECT time::date, type, quantity / :c1, balance / :c2 FROM item_history(:item)");
130
+					switch(unitBox.currentIndex)
131
+					{
132
+						case 0:
133
+							query.bind(":c1", 2.2);
134
+							query.bind(":c2", 2.2);
135
+							break;
136
+						case 1:
137
+							query.bind(":c1", 1);
138
+							query.bind(":c2", 1);
139
+							break;
140
+					}
141
+					query.bind(":item", itemBox.currentData());
142
+					query.exec();
143
+					output.writeStartElement("table");
144
+					output.writeStartElement("tr");
145
+					output.writeTextElement("th", "Date");
146
+					output.writeTextElement("th", "Type");
147
+					output.writeTextElement("th", "Quantity");
148
+					output.writeTextElement("th", "Balance");
149
+					output.writeEndElement(); // tr
150
+					var prev_balance = "0";
151
+					var prev_prec = 0;
152
+					var cur_prec = 0;
153
+					while(query.next()) {
154
+						output.writeStartElement("tr");
155
+						output.writeAttribute("class", query.value(1));
156
+						output.writeTextElement("td", query.value(0));
157
+						output.writeTextElement("td", query.value(1));
158
+						if(query.value(1) == "INVENTORY") {
159
+							var split = prev_balance.split('.');
160
+							if(split.length > 1) {
161
+								prev_prec = split[1].length;
162
+							} else {
163
+								prev_prec = 0;
164
+							}
165
+							split = query.value(2).split('.');
166
+							if(split.length > 1) {
167
+								cur_prec = split[1].length;
168
+							} else {
169
+								cur_prec = 0;
170
+							}
171
+							var prec = prev_prec > cur_prec ? prev_prec : cur_prec;
172
+							output.writeTextElement("td", (Number(query.value(2)) - Number(prev_balance)).toFixed(prec));
173
+						} else {
174
+							output.writeTextElement("td", query.value(2));
175
+						}
176
+						output.writeTextElement("td", query.value(3));
177
+						prev_balance = query.value(3);
178
+						output.writeEndElement(); // tr
179
+					}
180
+					output.writeEndElement(); // table
181
+					/* Put the rest of the report here. No sense running queries if
182
+					   the item doesn't exist. */
183
+				} else {
184
+					/* Close tags if item data not found. */
185
+					output.writeEndElement(); // tr
186
+					output.writeEndElement(); // table
187
+				}
188
+				
189
+				output.writeEndElement(); // body
190
+				output.writeEndElement(); // html
191
+				output.writeEndDocument();
192
+				view.setContent(buffer);
193
+				buffer.close();
194
+				query = query.invalidate();
195
+			}
196
+			refresh();
197
+		]]>
198
+	</program>
199
+</window>

+ 2
- 1
config/Reports/rwacp.xml View File

114
 				query.exec();
114
 				query.exec();
115
 				query.next();
115
 				query.next();
116
                 output.writeTextElement("td", query.value(0));
116
                 output.writeTextElement("td", query.value(0));
117
-                output.writeEndElement();
117
+                query = query.invalidate();
118
+				output.writeEndElement();
118
                 output.writeEndElement();
119
                 output.writeEndElement();
119
                 output.writeEndElement();
120
                 output.writeEndElement();
120
                 output.writeEndElement();
121
                 output.writeEndElement();

+ 1
- 0
config/Windows/batchdetailsnew.xml View File

295
 				rq = rq + 'string($t/annotation), "~")';
295
 				rq = rq + 'string($t/annotation), "~")';
296
 				colQuery.setQuery(rq);
296
 				colQuery.setQuery(rq);
297
 				var annotationData = colQuery.exec();
297
 				var annotationData = colQuery.exec();
298
+				colQuery = colQuery.invalidate();
298
 				buffer2.close();
299
 				buffer2.close();
299
 				output.writeStartElement("tbody");
300
 				output.writeStartElement("tbody");
300
 				var annotationRecords = annotationData.split("~");
301
 				var annotationRecords = annotationData.split("~");

+ 35
- 3
config/Windows/navigation.xml View File

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\"");
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");
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");
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 calculate_inventory_balance() RETURNS trigger AS $$ DECLARE old_quantity numeric; BEGIN old_quantity := (SELECT balance FROM working WHERE time = (SELECT max(time) FROM working)); IF old_quantity IS NULL THEN old_quantity := 0; END IF; IF NEW.type = 'PURCHASE' OR NEW.type = 'MAKE' THEN NEW.balance := old_quantity + NEW.quantity; ELSE IF NEW.type = 'INVENTORY' THEN NEW.balance := NEW.quantity; ELSE IF NEW.type = 'USE' OR NEW.type = 'SALE' OR NEW.type = 'LOSS' THEN NEW.balance := old_quantity - NEW.quantity; END IF; END IF; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql");
214
-			query.exec("CREATE FUNCTION item_history(bigint) RETURNS SETOF item_transaction_with_balance AS $_$ DECLARE r item_transaction_with_balance; BEGIN CREATE TEMPORARY TABLE working(time timestamp without time zone, item bigint, quantity numeric, cost numeric, vendor text, reason text, customer text, type text, balance numeric) ON COMMIT DROP; CREATE TRIGGER quantity_update BEFORE INSERT ON working FOR EACH ROW EXECUTE PROCEDURE calculate_inventory_balance(); INSERT INTO working SELECT time, item, quantity, cost, vendor, reason, customer, type, NULL AS balance FROM all_transactions WHERE item = $1 ORDER BY time ASC; FOR r IN SELECT time, item, quantity, cost, vendor, reason, customer, type, balance FROM working LOOP RETURN NEXT r; END LOOP; DROP TABLE working; RETURN; END; $_$ LANGUAGE plpgsql");
215
 			query.exec("CREATE FUNCTION log_make() RETURNS trigger AS $$ BEGIN IF NEW.roasted_quantity IS NOT NULL THEN INSERT INTO make VALUES(NEW.time, NEW.roasted_id, NEW.roasted_quantity); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql");
213
 			query.exec("CREATE FUNCTION log_make() RETURNS trigger AS $$ BEGIN IF NEW.roasted_quantity IS NOT NULL THEN INSERT INTO make VALUES(NEW.time, NEW.roasted_id, NEW.roasted_quantity); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql");
216
 			query.exec("CREATE FUNCTION log_make_update() RETURNS trigger AS $$ BEGIN IF NEW.roasted_quantity <> OLD.roasted_quantity AND NEW.roasted_quantity IS NOT NULL THEN INSERT INTO make VALUES(NEW.time, NEW.roasted_id, NEW.roasted_quantity); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql");
214
 			query.exec("CREATE FUNCTION log_make_update() RETURNS trigger AS $$ BEGIN IF NEW.roasted_quantity <> OLD.roasted_quantity AND NEW.roasted_quantity IS NOT NULL THEN INSERT INTO make VALUES(NEW.time, NEW.roasted_id, NEW.roasted_quantity); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql");
217
 			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");
215
 			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");
252
 			query.exec("INSERT INTO TypicaFeatures VALUES('sample-roasting', TRUE, 1)");
250
 			query.exec("INSERT INTO TypicaFeatures VALUES('sample-roasting', TRUE, 1)");
253
 			query = query.invalidate();
251
 			query = query.invalidate();
254
 		};
252
 		};
255
-
253
+		
254
+		/* Some changes to the database are required to log who performed what tasks.
255
+		   This adds logging for all transaction types and also for the roasting log.*/
256
+		var DBUpdateMultiUser = function() {
257
+			var query = new QSqlQuery;
258
+			query.exec("ALTER TABLE transactions ADD COLUMN person text DEFAULT NULL");
259
+			query.exec("ALTER TABLE roasting_log ADD COLUMN person text DEFAULT NULL");
260
+			query.exec("CREATE OR REPLACE FUNCTION log_session_user() RETURNS trigger AS $$ BEGIN NEW.person := session_user; RETURN NEW; END; $$ LANGUAGE plpgsql");
261
+			query.exec("CREATE TRIGGER log_person BEFORE INSERT ON inventory FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
262
+			query.exec("CREATE TRIGGER log_person BEFORE INSERT ON loss FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
263
+			query.exec("CREATE TRIGGER log_person BEFORE INSERT ON make FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
264
+			query.exec("CREATE TRIGGER log_person BEFORE INSERT ON purchase FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
265
+			query.exec("CREATE TRIGGER log_person BEFORE INSERT ON sale FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
266
+			query.exec("CREATE TRIGGER log_person BEFORE INSERT ON use FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
267
+			query.exec("CREATE TRIGGER log_person BEFORE INSERT ON roasting_log FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
268
+			query.exec("UPDATE TypicaFeatures SET version = 2 WHERE feature = 'base-features'");
269
+			query = query.invalidate();
270
+		};
271
+		
272
+		/* Bug fix and optimization for item_history */
273
+		var DBUpdateHistory = function() {
274
+			var query = new QSqlQuery;
275
+			query.exec("CREATE TYPE transaction_type AS (type text, quantity numeric)");
276
+			query.exec("CREATE FUNCTION update_balance(numeric, transaction_type) RETURNS numeric AS $$ BEGIN CASE $2.type WHEN 'PURCHASE', 'MAKE' THEN RETURN $1 + $2.quantity; WHEN 'INVENTORY' THEN RETURN $2.quantity; WHEN 'USE', 'SALE', 'LOSS' THEN RETURN $1 - $2.quantity; END CASE; END; $$ LANGUAGE plpgsql STRICT");
277
+			query.exec("CREATE AGGREGATE transaction_balance (BASETYPE = transaction_type, SFUNC = update_balance, STYPE = numeric, INITCOND = '0')");
278
+			query.exec("CREATE OR REPLACE FUNCTION item_history(bigint) RETURNS SETOF item_transaction_with_balance AS $$ SELECT time, item, quantity, cost, vendor, reason, customer, type, transaction_balance((type, quantity)::transaction_type) OVER (PARTITION BY item ORDER BY time ASC) AS balance FROM all_transactions WHERE item = $1; $$ LANGUAGE SQL");
279
+			query.exec("DROP FUNCTION calculate_inventory_balance()");
280
+			query = query.invalidate();
281
+		};
282
+		
256
 		query = new QSqlQuery();
283
 		query = new QSqlQuery();
257
 		/* A table keeps track of database versioning information. This table is created
284
 		/* A table keeps track of database versioning information. This table is created
258
 		   if required. */
285
 		   if required. */
267
 			{
294
 			{
268
 				DBCreateBase();
295
 				DBCreateBase();
269
 			}
296
 			}
297
+			if(query.value(2) < 2)
298
+			{
299
+				DBUpdateMultiUser();
300
+				DBUpdateHistory();
301
+			}
270
 		}
302
 		}
271
 		else
303
 		else
272
 		{
304
 		{

+ 16
- 1
config/Windows/newbatch.xml View File

21
         </layout>
21
         </layout>
22
         <label>Green Coffee:</label>
22
         <label>Green Coffee:</label>
23
         <sqltablearray columns="2" id="greens">
23
         <sqltablearray columns="2" id="greens">
24
-            <column name="Coffee" delegate="sql" showdata="true" null="false" data="0" display="1">SELECT id, name FROM coffees WHERE quantity &lt;&gt; 0 ORDER BY name</column>
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" />
25
             <column name="Weight" />
26
         </sqltablearray>
26
         </sqltablearray>
27
         <layout type="horizontal">
27
         <layout type="horizontal">
119
 				}
119
 				}
120
 			}
120
 			}
121
 			model.dataChanged.connect(function() {
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
+				}
122
 				green.text = table.columnSum(1, 0);
136
 				green.text = table.columnSum(1, 0);
123
 				table.resizeColumnToContents(0);
137
 				table.resizeColumnToContents(0);
124
                 if(parseFloat(green.text) > 0)
138
                 if(parseFloat(green.text) > 0)
160
             var profilebutton = findChildObject(this, 'load');
174
             var profilebutton = findChildObject(this, 'load');
161
             profilebutton.setEnabled(false);
175
             profilebutton.setEnabled(false);
162
             roasted['currentIndexChanged(int)'].connect(function() {
176
             roasted['currentIndexChanged(int)'].connect(function() {
177
+				table.clear();
163
                 var query = new QSqlQuery();
178
                 var query = new QSqlQuery();
164
                 var q = "SELECT EXISTS(SELECT 1 FROM item_files WHERE item = ";
179
                 var q = "SELECT EXISTS(SELECT 1 FROM item_files WHERE item = ";
165
                 q = q + roasted.currentData();
180
                 q = q + roasted.currentData();

+ 255
- 46
src/typica.w View File

2256
 QScriptValue XQuery_bind(QScriptContext *context, QScriptEngine *engine);
2256
 QScriptValue XQuery_bind(QScriptContext *context, QScriptEngine *engine);
2257
 QScriptValue XQuery_exec(QScriptContext *context, QScriptEngine *engine);
2257
 QScriptValue XQuery_exec(QScriptContext *context, QScriptEngine *engine);
2258
 QScriptValue XQuery_setQuery(QScriptContext *context, QScriptEngine *engine);
2258
 QScriptValue XQuery_setQuery(QScriptContext *context, QScriptEngine *engine);
2259
+QScriptValue XQuery_invalidate(QScriptContext *context, QScriptEngine *engine);
2259
 void setXQueryProperties(QScriptValue value, QScriptEngine *engine);
2260
 void setXQueryProperties(QScriptValue value, QScriptEngine *engine);
2260
 
2261
 
2261
 @ The constructor must be registered with the host environment. This is done a
2262
 @ The constructor must be registered with the host environment. This is done a
2266
 engine->globalObject().setProperty("XQuery", constructor);
2267
 engine->globalObject().setProperty("XQuery", constructor);
2267
 
2268
 
2268
 @ The constructor just needs to make sure the functions we want to make
2269
 @ The constructor just needs to make sure the functions we want to make
2269
-available are applied.
2270
+available are applied. A method is also provided to free the |QXmlQuery|.
2270
 
2271
 
2271
 @<Functions for scripting@>=
2272
 @<Functions for scripting@>=
2272
 QScriptValue constructXQuery(QScriptContext *, QScriptEngine *engine)
2273
 QScriptValue constructXQuery(QScriptContext *, QScriptEngine *engine)
2276
 	return object;
2277
 	return object;
2277
 }
2278
 }
2278
 
2279
 
2280
+QScriptValue XQuery_invalidate(QScriptContext *context, QScriptEngine *)
2281
+{
2282
+	QXmlQuery *self = getself<QXmlQuery *>(context);
2283
+	delete self;
2284
+	return QScriptValue();
2285
+}
2286
+
2279
 void setXQueryProperties(QScriptValue value, QScriptEngine *engine)
2287
 void setXQueryProperties(QScriptValue value, QScriptEngine *engine)
2280
 {
2288
 {
2281
 	value.setProperty("bind", engine->newFunction(XQuery_bind));
2289
 	value.setProperty("bind", engine->newFunction(XQuery_bind));
2282
 	value.setProperty("exec", engine->newFunction(XQuery_exec));
2290
 	value.setProperty("exec", engine->newFunction(XQuery_exec));
2283
 	value.setProperty("setQuery", engine->newFunction(XQuery_setQuery));
2291
 	value.setProperty("setQuery", engine->newFunction(XQuery_setQuery));
2292
+	value.setProperty("invalidate", engine->newFunction(XQuery_invalidate));
2284
 }
2293
 }
2285
 
2294
 
2286
 @ The |bind()| property can be used to specify a |QIODevice| to be referenced by
2295
 @ The |bind()| property can be used to specify a |QIODevice| to be referenced by
3369
 	public:@/
3378
 	public:@/
3370
 		SqlQueryConnection(const QString &query = QString());
3379
 		SqlQueryConnection(const QString &query = QString());
3371
 		~SqlQueryConnection();
3380
 		~SqlQueryConnection();
3372
-		QSqlQuery* operator->();
3381
+		QSqlQuery* operator->() const;
3373
 	private:@/
3382
 	private:@/
3374
 		QString connection;
3383
 		QString connection;
3375
 		QSqlQuery *q;
3384
 		QSqlQuery *q;
3409
 object.
3418
 object.
3410
 
3419
 
3411
 @<SqlQueryConnection implementation@>=
3420
 @<SqlQueryConnection implementation@>=
3412
-QSqlQuery* SqlQueryConnection::operator->()
3421
+QSqlQuery* SqlQueryConnection::operator->() const
3413
 {
3422
 {
3414
 	return q;
3423
 	return q;
3415
 }
3424
 }
3448
 @<Functions for scripting@>=
3457
 @<Functions for scripting@>=
3449
 QScriptValue constructQSqlQuery(QScriptContext *, QScriptEngine *engine)
3458
 QScriptValue constructQSqlQuery(QScriptContext *, QScriptEngine *engine)
3450
 {
3459
 {
3460
+	SqlQueryConnection *obj = new SqlQueryConnection();
3451
 	QScriptValue object =
3461
 	QScriptValue object =
3452
-	     engine->toScriptValue<void *>(new SqlQueryConnection());
3462
+	     engine->toScriptValue<void *>(obj);
3453
 	setQSqlQueryProperties(object, engine);
3463
 	setQSqlQueryProperties(object, engine);
3454
 	return object;
3464
 	return object;
3455
 }
3465
 }
3478
 @<Functions for scripting@>=
3488
 @<Functions for scripting@>=
3479
 QScriptValue QSqlQuery_exec(QScriptContext *context, QScriptEngine *engine)
3489
 QScriptValue QSqlQuery_exec(QScriptContext *context, QScriptEngine *engine)
3480
 {
3490
 {
3481
-	SqlQueryConnection *query = getself<SqlQueryConnection *>(context);
3491
+	QSqlQuery *q = getself<SqlQueryConnection *>(context)->operator->();
3482
 	QScriptValue retval;
3492
 	QScriptValue retval;
3483
 	if(context->argumentCount() == 1)
3493
 	if(context->argumentCount() == 1)
3484
 	{
3494
 	{
3485
 		retval = QScriptValue(engine,
3495
 		retval = QScriptValue(engine,
3486
-		                      query->exec(argument<QString>(0, context)));
3496
+		                      q->exec(argument<QString>(0, context)));
3487
 	}
3497
 	}
3488
 	else
3498
 	else
3489
 	{
3499
 	{
3490
-		retval = QScriptValue(engine, query->exec());
3500
+		retval = QScriptValue(engine, q->exec());
3491
 	}
3501
 	}
3492
-	if(query->lastError().isValid())
3502
+	if(q->lastError().isValid())
3493
 	{
3503
 	{
3494
-		qDebug() << query->lastQuery();
3495
-		qDebug() << query->lastError().text();
3504
+		qDebug() << q->lastQuery();
3505
+		qDebug() << q->lastError().text();
3496
 	}
3506
 	}
3497
 	return retval;
3507
 	return retval;
3498
 }
3508
 }
3499
 
3509
 
3500
 QScriptValue QSqlQuery_executedQuery(QScriptContext *context, QScriptEngine *)
3510
 QScriptValue QSqlQuery_executedQuery(QScriptContext *context, QScriptEngine *)
3501
 {
3511
 {
3502
-	SqlQueryConnection *query = getself<SqlQueryConnection *>(context);
3512
+	QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
3503
 	return QScriptValue(query->lastQuery());
3513
 	return QScriptValue(query->lastQuery());
3504
 }
3514
 }
3505
 
3515
 
3506
 QScriptValue QSqlQuery_next(QScriptContext *context, QScriptEngine *engine)
3516
 QScriptValue QSqlQuery_next(QScriptContext *context, QScriptEngine *engine)
3507
 {
3517
 {
3508
-	SqlQueryConnection *query = getself<SqlQueryConnection *>(context);
3518
+	QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
3509
 	return QScriptValue(engine, query->next());
3519
 	return QScriptValue(engine, query->next());
3510
 }
3520
 }
3511
 
3521
 
3512
 QScriptValue QSqlQuery_value(QScriptContext *context, QScriptEngine *engine)
3522
 QScriptValue QSqlQuery_value(QScriptContext *context, QScriptEngine *engine)
3513
 {
3523
 {
3514
-	SqlQueryConnection *query = getself<SqlQueryConnection *>(context);
3524
+	QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
3515
 	return QScriptValue(engine,
3525
 	return QScriptValue(engine,
3516
 	                    query->value(argument<int>(0, context)).toString());
3526
 	                    query->value(argument<int>(0, context)).toString());
3517
 }
3527
 }
3522
 @<Functions for scripting@>=
3532
 @<Functions for scripting@>=
3523
 QScriptValue QSqlQuery_prepare(QScriptContext *context, QScriptEngine *engine)
3533
 QScriptValue QSqlQuery_prepare(QScriptContext *context, QScriptEngine *engine)
3524
 {
3534
 {
3525
-	SqlQueryConnection *query = getself<SqlQueryConnection *>(context);
3535
+	QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
3526
 	return QScriptValue(engine, query->prepare(argument<QString>(0, context)));
3536
 	return QScriptValue(engine, query->prepare(argument<QString>(0, context)));
3527
 }
3537
 }
3528
 
3538
 
3529
 QScriptValue QSqlQuery_bind(QScriptContext *context, QScriptEngine *)
3539
 QScriptValue QSqlQuery_bind(QScriptContext *context, QScriptEngine *)
3530
 {
3540
 {
3531
-	SqlQueryConnection *query = getself<SqlQueryConnection *>(context);
3541
+	QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
3532
 	query->bindValue(argument<QString>(0, context),
3542
 	query->bindValue(argument<QString>(0, context),
3533
 	                 argument<QVariant>(1, context));
3543
 	                 argument<QVariant>(1, context));
3534
 	return QScriptValue();
3544
 	return QScriptValue();
3537
 QScriptValue QSqlQuery_bindFileData(QScriptContext *context,
3547
 QScriptValue QSqlQuery_bindFileData(QScriptContext *context,
3538
                                     QScriptEngine *)
3548
                                     QScriptEngine *)
3539
 {
3549
 {
3540
-	SqlQueryConnection *query = getself<SqlQueryConnection *>(context);
3550
+	QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
3541
 	QString placeholder = argument<QString>(0, context);
3551
 	QString placeholder = argument<QString>(0, context);
3542
 	QString filename = argument<QString>(1, context);
3552
 	QString filename = argument<QString>(1, context);
3543
 	QFile file(filename);
3553
 	QFile file(filename);
3554
 QScriptValue QSqlQuery_bindDeviceData(QScriptContext *context,
3564
 QScriptValue QSqlQuery_bindDeviceData(QScriptContext *context,
3555
                                       QScriptEngine *)
3565
                                       QScriptEngine *)
3556
 {
3566
 {
3557
-	SqlQueryConnection *query = getself<SqlQueryConnection *>(context);
3567
+	QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
3558
 	QString placeholder = argument<QString>(0, context);
3568
 	QString placeholder = argument<QString>(0, context);
3559
 	QIODevice *device = argument<QIODevice *>(1, context);
3569
 	QIODevice *device = argument<QIODevice *>(1, context);
3560
 	device->reset();
3570
 	device->reset();
3680
 @<Functions for scripting@>=
3690
 @<Functions for scripting@>=
3681
 QScriptValue annotationFromRecord(QScriptContext *context, QScriptEngine *)
3691
 QScriptValue annotationFromRecord(QScriptContext *context, QScriptEngine *)
3682
 {
3692
 {
3683
-	SqlQueryConnection query;
3693
+	SqlQueryConnection h;
3694
+	QSqlQuery *query = h.operator->();
3684
 	QString q = "SELECT file FROM files WHERE id = :file";
3695
 	QString q = "SELECT file FROM files WHERE id = :file";
3685
-	query.prepare(q);
3686
-	query.bindValue(":file", argument<int>(0, context));
3687
-	query.exec();
3688
-	query.next();
3689
-	QByteArray array = query.value(0).toByteArray();
3696
+	query->prepare(q);
3697
+	query->bindValue(":file", argument<int>(0, context));
3698
+	query->exec();
3699
+	query->next();
3700
+	QByteArray array = query->value(0).toByteArray();
3690
 	QBuffer buffer(&array);
3701
 	QBuffer buffer(&array);
3691
 	buffer.open(QIODevice::ReadOnly);
3702
 	buffer.open(QIODevice::ReadOnly);
3692
 	QXmlQuery xquery;
3703
 	QXmlQuery xquery;
3866
 {\tt <window>} element into a window displayed on screen, the script in the
3877
 {\tt <window>} element into a window displayed on screen, the script in the
3867
 {\tt <program>} elements must call a function to display a specified window.
3878
 {\tt <program>} elements must call a function to display a specified window.
3868
 
3879
 
3880
+Report windows can be produced by scripts in a similar, but slightly different
3881
+manner.
3882
+
3869
 \danger This design works, but it'@q'@>s not particularly good design. It was written
3883
 \danger This design works, but it'@q'@>s not particularly good design. It was written
3870
 under severe time constraints and should be redesigned or at least cleaned up
3884
 under severe time constraints and should be redesigned or at least cleaned up
3871
 and reorganized.\endanger
3885
 and reorganized.\endanger
3872
 
3886
 
3873
 @<Function prototypes for scripting@>=
3887
 @<Function prototypes for scripting@>=
3874
 QScriptValue createWindow(QScriptContext *context, QScriptEngine *engine);
3888
 QScriptValue createWindow(QScriptContext *context, QScriptEngine *engine);
3889
+QScriptValue createReport(QScriptContext *context, QScriptEngine *engine);
3875
 void addLayoutToWidget(QDomElement element, QStack<QWidget*> *widgetStack,
3890
 void addLayoutToWidget(QDomElement element, QStack<QWidget*> *widgetStack,
3876
                        QStack<QLayout*> *layoutStack);
3891
                        QStack<QLayout*> *layoutStack);
3877
 void addLayoutToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
3892
 void addLayoutToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
3931
 void addSpinBoxToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
3946
 void addSpinBoxToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
3932
                         QStack<QLayout *> *layoutStack);
3947
                         QStack<QLayout *> *layoutStack);
3933
 
3948
 
3934
-@ The function for creating the window must be made available to the scripting
3949
+@ The functions for creating windows must be made available to the scripting
3935
 engine.
3950
 engine.
3936
 
3951
 
3937
 @<Set up the scripting engine@>=
3952
 @<Set up the scripting engine@>=
3938
 engine->globalObject().setProperty("createWindow",
3953
 engine->globalObject().setProperty("createWindow",
3939
                                    engine->newFunction(createWindow));
3954
                                    engine->newFunction(createWindow));
3955
+engine->globalObject().setProperty("createReport",
3956
+                                   engine->newFunction(createReport));
3940
 
3957
 
3941
 @ This function must examine the configuration document in search of the
3958
 @ This function must examine the configuration document in search of the
3942
 appropriate window element, parse the contents of that element, and create a
3959
 appropriate window element, parse the contents of that element, and create a
3956
 	return object;
3973
 	return object;
3957
 }
3974
 }
3958
 
3975
 
3976
+@ Report files are not part of the configuration document and must be created
3977
+differently. While there is a special menu type that handles all of this
3978
+without involving the host environment, scripted generation and manipulation of
3979
+report windows requires another function. This function will only work after a
3980
+window with a reports menu has been created.
3981
+
3982
+@<Functions for scripting@>=
3983
+QScriptValue createReport(QScriptContext *context, QScriptEngine *engine)
3984
+{
3985
+	QString targetID = argument<QString>(0, context);
3986
+	QFile file(QString("reports:%1").arg(targetID));
3987
+	QScriptValue object;
3988
+	if(file.open(QIODevice::ReadOnly))
3989
+	{
3990
+		QDomDocument document;
3991
+		document.setContent(&file, true);
3992
+		QDomElement element = document.documentElement();
3993
+		if(!element.isNull())
3994
+		{
3995
+			@<Display the window@>@;
3996
+		}
3997
+		file.close();
3998
+	}
3999
+	return object;
4000
+}
4001
+
3959
 @ First we must locate the {\tt <window>} element. The most sensible way to do
4002
 @ First we must locate the {\tt <window>} element. The most sensible way to do
3960
 this would require that each {\tt <window>} element has an ID attribute and
4003
 this would require that each {\tt <window>} element has an ID attribute and
3961
 search the DOM tree for that ID. Unfortunately, as of this writing,
4004
 search the DOM tree for that ID. Unfortunately, as of this writing,
5103
 @<Assign column delegate from SQL@>=
5146
 @<Assign column delegate from SQL@>=
5104
 SqlComboBoxDelegate *delegate = new SqlComboBoxDelegate;
5147
 SqlComboBoxDelegate *delegate = new SqlComboBoxDelegate;
5105
 SqlComboBox *widget = new SqlComboBox();
5148
 SqlComboBox *widget = new SqlComboBox();
5149
+if(currentElement.hasAttribute("nulltext"))
5150
+{
5151
+	widget->setNullText(currentElement.attribute("nulltext"));
5152
+}
5153
+if(currentElement.hasAttribute("nulldata"))
5154
+{
5155
+	widget->setNullData(QVariant(currentElement.attribute("nulldata")));
5156
+}
5106
 if(currentElement.hasAttribute("null"))
5157
 if(currentElement.hasAttribute("null"))
5107
 {
5158
 {
5108
 	if(currentElement.attribute("null") == "true")
5159
 	if(currentElement.attribute("null") == "true")
5556
 QScriptValue SaltTable_quotedColumnArray(QScriptContext *context,
5607
 QScriptValue SaltTable_quotedColumnArray(QScriptContext *context,
5557
                                          QScriptEngine *engine);
5608
                                          QScriptEngine *engine);
5558
 QScriptValue SaltTable_setData(QScriptContext *context, QScriptEngine *engine);
5609
 QScriptValue SaltTable_setData(QScriptContext *context, QScriptEngine *engine);
5610
+QScriptValue SaltTable_clear(QScriptContext *context, QScriptEngine *engine);
5611
+QScriptValue SaltTable_removeRow(QScriptContext *context, QScriptEngine *engine);
5612
+QScriptValue SaltTable_findData(QScriptContext *context, QScriptEngine *engine);
5559
 
5613
 
5560
 @ There are times when it is useful to obtain the sum of values in a column of
5614
 @ There are times when it is useful to obtain the sum of values in a column of
5561
 a SaltTable object. For example, when a column represents the weight of the
5615
 a SaltTable object. For example, when a column represents the weight of the
5686
 	return retval;
5740
 	return retval;
5687
 }
5741
 }
5688
 
5742
 
5743
+@ There are times when it is useful to clear the content of a table. This is
5744
+used, for example, in the green coffees table after changing the roasted coffee
5745
+item to eliminate excess rows in the case where the previously selected item
5746
+was a pre-roast blend.
5747
+
5748
+@<Functions for scripting@>=
5749
+QScriptValue SaltTable_clear(QScriptContext *context, QScriptEngine *)
5750
+{
5751
+	QTableView *self = getself<QTableView *>(context);
5752
+	SaltModel *model = qobject_cast<SaltModel *>(self->model());
5753
+	model->clear();
5754
+	return QScriptValue();
5755
+}
5756
+
5757
+@ It is sometimes useful to remove a row from a table. This is done in the new
5758
+batch window when the coffee for a row is set to a NULL item.
5759
+
5760
+@<Functions for scripting@>=
5761
+QScriptValue SaltTable_removeRow(QScriptContext *context, QScriptEngine *engine)
5762
+{
5763
+	QTableView *self = getself<QTableView *>(context);
5764
+	SaltModel *model = qobject_cast<SaltModel *>(self->model());
5765
+	int row = argument<int>(0, context);
5766
+	return engine->newVariant(model->removeRow(row));
5767
+}
5768
+
5769
+@ To remove the correct row, it is sometimes useful to query the table for
5770
+special values. This is done with the |findData()| method on the underlying
5771
+model.
5772
+
5773
+@<Functions for scripting@>=
5774
+QScriptValue SaltTable_findData(QScriptContext *context, QScriptEngine *engine)
5775
+{
5776
+	QTableView *self = getself<QTableView *>(context);
5777
+	SaltModel *model = qobject_cast<SaltModel *>(self->model());
5778
+	QVariant value = argument<QVariant>(0, context);
5779
+	int column = argument<int>(1, context);
5780
+	return engine->newVariant(model->findData(value, column));
5781
+}
5782
+
5689
 @ These functions need to be added as properties of the table when it is passed
5783
 @ These functions need to be added as properties of the table when it is passed
5690
 to the host environment.
5784
 to the host environment.
5691
 
5785
 
5705
 	value.setProperty("data", engine->newFunction(SaltTable_data));
5799
 	value.setProperty("data", engine->newFunction(SaltTable_data));
5706
 	value.setProperty("model", engine->newFunction(SaltTable_model));
5800
 	value.setProperty("model", engine->newFunction(SaltTable_model));
5707
 	value.setProperty("setData", engine->newFunction(SaltTable_setData));
5801
 	value.setProperty("setData", engine->newFunction(SaltTable_setData));
5802
+	value.setProperty("clear", engine->newFunction(SaltTable_clear));
5803
+	value.setProperty("removeRow", engine->newFunction(SaltTable_removeRow));
5804
+	value.setProperty("findData", engine->newFunction(SaltTable_findData));
5708
 }
5805
 }
5709
 
5806
 
5710
 @ The |SqlComboBox| is another class that is not constructed from scripts but is
5807
 @ The |SqlComboBox| is another class that is not constructed from scripts but is
5719
 QScriptValue QComboBox_addItem(QScriptContext *context, QScriptEngine *engine);
5816
 QScriptValue QComboBox_addItem(QScriptContext *context, QScriptEngine *engine);
5720
 QScriptValue QComboBox_setModel(QScriptContext *context, QScriptEngine *engine);
5817
 QScriptValue QComboBox_setModel(QScriptContext *context, QScriptEngine *engine);
5721
 QScriptValue QComboBox_findText(QScriptContext *context, QScriptEngine *engine);
5818
 QScriptValue QComboBox_findText(QScriptContext *context, QScriptEngine *engine);
5819
+QScriptValue QComboBox_findData(QScriptContext *context, QScriptEngine *engine);
5722
 
5820
 
5723
 @ These functions should seem familiar by now.
5821
 @ These functions should seem familiar by now.
5724
 
5822
 
5736
 	value.setProperty("addItem", engine->newFunction(QComboBox_addItem));
5834
 	value.setProperty("addItem", engine->newFunction(QComboBox_addItem));
5737
 	value.setProperty("setModel", engine->newFunction(QComboBox_setModel));
5835
 	value.setProperty("setModel", engine->newFunction(QComboBox_setModel));
5738
 	value.setProperty("findText", engine->newFunction(QComboBox_findText));
5836
 	value.setProperty("findText", engine->newFunction(QComboBox_findText));
5837
+	value.setProperty("findData", engine->newFunction(QComboBox_findData));
5739
 }
5838
 }
5740
 
5839
 
5741
 QScriptValue QComboBox_currentData(QScriptContext *context,
5840
 QScriptValue QComboBox_currentData(QScriptContext *context,
5766
 	return QScriptValue(engine, self->findText(argument<QString>(0, context)));
5865
 	return QScriptValue(engine, self->findText(argument<QString>(0, context)));
5767
 }
5866
 }
5768
 
5867
 
5868
+QScriptValue QComboBox_findData(QScriptContext *context, QScriptEngine *engine)
5869
+{
5870
+	QComboBox *self = getself<QComboBox *>(context);
5871
+	return QScriptValue(engine, self->findData(argument<QVariant>(0, context)));
5872
+}
5873
+
5769
 @i abouttypica.w
5874
 @i abouttypica.w
5770
 
5875
 
5771
 @** A representation of temperature measurements.
5876
 @** A representation of temperature measurements.
11802
 QSqlDatabase Application::database()
11907
 QSqlDatabase Application::database()
11803
 {
11908
 {
11804
 	QString connectionName;
11909
 	QString connectionName;
11805
-	QSqlDatabase connection = QSqlDatabase::database();
11910
+	QSqlDatabase connection =
11911
+		QSqlDatabase::database(QLatin1String(QSqlDatabase::defaultConnection), false);
11806
 	do
11912
 	do
11807
 	{
11913
 	{
11808
 		connectionName = QUuid::createUuid().toString();
11914
 		connectionName = QUuid::createUuid().toString();
11809
 	} while (QSqlDatabase::connectionNames().contains(connectionName));
11915
 	} while (QSqlDatabase::connectionNames().contains(connectionName));
11810
-	return QSqlDatabase::cloneDatabase(connection, connectionName);
11916
+	return QSqlDatabase::cloneDatabase(connection, QString(connectionName));
11811
 }
11917
 }
11812
 
11918
 
11813
 @** Table editor for ordered arrays with SQL relations.
11919
 @** Table editor for ordered arrays with SQL relations.
11884
 		QModelIndex parent(const QModelIndex &index) const;
11990
 		QModelIndex parent(const QModelIndex &index) const;
11885
 		QString arrayLiteral(int column, int role) const;
11991
 		QString arrayLiteral(int column, int role) const;
11886
 		QString quotedArrayLiteral(int column, int role) const;
11992
 		QString quotedArrayLiteral(int column, int role) const;
11993
+		void clear();
11994
+		bool removeRows(int row, int count,
11995
+		                const QModelIndex &parent = QModelIndex());
11996
+		int findData(const QVariant &value, int column, int role = Qt::UserRole);
11887
 };
11997
 };
11888
 
11998
 
11889
 @ The only unique methods in this class are the |arrayLiteral| and
11999
 @ The only unique methods in this class are the |arrayLiteral| and
12154
 	return QModelIndex();
12264
 	return QModelIndex();
12155
 }
12265
 }
12156
 
12266
 
12267
+@ There are some times when it is useful to clear the model data. Note that
12268
+column header data is retained and the table will contain a single empty row
12269
+after this method is called.
12270
+
12271
+@<SaltModel Implementation@>=
12272
+void SaltModel::clear()
12273
+{
12274
+	beginResetModel();
12275
+	modelData.clear();
12276
+	@<Expand the SaltModel@>@;
12277
+	endResetModel();
12278
+}
12279
+
12280
+@ Another commonly useful operation is the ability to remove rows from the
12281
+model. The new batch window uses this feature to eliminate rows in which the
12282
+coffee is set to NULL. Note that if all rows of the model are removed, a new
12283
+empty row will be created.
12284
+
12285
+@<SaltModel Implementation@>=
12286
+bool SaltModel::removeRows(int row, int count,
12287
+                           const QModelIndex &parent)
12288
+{
12289
+	if(parent == QModelIndex())
12290
+	{
12291
+		if(row >= 0 && count > 0 && (row + count - 1) < modelData.size())
12292
+		{
12293
+			beginRemoveRows(parent, row, row + count - 1);
12294
+			for(int i = 0; i < count; i++)
12295
+			{
12296
+				modelData.removeAt(row);
12297
+			}
12298
+			endRemoveRows();
12299
+			if(modelData.size() == 0)
12300
+			{
12301
+				beginInsertRows(parent, 0, 0);
12302
+				@<Expand the SaltModel@>@;
12303
+				endInsertRows();
12304
+			}
12305
+			return true;
12306
+		}
12307
+	}
12308
+	return false;
12309
+}
12310
+
12311
+@ To find the row number for removal operations it is useful to search for
12312
+special values on a given role. The |findData()| method returns the first row
12313
+in which the given value matches for a particular column and a particular role
12314
+or |-1| if no such match exists.
12315
+
12316
+@<SaltModel Implementation@>=
12317
+int SaltModel::findData(const QVariant &value, int column, int role)
12318
+{
12319
+	for(int i = 0; i < modelData.size(); i++)
12320
+	{
12321
+		if(modelData.at(i).size() > column)
12322
+		{
12323
+			if(modelData.at(i).at(column).contains(role))
12324
+			{
12325
+				if(modelData.at(i).at(column).value(role) == value)
12326
+				{
12327
+					return i;
12328
+				}
12329
+			}
12330
+		}
12331
+	}
12332
+	return -1;
12333
+}
12334
+
12157
 @* A Delegate for SQL Relations.
12335
 @* A Delegate for SQL Relations.
12158
 
12336
 
12159
 \noindent The first column of the table view being described is responsible for
12337
 \noindent The first column of the table view being described is responsible for
12179
 	int dataColumn;
12357
 	int dataColumn;
12180
 	int displayColumn;
12358
 	int displayColumn;
12181
 	bool dataColumnShown;
12359
 	bool dataColumnShown;
12360
+	QString specialNullText;
12361
+	QVariant specialNullData;
12182
 	public:@/
12362
 	public:@/
12183
 		SqlComboBox();
12363
 		SqlComboBox();
12184
 		~SqlComboBox();
12364
 		~SqlComboBox();
12188
 		void addSqlOptions(QString query);
12368
 		void addSqlOptions(QString query);
12189
 		void setDataColumn(int column);
12369
 		void setDataColumn(int column);
12190
 		void setDisplayColumn(int column);
12370
 		void setDisplayColumn(int column);
12191
-		void showData(bool show);@t\2@>@/
12371
+		void showData(bool show);
12372
+		void setNullText(QString nullText);
12373
+		void setNullData(QVariant nullData);@t\2@>@/
12192
 }@t\kern-3pt@>;
12374
 }@t\kern-3pt@>;
12193
 
12375
 
12194
 @ In order to make this class work a little more nicely as an item delegate,
12376
 @ In order to make this class work a little more nicely as an item delegate,
12221
 @ Next, there is a need to know if the NULL value may legally be selected. Where
12403
 @ Next, there is a need to know if the NULL value may legally be selected. Where
12222
 this is the case, we generally want this to be inserted first. As the
12404
 this is the case, we generally want this to be inserted first. As the
12223
 |QComboBox| supports storing both display and user data, much of the code is a
12405
 |QComboBox| supports storing both display and user data, much of the code is a
12224
-thin wrapper around calls to the base class.
12406
+thin wrapper around calls to the base class. The text and data for the NULL
12407
+value can be set arbitrarily, which can be useful in certain cases. Note that
12408
+any customization of the NULL text or data must be set before a call to
12409
+|addNullOption()|.
12225
 
12410
 
12226
 @<SqlComboBox Implementation@>=
12411
 @<SqlComboBox Implementation@>=
12227
 void SqlComboBox::addNullOption()
12412
 void SqlComboBox::addNullOption()
12228
 {
12413
 {
12229
-	addItem(tr("Unknown"), QVariant(QVariant::String));
12414
+	addItem(specialNullText, specialNullData);
12415
+}
12416
+
12417
+void SqlComboBox::setNullText(QString nullText)
12418
+{
12419
+	specialNullText = nullText;
12420
+}
12421
+
12422
+void SqlComboBox::setNullData(QVariant nullData)
12423
+{
12424
+	specialNullData = nullData;
12230
 }
12425
 }
12231
 
12426
 
12232
 @ Typically, the SQL query used to populate this widget will request two columns
12427
 @ Typically, the SQL query used to populate this widget will request two columns
12253
 @<SqlComboBox Implementation@>=
12448
 @<SqlComboBox Implementation@>=
12254
 void SqlComboBox::addSqlOptions(QString query)
12449
 void SqlComboBox::addSqlOptions(QString query)
12255
 {
12450
 {
12256
-	SqlQueryConnection *dbquery = new SqlQueryConnection;
12451
+	SqlQueryConnection h;
12452
+	QSqlQuery *dbquery = h.operator->();
12257
 	if(!dbquery->exec(query))
12453
 	if(!dbquery->exec(query))
12258
 	{
12454
 	{
12259
 		QSqlError error = dbquery->lastError();
12455
 		QSqlError error = dbquery->lastError();
12273
 		}
12469
 		}
12274
 		addItem(displayValue, dataValue);
12470
 		addItem(displayValue, dataValue);
12275
 	}
12471
 	}
12276
-	delete dbquery;
12277
 }
12472
 }
12278
 
12473
 
12279
 @ The constructor initializes some private member data. A size policy is also
12474
 @ The constructor initializes some private member data. A size policy is also
12288
 
12483
 
12289
 @<SqlComboBox Implementation@>=
12484
 @<SqlComboBox Implementation@>=
12290
 SqlComboBox::SqlComboBox() :
12485
 SqlComboBox::SqlComboBox() :
12291
-	dataColumn(0), displayColumn(0), dataColumnShown(false)
12486
+	dataColumn(0), displayColumn(0), dataColumnShown(false),
12487
+	specialNullText(tr("Unknown")), specialNullData(QVariant::String)
12292
 {
12488
 {
12293
 	view()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
12489
 	view()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
12294
 }
12490
 }
12562
 		settings.setValue("database/dbname", dbname->text());
12758
 		settings.setValue("database/dbname", dbname->text());
12563
 		settings.setValue("database/user", user->text());
12759
 		settings.setValue("database/user", user->text());
12564
 		settings.setValue("database/password", password->text());
12760
 		settings.setValue("database/password", password->text());
12761
+		database.close();
12565
 		accept();
12762
 		accept();
12566
 	}
12763
 	}
12567
 	else
12764
 	else
12597
 {
12794
 {
12598
 	settings.setValue("database/exists", "false");
12795
 	settings.setValue("database/exists", "false");
12599
 }
12796
 }
12797
+else
12798
+{
12799
+	database.close();
12800
+}
12600
 
12801
 
12601
 @** Viewing a record of batches.
12802
 @** Viewing a record of batches.
12602
 
12803
 
12721
 
12922
 
12722
 @ The other functions are wrappers around model methods.
12923
 @ The other functions are wrappers around model methods.
12723
 
12924
 
12724
-\danger |setQuery()| leaks database connections.
12725
-
12726
 @<SqlQueryView implementation@>=
12925
 @<SqlQueryView implementation@>=
12727
 void SqlQueryView::setQuery(const QString &query)
12926
 void SqlQueryView::setQuery(const QString &query)
12728
 {
12927
 {
12730
 	database.open();
12929
 	database.open();
12731
 	QSqlQuery q(query, database);
12930
 	QSqlQuery q(query, database);
12732
 	((QSqlQueryModel*)model())->setQuery(q);
12931
 	((QSqlQueryModel*)model())->setQuery(q);
12932
+	database.close();
12733
 }
12933
 }
12734
 
12934
 
12735
 bool SqlQueryView::setHeaderData(int section, Qt::Orientation@, orientation,
12935
 bool SqlQueryView::setHeaderData(int section, Qt::Orientation@, orientation,
12841
 type is highly inefficient. There are many optimizations available if this
13041
 type is highly inefficient. There are many optimizations available if this
12842
 becomes problematic.\endanger
13042
 becomes problematic.\endanger
12843
 
13043
 
13044
+When a report menu is generated, the directory used for this is added as a
13045
+search path for the |"reports"| prefix. This is used by the |createReport()|
13046
+script method and is intended to allow access to reports from outside of the
13047
+Report menu.
13048
+
12844
 @<Populate reports menu@>=
13049
 @<Populate reports menu@>=
12845
 QSettings settings;
13050
 QSettings settings;
12846
-QDir directory(QString("%1/%2").arg(settings.value("config").toString()).
12847
-                                arg(element.attribute("src")));
13051
+QString reportDirectory = QString("%1/%2").arg(settings.value("config").
13052
+                                               toString()).
13053
+                                           arg(element.attribute("src"));
13054
+QDir::addSearchPath("reports", reportDirectory);
13055
+QDir directory(reportDirectory);
12848
 directory.setFilter(QDir::Files);
13056
 directory.setFilter(QDir::Files);
12849
 directory.setSorting(QDir::Name);
13057
 directory.setSorting(QDir::Name);
12850
 QStringList nameFilter;
13058
 QStringList nameFilter;
13191
 change to the table and the next child element, if any, will be processed.
13399
 change to the table and the next child element, if any, will be processed.
13192
 
13400
 
13193
 @<Add SQL query results to report table@>=
13401
 @<Add SQL query results to report table@>=
13194
-SqlQueryConnection query;
13195
-query.prepare(currentElement.text());
13402
+SqlQueryConnection h;
13403
+QSqlQuery *query = h.operator->();
13404
+query->prepare(currentElement.text());
13196
 foreach(QString key, bindings.uniqueKeys())
13405
 foreach(QString key, bindings.uniqueKeys())
13197
 {
13406
 {
13198
 	if(currentElement.text().contains(key))
13407
 	if(currentElement.text().contains(key))
13199
 	{
13408
 	{
13200
-		query.bindValue(key, bindings.value(key));
13409
+		query->bindValue(key, bindings.value(key));
13201
 	}
13410
 	}
13202
 }
13411
 }
13203
-query.exec();
13204
-if(!query.next())
13412
+query->exec();
13413
+if(!query->next())
13205
 {
13414
 {
13206
 	continue;
13415
 	continue;
13207
 }
13416
 }
13208
-if(query.record().count() > columns)
13417
+if(query->record().count() > columns)
13209
 {
13418
 {
13210
-	table->appendColumns(query.record().count() - columns);
13419
+	table->appendColumns(query->record().count() - columns);
13211
 }
13420
 }
13212
 do
13421
 do
13213
 {
13422
 {
13214
 	table->appendRows(1);
13423
 	table->appendRows(1);
13215
 	rows++;
13424
 	rows++;
13216
 	currentRow++;
13425
 	currentRow++;
13217
-	for(int j = 0; j < query.record().count(); j++)
13426
+	for(int j = 0; j < query->record().count(); j++)
13218
 	{
13427
 	{
13219
 		QTextTableCell cell = table->cellAt(currentRow, j);
13428
 		QTextTableCell cell = table->cellAt(currentRow, j);
13220
 		cursor = cell.firstCursorPosition();
13429
 		cursor = cell.firstCursorPosition();
13221
-		cursor.insertText(query.value(j).toString());
13430
+		cursor.insertText(query->value(j).toString());
13222
 	}
13431
 	}
13223
-} while(query.next());
13432
+} while(query->next());
13224
 
13433
 
13225
 @ It is sometimes desirable to add fixed data such as column headers to a table.
13434
 @ It is sometimes desirable to add fixed data such as column headers to a table.
13226
 This is done with the {\tt <row>} element.
13435
 This is done with the {\tt <row>} element.

Loading…
Cancel
Save