Typica is a free program for professional coffee roasters. https://typica.us
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <window id="batchWindow">
  2. <menu name="Batch">
  3. <item id="new" shortcut="Ctrl+N">New Batch…</item>
  4. </menu>
  5. <layout type="horizontal">
  6. <layout type="vertical">
  7. <layout type="horizontal">
  8. <label>Machine:</label>
  9. <line id="machine" writable="false" />
  10. <label>Unit:</label>
  11. <sqldrop id="unit" />
  12. <stretch />
  13. </layout>
  14. <layout type="horizontal">
  15. <label>Roasted Coffee:</label>
  16. <sqldrop data="0" display="1" showdata="true" id="roasted">
  17. <null />
  18. <query>SELECT id, name FROM items WHERE category = 'Coffee: Roasted' AND id IN (SELECT item FROM current_items) ORDER BY name</query>
  19. </sqldrop>
  20. <stretch />
  21. </layout>
  22. <label>Green Coffee:</label>
  23. <sqltablearray columns="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>
  25. <column name="Weight" />
  26. </sqltablearray>
  27. <layout type="horizontal">
  28. <label>Green Weight:</label>
  29. <line id="green" writable="false">0.0</line>
  30. </layout>
  31. <layout type="horizontal">
  32. <button name="Load Profile" type="push" id="load" />
  33. <button name="No Profile" type="push" id="noprofile" />
  34. </layout>
  35. <layout type="horizontal">
  36. <label>Time:</label>
  37. <line id="time" writable="false" />
  38. <label>Duration:</label>
  39. <line id="duration" writable="false" />
  40. </layout>
  41. <layout type="horizontal">
  42. <label>Roasted Weight:</label>
  43. <line id="roast" validator="numeric" />
  44. <label>Weight Loss:</label>
  45. <line id="wloss" writable="false" />
  46. <button type="check" id="approval" name="Approved" />
  47. </layout>
  48. <layout type="horizontal">
  49. <label>Annotation:</label>
  50. <textarea id="annotation" />
  51. </layout>
  52. <layout type="horizontal">
  53. <button name="Submit" id="submit" type="push" />
  54. <button name="Save log as target profile" type="check" id="target" />
  55. </layout>
  56. </layout>
  57. <layout type="vertical">
  58. <label>Connected Scales</label>
  59. <layout type="vertical" id="scales" />
  60. <stretch />
  61. </layout>
  62. </layout>
  63. <program>
  64. <![CDATA[
  65. var unitBox = findChildObject(this, 'unit');
  66. unitBox.addItem("g");
  67. unitBox.addItem("Kg");
  68. unitBox.addItem("oz");
  69. unitBox.addItem("lb");
  70. unitBox.currentIndex = (QSettings.value("script/batch_unit", unitBox.findText("lb")));
  71. var machine = findChildObject(this, "machine");
  72. machine.setText(selectedRoasterName + " (" + selectedRoasterID + ")");
  73. var newMenu = findChildObject(this, 'new');
  74. newMenu.triggered.connect(function() {
  75. var bwindow = createWindow("batchWindow");
  76. bwindow.windowTitle = "Typica - [*]New Batch";
  77. });
  78. var batch = this;
  79. var table = findChildObject(this, 'greens');
  80. var green = findChildObject(this, 'green');
  81. var model = table.model();
  82. var lossField = findChildObject(this, 'wloss');
  83. lossField.maximumWidth = 80;
  84. var roasted = findChildObject(this, 'roasted');
  85. var roastwt = findChildObject(this, 'roast');
  86. roastwt.maximumWidth = 80;
  87. var scalesLayout = findChildObject(this, 'scales');
  88. scalesLayout.spacing = 10;
  89. if(navigationwindow.loggingWindow.scales.length > 0) {
  90. for(var i = 0; i < navigationwindow.loggingWindow.scales.length; i++) {
  91. var scale = navigationwindow.loggingWindow.scales[i];
  92. var label = new DragLabel();
  93. var weighButton = new QPushButton();
  94. weighButton.text = "Weigh";
  95. weighButton.clicked.connect(scale.weigh);
  96. label.updateMeasurement = function(m, u) {
  97. switch(unitBox.currentIndex) {
  98. case 0:
  99. this.text = Units.convertWeight(m, u, Units.Gram).toFixed(1);
  100. break;
  101. case 1:
  102. this.text = Units.convertWeight(m, u, Units.Kilogram).toFixed(4);
  103. break;
  104. case 2:
  105. this.text = Units.convertWeight(m, u, Units.Ounce).toFixed(3);
  106. break;
  107. case 3:
  108. this.text = Units.convertWeight(m, u, Units.Pound).toFixed(4);
  109. break;
  110. }
  111. };
  112. scalesLayout.addWidget(label);
  113. scalesLayout.addWidget(weighButton);
  114. scale.newMeasurement.connect(function(m, u) {
  115. label.updateMeasurement(m, u);
  116. });
  117. scale.weigh();
  118. unitBox['currentIndexChanged(int)'].connect(scale.weigh);
  119. }
  120. }
  121. model.dataChanged.connect(function() {
  122. green.text = table.columnSum(1, 0);
  123. table.resizeColumnToContents(0);
  124. if(parseFloat(green.text) > 0)
  125. {
  126. if(parseFloat(roastwt.text) > 0)
  127. {
  128. lossField.text = (((parseFloat(green.text) - parseFloat(roastwt.text)) / parseFloat(green.text)) * 100).toFixed(2) + "%";
  129. }
  130. else
  131. {
  132. lossField.text = "100%";
  133. }
  134. }
  135. });
  136. roastwt.textChanged.connect(function() {
  137. if(parseFloat(green.text) > 0)
  138. {
  139. if(parseFloat(roastwt.text) > 0)
  140. {
  141. lossField.text = (((parseFloat(green.text) - parseFloat(roastwt.text)) / parseFloat(green.text)) * 100).toFixed(2) + "%";
  142. }
  143. else
  144. {
  145. lossField.text = "100%";
  146. }
  147. }
  148. });
  149. var convertToPounds = function(w, u) {
  150. switch(u) {
  151. case "g":
  152. return w * 0.0022;
  153. case "oz":
  154. return w * 0.0625;
  155. case "Kg":
  156. return w * 2.2;
  157. }
  158. return w;
  159. };
  160. var profilebutton = findChildObject(this, 'load');
  161. profilebutton.setEnabled(false);
  162. roasted['currentIndexChanged(int)'].connect(function() {
  163. var query = new QSqlQuery();
  164. var q = "SELECT EXISTS(SELECT 1 FROM item_files WHERE item = ";
  165. q = q + roasted.currentData();
  166. q = q + ")";
  167. query.exec(q);
  168. if(query.next())
  169. {
  170. if(query.value(0) == 'false')
  171. {
  172. profilebutton.setEnabled(false);
  173. }
  174. else
  175. {
  176. profilebutton.setEnabled(true);
  177. }
  178. }
  179. else
  180. {
  181. profilebutton.setEnabled(false);
  182. }
  183. var title = "Typica - [*]New Batch (";
  184. title = title + roasted.currentText;
  185. title = title + ")";
  186. batch.windowTitle = title;
  187. q = "SELECT unroasted_id FROM roasting_log WHERE roasted_id = ";
  188. q = q + roasted.currentData();
  189. q = q + " AND time = (SELECT max(time) FROM roasting_log WHERE roasted_id = ";
  190. q = q + roasted.currentData();
  191. q = q + ")";
  192. query.exec(q);
  193. if(query.next())
  194. {
  195. var unroasted_items = sqlToArray(query.value(0));
  196. var names = [];
  197. q = "SELECT name FROM items WHERE id = :id AND quantity <> 0";
  198. query.prepare(q);
  199. var allInStock = true;
  200. for(var i = 0; i < unroasted_items.length; i++)
  201. {
  202. query.bind("id", unroasted_items[i]);
  203. query.exec();
  204. if(query.next())
  205. {
  206. names[i] = query.value(0);
  207. }
  208. else
  209. {
  210. allInStock = false;
  211. }
  212. }
  213. if(allInStock)
  214. {
  215. for(var i = 0; i < unroasted_items.length; i++)
  216. {
  217. table.setData(i, 0, names[i], 0);
  218. table.setData(i, 0, unroasted_items[i], 32);
  219. }
  220. }
  221. }
  222. });
  223. profilebutton.clicked.connect(function() {
  224. batch.windowModified = true;
  225. currentBatchInfo = batch;
  226. query = new QSqlQuery();
  227. var q = "SELECT files FROM item_files WHERE item = :item AND time = (SELECT max(time) FROM item_files WHERE item = :again)";
  228. query.prepare(q);
  229. query.bind(":item", Number(roasted.currentData()));
  230. query.bind(":again", Number(roasted.currentData()));
  231. query.exec();
  232. if(query.next())
  233. {
  234. var files = query.value(0);
  235. files = files.replace("{", "(");
  236. files = files.replace("}", ")");
  237. q = "SELECT file, name FROM files WHERE id IN ";
  238. q = q + files;
  239. q = q + " AND type = 'profile'";
  240. query.exec(q);
  241. if(query.next())
  242. {
  243. var targetseries = -1;
  244. var buffer = new QBuffer(query.value(0));
  245. var pname = query.value(1);
  246. var input = new XMLInput(buffer, 1);
  247. var graph = findChildObject(navigationwindow.loggingWindow, 'graph');
  248. var log = findChildObject(navigationwindow.loggingWindow, 'log');
  249. log.clear();
  250. graph.clear();
  251. input.newTemperatureColumn.connect(log.setHeaderData);
  252. input.newTemperatureColumn.connect(function(col, text) {
  253. if(text == navigationwindow.loggingWindow.targetcolumnname)
  254. {
  255. targetseries = col;
  256. }
  257. });
  258. input.newAnnotationColumn.connect(log.setHeaderData);
  259. input.measure.connect(graph.newMeasurement);
  260. input.measure.connect(log.newMeasurement);
  261. input.measure.connect(function(data, series) {
  262. if(series == targetseries)
  263. {
  264. targetDetector.newMeasurement(data);
  265. }
  266. });
  267. input.annotation.connect(log.newAnnotation);
  268. var lc;
  269. input.lastColumn.connect(function(c) {
  270. lc = c;
  271. QSettings.setValue("liveColumn", c + 1);
  272. navigationwindow.loggingWindow.postLoadColumnSetup(c)
  273. });
  274. }
  275. }
  276. query = query.invalidate();
  277. navigationwindow.loggingWindow.windowTitle = "Typica - [*]" + pname;
  278. navigationwindow.loggingWindow.raise();
  279. navigationwindow.loggingWindow.activateWindow();
  280. input.input();
  281. log.newAnnotation("End", 1, lc);
  282. });
  283. var noprofilebutton = findChildObject(this, 'noprofile');
  284. noprofilebutton.clicked.connect(function() {
  285. batch.windowModified = true;
  286. currentBatchInfo = batch;
  287. navigationwindow.loggingWindow.raise();
  288. navigationwindow.loggingWindow.activateWindow();
  289. });
  290. var submitbutton = findChildObject(this, 'submit');
  291. var timefield = findChildObject(this, 'time');
  292. var notes = findChildObject(this, 'annotation');
  293. var duration = findChildObject(this, 'duration');
  294. var approval = findChildObject(this, 'approval');
  295. var target = findChildObject(this, 'target');
  296. submitbutton.clicked.connect(function() {
  297. checkQuery = new QSqlQuery();
  298. checkQuery.exec("SELECT 1 FROM machine WHERE id = " + selectedRoasterID);
  299. if(!checkQuery.next())
  300. {
  301. checkQuery.prepare("INSERT INTO machine VALUES(:id, :name)");
  302. checkQuery.bind(":id", selectedRoasterID);
  303. checkQuery.bind(":name", selectedRoasterName);
  304. checkQuery.exec();
  305. }
  306. checkQuery = checkQuery.invalidate();
  307. var q = "INSERT INTO files VALUES(default, :name, 'profile', NULL, :data) RETURNING id";
  308. query = new QSqlQuery();
  309. query.prepare(q);
  310. query.bind(":name", timefield.text + " " + roasted.currentText);
  311. query.bindFileData(":data", batch.tempData);
  312. query.exec();
  313. query.next();
  314. var fileno = query.value(0);
  315. var file = new QFile(batch.tempData);
  316. file.remove();
  317. var q2 = "INSERT INTO roasting_log VALUES(:time, ";
  318. q2 = q2 + table.columnArray(0, 32);
  319. q2 = q2 + ", ";
  320. for(var i = 0; table.data(i, 1, 0).value != ""; i++)
  321. {
  322. table.setData(i, 1, convertToPounds(parseFloat(table.data(i, 1, 0)), unitBox.currentText) ,32)
  323. }
  324. q2 = q2 + table.columnArray(1, 32);
  325. q2 = q2 + ", ";
  326. q2 = q2 + convertToPounds(parseFloat(green.text), unitBox.currentText);
  327. q2 = q2 + ", ";
  328. q2 = q2 + roasted.currentData();
  329. q2 = q2 + ", ";
  330. q2 = q2 + convertToPounds(parseFloat(roastwt.text), unitBox.currentText);
  331. q2 = q2 + ", 'ROAST', :annotation, ";
  332. q2 = q2 + selectedRoasterID;
  333. q2 = q2 + ", :duration, :approval, NULL, NULL, NULL, NULL, '{";
  334. q2 = q2 + fileno;
  335. q2 = q2 + "}')";
  336. query2 = new QSqlQuery();
  337. query2.prepare(q2);
  338. query2.bind(":time", timefield.text);
  339. query2.bind(":annotation", notes.plainText);
  340. query2.bind(":duration", duration.text);
  341. query2.bind(":approval", approval.checked);
  342. query2.exec();
  343. query2 = query2.invalidate();
  344. if(target.checked) {
  345. var q3 = "INSERT INTO item_files VALUES(:time, :item, '{";
  346. q3 = q3 + fileno;
  347. q3 = q3 + "}')";
  348. query.prepare(q3);
  349. query.bind(":time", timefield.text);
  350. query.bind(":item", roasted.currentData());
  351. query.exec();
  352. }
  353. query = query.invalidate();
  354. batch.windowModified = false;
  355. batch.close();
  356. });
  357. ]]>
  358. </program>
  359. </window>