Browse Source

Add summary chart, invoice links, and batch links to item transaction report

Neal Wilson 10 years ago
parent
commit
dac3bbfc65
3 changed files with 238 additions and 2 deletions
  1. 117
    2
      config/Reports/itemtransactions.xml
  2. 5
    0
      config/Scripts/d3.min.js
  3. 116
    0
      config/Scripts/greenusechart.js

+ 117
- 2
config/Reports/itemtransactions.xml View File

@@ -18,6 +18,7 @@
18 18
 	</menu>
19 19
 	<program>
20 20
 		<![CDATA[
21
+			var externpath = "file:///home/neal/TCS/";
21 22
 			this.windowTitle = "Typica - Item Transactions";
22 23
 			var itemBox = findChildObject(this, 'item');
23 24
 			var unitBox = findChildObject(this, 'unit');
@@ -46,6 +47,9 @@
46 47
 				output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
47 48
 				output.writeStartElement("head");
48 49
 				output.writeTextElement("title", "Item Transactions");
50
+				output.writeStartElement("script");
51
+				output.writeAttribute("src", "Scripts/d3.min.js");
52
+				output.writeEndElement();
49 53
 				output.writeStartElement("style");
50 54
 				output.writeAttribute("type", "text/css");
51 55
 				output.writeCDATA("tr.PURCHASE {background-color: #77FF77}");
@@ -126,7 +130,48 @@
126 130
 					}
127 131
 					output.writeEndElement() // table
128 132
 					
129
-					query.prepare("SELECT time::date, type, quantity / :c1, balance / :c2 FROM item_history(:item)");
133
+					output.writeStartElement("div");
134
+					output.writeAttribute("id", "chart");
135
+					output.writeEndElement();
136
+					
137
+					query.prepare("WITH q AS (SELECT roasted_id, unroasted_id, unroasted_quantity, unroasted_total_quantity, roasted_quantity, generate_subscripts(unroasted_quantity, 1) AS s FROM roasting_log) SELECT (SELECT name FROM items WHERE id = roasted_id) AS name, roasted_id, SUM(unroasted_quantity[s]) AS total, COUNT(unroasted_quantity[s]), SUM((unroasted_quantity[s]/unroasted_total_quantity)*roasted_quantity)::numeric(12,3) AS roast_proportion FROM q WHERE unroasted_id[s] = :item1 GROUP BY roasted_id UNION SELECT 'Green Sales', NULL, SUM(quantity), COUNT(1), NULL FROM sale WHERE item = :item2 UNION SELECT 'Inventory Adjustment', NULL, ((SELECT SUM(quantity) FROM purchase WHERE item = :item3) - (SELECT quantity FROM items WHERE id = :item4) - (SELECT SUM(quantity) FROM all_transactions WHERE type != 'PURCHASE' AND type != 'INVENTORY' AND item = :item5)), (SELECT COUNT(1) FROM inventory WHERE item = :item6), NULL UNION SELECT 'Loss', NULL, SUM(quantity), COUNT(1), NULL FROM loss WHERE item = :item7 UNION SELECT 'Current Inventory', NULL, (SELECT quantity FROM items WHERE id = :item8), NULL, NULL ORDER BY total DESC");
138
+					query.bind(":item1", itemBox.currentData());
139
+					query.bind(":item2", itemBox.currentData());
140
+					query.bind(":item3", itemBox.currentData());
141
+					query.bind(":item4", itemBox.currentData());
142
+					query.bind(":item5", itemBox.currentData());
143
+					query.bind(":item6", itemBox.currentData());
144
+					query.bind(":item7", itemBox.currentData());
145
+					query.bind(":item8", itemBox.currentData());
146
+					query.exec();
147
+					var chartData = "var data = [";
148
+					var roastedCoffeeLines = "";
149
+					var adjustmentLines = "";
150
+					var currentInventoryLine = "";
151
+					var conversion = 1;
152
+					if(unitBox.currentIndex == 0) {
153
+						conversion = 2.2;
154
+					}
155
+					while(query.next()) {
156
+						if(Number(query.value(1)) > 0) {
157
+							roastedCoffeeLines += "['" + query.value(0).replace(/\'/g, "\\x27") + "'," + query.value(2) / conversion + "," + query.value(3) + "," + query.value(4) / conversion + "],";
158
+						} else if (query.value(0) == "Current Inventory") {
159
+							currentInventoryLine = "['Current Inventory'," + query.value(2) / conversion + "," + query.value(3) + "," + query.value(4) / conversion + "]";
160
+						} else {
161
+							if(Number(query.value(3)) > 0) {
162
+								adjustmentLines += "['" + query.value(0) + "'," + query.value(2) / conversion + "," + query.value(3) + "," + query.value(4) / conversion + "],";
163
+							}
164
+						}
165
+					}
166
+					chartData = chartData + roastedCoffeeLines + adjustmentLines + currentInventoryLine + "];";
167
+					
168
+					output.writeTextElement("script", chartData);
169
+					
170
+					output.writeStartElement("script");
171
+					output.writeAttribute("src", "Scripts/greenusechart.js");
172
+					output.writeEndElement();
173
+					
174
+					query.prepare("SELECT time::date, type, quantity / :c1, balance / :c2, (SELECT files FROM roasting_log WHERE roasting_log.time = item_history.time AND item = ANY(unroasted_id)), (SELECT invoice_id FROM invoice_items WHERE item = item_id AND item_history.type = 'PURCHASE'), (SELECT vendor || ' ' || invoice FROM invoices WHERE id = (SELECT invoice_id FROM invoice_items WHERE item = item_id AND item_history.type = 'PURCHASE')), (SELECT name FROM items WHERE id = (SELECT roasted_id FROM roasting_log WHERE roasting_log.time = item_history.time AND item = ANY(unroasted_id))) FROM item_history(:item)");
130 175
 					switch(unitBox.currentIndex)
131 176
 					{
132 177
 						case 0:
@@ -146,6 +191,7 @@
146 191
 					output.writeTextElement("th", "Type");
147 192
 					output.writeTextElement("th", "Quantity");
148 193
 					output.writeTextElement("th", "Balance");
194
+					output.writeTextElement("th", "Record");
149 195
 					output.writeEndElement(); // tr
150 196
 					var prev_balance = "0";
151 197
 					var prev_prec = 0;
@@ -175,6 +221,23 @@
175 221
 						}
176 222
 						output.writeTextElement("td", query.value(3));
177 223
 						prev_balance = query.value(3);
224
+						if(query.value(1) == "PURCHASE") {
225
+							output.writeStartElement("td");
226
+							output.writeStartElement("a");
227
+							output.writeAttribute("href", "typica://script/i" + query.value(5));
228
+							output.writeCDATA(query.value(6) + " (" + query.value(5) + ")");
229
+							output.writeEndElement();
230
+							output.writeEndElement();
231
+						} else if(query.value(1) == "USE") {
232
+							output.writeStartElement("td");
233
+							output.writeStartElement("a");
234
+							output.writeAttribute("href", "typica://script/p" + query.value(4).slice(1,-1));
235
+							output.writeCDATA(query.value(7) + " " + query.value(4));
236
+							output.writeEndElement();
237
+							output.writeEndElement();
238
+						} else {
239
+							output.writeTextElement("td", "");
240
+						}
178 241
 						output.writeEndElement(); // tr
179 242
 					}
180 243
 					output.writeEndElement(); // table
@@ -193,7 +256,59 @@
193 256
 				buffer.close();
194 257
 				query = query.invalidate();
195 258
 			}
196
-			refresh();
259
+			if(itemBox.currentData() > 0) {
260
+				refresh();
261
+			}
262
+			
263
+			/* Open invoices */
264
+			var openInvoice = function(url) {
265
+				var arg = url.slice(1, url.length);
266
+				var info = createWindow("invoiceinfo");
267
+				info.setInvoiceID(arg);
268
+				var query = new QSqlQuery();
269
+				query.exec("SELECT time, invoice, vendor FROM invoices WHERE id = " + arg);
270
+				query.next();
271
+				var timefield = findChildObject(info, 'date');
272
+				timefield.text = query.value(0);
273
+				var vendorfield = findChildObject(info, 'vendor');
274
+				vendorfield.text = query.value(2);
275
+				var invoicefield = findChildObject(info, 'invoice');
276
+				invoicefield.text = query.value(1);
277
+				var itemtable = findChildObject(info, 'itemtable');
278
+				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");
279
+				query = query.invalidate();
280
+			};
281
+			
282
+			/* Open batch data */
283
+			var openProfile = function(url) {
284
+				var arg = url.slice(1, url.length);
285
+				var details = createWindow("batchDetails");
286
+				var fakeTable = new Object;
287
+				fakeTable.holding = new Array(7);
288
+				fakeTable.data = function(r, c) {
289
+					return this.holding[c];
290
+				};
291
+				var query = new QSqlQuery();
292
+				query.exec("SELECT time, machine, (SELECT name FROM items WHERE id = roasted_id) AS name, unroasted_total_quantity AS green, roasted_quantity AS roasted, ((unroasted_total_quantity - roasted_quantity) / unroasted_total_quantity * 100::numeric)::numeric(12,2) AS weight_loss, duration, annotation FROM roasting_log WHERE files = '{" + arg + "}'");
293
+				query.next();
294
+				for(var i = 0; i < 8; i++) {
295
+					fakeTable.holding[i] = query.value(i);
296
+				}
297
+				query = query.invalidate();
298
+				details.loadData(fakeTable, 0);
299
+			};
300
+			
301
+			view.scriptLinkClicked.connect(function(url) {
302
+				var linkType = url[0];
303
+				switch(linkType) {
304
+					case 'i':
305
+						openInvoice(url);
306
+						break;
307
+					case 'p':
308
+						openProfile(url);
309
+						break;
310
+				}
311
+			});
197 312
 		]]>
198 313
 	</program>
199 314
 </window>

+ 5
- 0
config/Scripts/d3.min.js
File diff suppressed because it is too large
View File


+ 116
- 0
config/Scripts/greenusechart.js View File

@@ -0,0 +1,116 @@
1
+// Data access functions
2
+var label = function(d) {return d[0];};
3
+var greenValue = function(d) {return d[1];};
4
+var transactionCount = function(d) {return d[2];};
5
+var roastValue = function(d) {return d[3];};
6
+
7
+// Chart parameters
8
+var valueLabelWidth = 0; // Temporary, will be updated when labels are generated
9
+var barHeight = 30;
10
+var barHeight2 = barHeight / 2;
11
+var barLabelWidth = 0;
12
+var barLabelPadding = 5;
13
+var gridLabelHeight = 18;
14
+var gridChartOffset = 3;
15
+var maxBarWidth = 500;
16
+
17
+// Scales
18
+var x1s = d3.scale.linear().domain([0, d3.max(data, function(d) { return greenValue(d); })*1.15]).range([0, maxBarWidth]);
19
+var x2s = d3.scale.linear().domain([0, d3.max(data, function(d) { return transactionCount(d); })*1.15]).range([0, maxBarWidth]);
20
+var ys = d3.scale.ordinal().domain(d3.range(0, data.length)).rangeBands([0, data.length * barHeight]);
21
+var ytext = function(d, i) {return ys(i) + ys.rangeBand() / 2;};
22
+var ybar = function(d, i) {return ys(i);};
23
+var ybar2 = function(d, i) {return ys(i) + (barHeight2 / 2)};
24
+var ybar3 = function(d, i) {return ys(i);};
25
+var ybar4 = function(d, i) {return ys(i) + barHeight;};
26
+
27
+// Chart
28
+var svg = d3.select("#chart").append("svg")
29
+	.attr("width", 1000)
30
+	.attr("height", gridLabelHeight + gridLabelHeight + gridChartOffset + data.length * barHeight);
31
+
32
+// Bar labels
33
+var yxe = svg.append("g")
34
+	.attr("class", "y axis left");
35
+yxe.selectAll("text").data(data).enter().append("text")
36
+	.attr("y", ytext)
37
+	.attr("stroke", "none")
38
+	.attr("fill", "black")
39
+	.attr("dy", ".35em")
40
+	.attr("text-anchor", "end")
41
+	.text(label);
42
+// Determine maximum label width
43
+yxe.selectAll("text").each(function() {
44
+	if(barLabelWidth < this.getComputedTextLength()) {
45
+		barLabelWidth = this.getComputedTextLength();
46
+	}
47
+});
48
+barLabelWidth += 10;
49
+yxe.attr("transform", "translate(" + (barLabelWidth - barLabelPadding) + "," + (gridLabelHeight + gridChartOffset) + ")");
50
+
51
+// Weight axis
52
+var x1xe = svg.append("g")
53
+	.attr("class", "x axis top")
54
+	.attr("transform", "translate(" + barLabelWidth + "," + gridLabelHeight + ")");
55
+	
56
+x1xe.selectAll("text").data(x1s.ticks(10)).enter().append("text")
57
+	.attr("x", x1s)
58
+	.attr("dy", -3)
59
+	.attr("text-anchor", "middle")
60
+	.text(String);
61
+// Top ticks extend approximately half way down the chart
62
+x1xe.selectAll("line").data(x1s.ticks(10)).enter().append("line")
63
+	.attr("x1", x1s)
64
+	.attr("x2", x1s)
65
+	.attr("y1", 0)
66
+	.attr("y2", ys.rangeExtent()[1] /2)
67
+	.style("stroke", "#ccc");
68
+
69
+// Transaction count axis
70
+var x2xe = svg.append("g")
71
+	.attr("class", "x axis bottom")
72
+	.attr("transform", "translate(" + barLabelWidth + "," + (ys.rangeExtent()[1] + gridChartOffset + gridLabelHeight + gridLabelHeight) + ")");
73
+
74
+x2xe.selectAll("text").data(x2s.ticks(10)).enter().append("text")
75
+	.attr("x", x2s)
76
+	.attr("dy", -3)
77
+	.attr("text-anchor", "middle")
78
+	.text(String);
79
+// Bottom ticks extend approximately half way up the chart	
80
+x2xe.selectAll("line").data(x2s.ticks(10)).enter().append("line")
81
+	.attr("x1", x2s)
82
+	.attr("x2", x2s)
83
+	.attr("y1", -gridLabelHeight - (ys.rangeExtent()[1] /2))
84
+	.attr("y2", -gridLabelHeight)
85
+	.style("stroke", "#ccc");
86
+
87
+// Green coffee bars
88
+var gbars = svg.append("g")
89
+	.attr("transform", "translate(" + barLabelWidth + "," + (gridLabelHeight + gridChartOffset) + ")");
90
+gbars.selectAll("rect").data(data).enter().append("rect")
91
+	.attr("y", ybar)
92
+	.attr("height", barHeight)
93
+	.attr("width", function(d) {return x1s(greenValue(d));})
94
+	.attr("stroke", "white")
95
+	.attr("fill", "royalblue");
96
+
97
+// Roasted coffee bars
98
+var rbars = svg.append("g")
99
+	.attr("transform", "translate(" + barLabelWidth + "," + (gridLabelHeight + gridChartOffset) + ")");
100
+rbars.selectAll("rect").data(data).enter().append("rect")
101
+	.attr("y", ybar2)
102
+	.attr("height", barHeight2)
103
+	.attr("width", function(d) {return x1s(roastValue(d));})
104
+	.attr("stroke", "white")
105
+	.attr("fill", "orangered");
106
+
107
+// Transaction ticks
108
+var tticks = svg.append("g")
109
+	.attr("transform", "translate(" + barLabelWidth + "," + (gridLabelHeight + gridChartOffset) + ")");
110
+tticks.selectAll("line").data(data).enter().append("line")
111
+	.attr("x1", function(d) {return x2s(transactionCount(d));})
112
+	.attr("x2", function(d) {return x2s(transactionCount(d));})
113
+	.attr("y1", ybar3)
114
+	.attr("y2", ybar4)
115
+	.attr("stroke-width", "3")
116
+	.style("stroke", "black");

Loading…
Cancel
Save