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

newbatch.xml 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  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="3" id="greens">
  24. <column name="Coffee" delegate="sql" showdata="true" null="true" nulltext="Delete" nulldata="delete" data="0" display="1">SELECT id, name FROM coffees WHERE quantity &lt;&gt; 0 ORDER BY name</column>
  25. <column name="Weight" delegate="numeric" />
  26. <column name="Remaining" />
  27. </sqltablearray>
  28. <layout type="horizontal">
  29. <label>Green Weight:</label>
  30. <line id="green" writable="false">0.0</line>
  31. </layout>
  32. <layout type="horizontal">
  33. <button name="Load Profile" type="push" id="load" />
  34. <button name="No Profile" type="push" id="noprofile" />
  35. </layout>
  36. <layout type="horizontal">
  37. <label>Time:</label>
  38. <line id="time" writable="false" />
  39. <label>Duration:</label>
  40. <line id="duration" writable="false" />
  41. </layout>
  42. <layout type="horizontal">
  43. <label>Roasted Weight:</label>
  44. <line id="roast" validator="numeric" />
  45. <label>Weight Loss:</label>
  46. <line id="wloss" writable="false" />
  47. <button type="check" id="approval" name="Approved" />
  48. </layout>
  49. <layout type="horizontal">
  50. <label>Annotation:</label>
  51. <textarea id="annotation" />
  52. </layout>
  53. <layout type="horizontal">
  54. <button name="Submit" id="submit" type="push" />
  55. <button name="Save log as target profile" type="check" id="target" />
  56. </layout>
  57. </layout>
  58. <layout type="vertical">
  59. <label>Connected Scales</label>
  60. <layout type="vertical" id="scales" />
  61. <stretch />
  62. </layout>
  63. </layout>
  64. <program>
  65. <![CDATA[
  66. var unitBox = findChildObject(this, 'unit');
  67. unitBox.addItem("g");
  68. unitBox.addItem("Kg");
  69. unitBox.addItem("oz");
  70. unitBox.addItem("lb");
  71. unitBox.currentIndex = (QSettings.value("script/batch_unit", unitBox.findText("lb")));
  72. unitBox['currentIndexChanged(int)'].connect(function() {
  73. QSettings.setValue("script/batch_unit", unitBox.currentIndex);
  74. });
  75. var machine = findChildObject(this, "machine");
  76. machine.setText(selectedRoasterName + " (" + selectedRoasterID + ")");
  77. var newMenu = findChildObject(this, 'new');
  78. newMenu.triggered.connect(function() {
  79. var bwindow = createWindow("batchWindow");
  80. bwindow.windowTitle = "Typica - [*]New Batch";
  81. });
  82. var batch = this;
  83. var table = findChildObject(this, 'greens');
  84. var green = findChildObject(this, 'green');
  85. var model = table.model();
  86. var lossField = findChildObject(this, 'wloss');
  87. lossField.maximumWidth = 80;
  88. var roasted = findChildObject(this, 'roasted');
  89. var roastwt = findChildObject(this, 'roast');
  90. roastwt.maximumWidth = 80;
  91. var scalesLayout = findChildObject(this, 'scales');
  92. scalesLayout.spacing = 10;
  93. if(navigationwindow.loggingWindow.scales.length > 0) {
  94. for(var i = 0; i < navigationwindow.loggingWindow.scales.length; i++) {
  95. var scale = navigationwindow.loggingWindow.scales[i];
  96. var label = new DragLabel();
  97. var weighButton = new QPushButton();
  98. weighButton.text = "Weigh";
  99. weighButton.clicked.connect(scale.weigh);
  100. label.updateMeasurement = function(m, u) {
  101. switch(unitBox.currentIndex) {
  102. case 0:
  103. this.text = Units.convertWeight(m, u, Units.Gram).toFixed(1);
  104. break;
  105. case 1:
  106. this.text = Units.convertWeight(m, u, Units.Kilogram).toFixed(4);
  107. break;
  108. case 2:
  109. this.text = Units.convertWeight(m, u, Units.Ounce).toFixed(3);
  110. break;
  111. case 3:
  112. this.text = Units.convertWeight(m, u, Units.Pound).toFixed(4);
  113. break;
  114. }
  115. };
  116. scalesLayout.addWidget(label);
  117. scalesLayout.addWidget(weighButton);
  118. scale.newMeasurement.connect(function(m, u) {
  119. label.updateMeasurement(m, u);
  120. });
  121. scale.weigh();
  122. unitBox['currentIndexChanged(int)'].connect(scale.weigh);
  123. }
  124. }
  125. var remainingStock = new Array();
  126. var query = new QSqlQuery();
  127. query.exec("SELECT id, quantity, (SELECT conversion FROM lb_bag_conversion WHERE item = id) FROM coffees WHERE quantity <> 0");
  128. while(query.next()) {
  129. remainingStock.push({id: query.value(0),
  130. quantity: query.value(1),
  131. conversion: query.value(2)});
  132. }
  133. query = query.invalidate();
  134. var convertToPounds = function(w, u) {
  135. switch(u) {
  136. case "g":
  137. return w * 0.0022;
  138. case "oz":
  139. return w * 0.0625;
  140. case "Kg":
  141. return w * 2.2;
  142. }
  143. return w;
  144. };
  145. var convertFromPounds = function(w, u) {
  146. switch(u) {
  147. case "g":
  148. return w / 0.0022;
  149. case "oz":
  150. return w / 0.0625;
  151. case "Kg":
  152. return w / 2.2;
  153. }
  154. return w;
  155. };
  156. var updateGreenTable = function() {
  157. var deleteRow = -1;
  158. /* The combo box delegate updates user data before display data
  159. and this code is executed before the model update is fully
  160. complete. Rather than rely on this behavior continuing, we
  161. check that the display value has also been updated and defer
  162. row removal until both updates are complete.
  163. */
  164. while((deleteRow = table.findData("delete", 0)) > -1) {
  165. if(table.data(deleteRow, 0, 0) == "Delete") {
  166. table.removeRow(table.findData("delete", 0));
  167. } else {
  168. break;
  169. }
  170. }
  171. green.text = table.columnSum(1, 0);
  172. table.resizeColumnToContents(0);
  173. var gid = 0;
  174. var r = 0;
  175. while(gid >= 0)
  176. {
  177. gid = Number(table.data(r, 0, 32));
  178. if(isNaN(gid))
  179. {
  180. gid = -1;
  181. break;
  182. }
  183. var bagConversion = 1;
  184. for(var i = 0; i < remainingStock.length; i++)
  185. {
  186. if(gid == Number(remainingStock[i].id))
  187. {
  188. var displayValue = Number(remainingStock[i].quantity);
  189. bagConversion = Number(remainingStock[i].conversion);
  190. if(!isNaN(Number(table.data(r, 1, 0))))
  191. {
  192. var change = Number(table.data(r, 1, 0));
  193. switch(unitBox.currentIndex)
  194. {
  195. case 0:
  196. change = convertToPounds(change, "g");
  197. break;
  198. case 1:
  199. change = convertToPounds(change, "Kg");
  200. break;
  201. case 2:
  202. change = convertToPounds(change, "oz");
  203. break;
  204. }
  205. displayValue -= change;
  206. }
  207. var bagCount = (displayValue / bagConversion).toFixed(2);
  208. switch(unitBox.currentIndex)
  209. {
  210. case 0:
  211. displayValue = convertFromPounds(displayValue, "g");
  212. break;
  213. case 1:
  214. displayValue = convertFromPounds(displayValue, "Kg");
  215. break;
  216. case 2:
  217. displayValue = convertFromPounds(displayValue, "oz");
  218. break;
  219. }
  220. displayValue = "" + displayValue + " (" + bagCount + " bags)";
  221. if(table.data(r, 2, 0) != displayValue)
  222. {
  223. table.setData(r, 2, displayValue, 0);
  224. table.setData(r, 2, displayValue, 2);
  225. table.resizeColumnToContents(2);
  226. }
  227. }
  228. }
  229. r++;
  230. }
  231. if(parseFloat(green.text) > 0)
  232. {
  233. if(parseFloat(roastwt.text) > 0)
  234. {
  235. lossField.text = (((parseFloat(green.text) - parseFloat(roastwt.text)) / parseFloat(green.text)) * 100).toFixed(2) + "%";
  236. }
  237. else
  238. {
  239. lossField.text = "100%";
  240. }
  241. }
  242. };
  243. model.dataChanged.connect(updateGreenTable);
  244. unitBox['currentIndexChanged(int)'].connect(updateGreenTable);
  245. roastwt.textChanged.connect(function() {
  246. if(parseFloat(green.text) > 0)
  247. {
  248. if(parseFloat(roastwt.text) > 0)
  249. {
  250. lossField.text = (((parseFloat(green.text) - parseFloat(roastwt.text)) / parseFloat(green.text)) * 100).toFixed(2) + "%";
  251. }
  252. else
  253. {
  254. lossField.text = "100%";
  255. }
  256. }
  257. });
  258. var profilebutton = findChildObject(this, 'load');
  259. profilebutton.setEnabled(false);
  260. roasted['currentIndexChanged(int)'].connect(function() {
  261. table.clear();
  262. var query = new QSqlQuery();
  263. var q = "SELECT EXISTS(SELECT 1 FROM item_files WHERE item = ";
  264. q = q + roasted.currentData();
  265. q = q + ")";
  266. query.exec(q);
  267. if(query.next())
  268. {
  269. if(query.value(0) == 'false')
  270. {
  271. profilebutton.setEnabled(false);
  272. }
  273. else
  274. {
  275. profilebutton.setEnabled(true);
  276. }
  277. }
  278. else
  279. {
  280. profilebutton.setEnabled(false);
  281. }
  282. var title = "Typica - [*]New Batch (";
  283. title = title + roasted.currentText;
  284. title = title + ")";
  285. batch.windowTitle = title;
  286. q = "SELECT unroasted_id FROM roasting_log WHERE roasted_id = ";
  287. q = q + roasted.currentData();
  288. q = q + " AND time = (SELECT max(time) FROM roasting_log WHERE roasted_id = ";
  289. q = q + roasted.currentData();
  290. q = q + ")";
  291. query.exec(q);
  292. if(query.next())
  293. {
  294. var unroasted_items = sqlToArray(query.value(0));
  295. var names = [];
  296. q = "SELECT name FROM items WHERE id = :id AND quantity <> 0";
  297. query.prepare(q);
  298. var allInStock = true;
  299. for(var i = 0; i < unroasted_items.length; i++)
  300. {
  301. query.bind("id", unroasted_items[i]);
  302. query.exec();
  303. if(query.next())
  304. {
  305. names[i] = query.value(0);
  306. }
  307. else
  308. {
  309. allInStock = false;
  310. }
  311. }
  312. if(allInStock)
  313. {
  314. for(var i = 0; i < unroasted_items.length; i++)
  315. {
  316. table.setData(i, 0, names[i], 0);
  317. table.setData(i, 0, unroasted_items[i], 32);
  318. }
  319. }
  320. }
  321. });
  322. profilebutton.clicked.connect(function() {
  323. batch.windowModified = true;
  324. currentBatchInfo = batch;
  325. query = new QSqlQuery();
  326. var q = "SELECT files FROM item_files WHERE item = :item AND time = (SELECT max(time) FROM item_files WHERE item = :again)";
  327. query.prepare(q);
  328. query.bind(":item", Number(roasted.currentData()));
  329. query.bind(":again", Number(roasted.currentData()));
  330. query.exec();
  331. var graph;
  332. var log;
  333. if(query.next())
  334. {
  335. var files = query.value(0);
  336. files = files.replace("{", "(");
  337. files = files.replace("}", ")");
  338. q = "SELECT file, name FROM files WHERE id IN ";
  339. q = q + files;
  340. q = q + " AND type = 'profile'";
  341. query.exec(q);
  342. if(query.next())
  343. {
  344. var targetseries = -1;
  345. var buffer = new QBuffer(query.value(0));
  346. var pname = query.value(1);
  347. var input = new XMLInput(buffer, 1);
  348. graph = findChildObject(navigationwindow.loggingWindow, 'graph');
  349. log = findChildObject(navigationwindow.loggingWindow, 'log');
  350. log.clear();
  351. graph.clear();
  352. input.newTemperatureColumn.connect(log.setHeaderData);
  353. input.newTemperatureColumn.connect(function(col, text) {
  354. if(text == navigationwindow.loggingWindow.targetcolumnname)
  355. {
  356. targetseries = col;
  357. }
  358. });
  359. input.newAnnotationColumn.connect(log.setHeaderData);
  360. input.measure.connect(graph.newMeasurement);
  361. input.measure.connect(log.newMeasurement);
  362. input.measure.connect(function(data, series) {
  363. if(series == targetseries)
  364. {
  365. targetDetector.newMeasurement(data);
  366. }
  367. });
  368. var lc;
  369. input.lastColumn.connect(function(c) {
  370. lc = c;
  371. QSettings.setValue("liveColumn", c + 1);
  372. navigationwindow.loggingWindow.postLoadColumnSetup(c)
  373. });
  374. input.annotation.connect(function(note, tcol, ncol) {
  375. for(var i = tcol; i < ncol; i++) {
  376. log.newAnnotation(note, i, ncol);
  377. }
  378. });
  379. }
  380. }
  381. query = query.invalidate();
  382. navigationwindow.loggingWindow.windowTitle = "Typica - [*]" + pname;
  383. navigationwindow.loggingWindow.raise();
  384. navigationwindow.loggingWindow.activateWindow();
  385. graph.updatesEnabled = false;
  386. log.updatesEnabled = false;
  387. input.input();
  388. log.updatesEnabled = true;
  389. graph.updatesEnabled = true;
  390. log.newAnnotation("End", 1, lc);
  391. });
  392. var noprofilebutton = findChildObject(this, 'noprofile');
  393. noprofilebutton.clicked.connect(function() {
  394. batch.windowModified = true;
  395. currentBatchInfo = batch;
  396. navigationwindow.loggingWindow.raise();
  397. navigationwindow.loggingWindow.activateWindow();
  398. });
  399. var submitbutton = findChildObject(this, 'submit');
  400. var timefield = findChildObject(this, 'time');
  401. var notes = findChildObject(this, 'annotation');
  402. var duration = findChildObject(this, 'duration');
  403. var approval = findChildObject(this, 'approval');
  404. approval.checked = true;
  405. var target = findChildObject(this, 'target');
  406. submitbutton.clicked.connect(function() {
  407. checkQuery = new QSqlQuery();
  408. checkQuery.exec("SELECT 1 FROM machine WHERE id = " + selectedRoasterID);
  409. if(!checkQuery.next())
  410. {
  411. checkQuery.prepare("INSERT INTO machine (id, name) VALUES(:id, :name)");
  412. checkQuery.bind(":id", selectedRoasterID);
  413. checkQuery.bind(":name", selectedRoasterName);
  414. checkQuery.exec();
  415. }
  416. checkQuery = checkQuery.invalidate();
  417. var q = "INSERT INTO files (id, name, type, note, file) VALUES(default, :name, 'profile', NULL, :data) RETURNING id";
  418. query = new QSqlQuery();
  419. query.prepare(q);
  420. query.bind(":name", timefield.text + " " + roasted.currentText);
  421. query.bindFileData(":data", batch.tempData);
  422. query.exec();
  423. query.next();
  424. var fileno = query.value(0);
  425. var file = new QFile(batch.tempData);
  426. file.remove();
  427. var q2 = "INSERT INTO roasting_log (time, unroasted_id, unroasted_quantity, unroasted_total_quantity, roasted_id, roasted_quantity, transaction_type, annotation, machine, duration, approval, humidity, barometric, indoor_air, outdoor_air, files) VALUES(:time, ";
  428. q2 = q2 + table.columnArray(0, 32);
  429. q2 = q2 + ", ";
  430. for(var i = 0; table.data(i, 1, 0).value != ""; i++)
  431. {
  432. table.setData(i, 1, convertToPounds(parseFloat(table.data(i, 1, 0)), unitBox.currentText) ,32)
  433. }
  434. q2 = q2 + table.columnArray(1, 32);
  435. q2 = q2 + ", ";
  436. q2 = q2 + convertToPounds(parseFloat(green.text), unitBox.currentText);
  437. q2 = q2 + ", ";
  438. q2 = q2 + roasted.currentData();
  439. q2 = q2 + ", ";
  440. q2 = q2 + convertToPounds(parseFloat(roastwt.text), unitBox.currentText);
  441. q2 = q2 + ", 'ROAST', :annotation, ";
  442. q2 = q2 + selectedRoasterID;
  443. q2 = q2 + ", :duration, :approval, NULL, NULL, NULL, NULL, '{";
  444. q2 = q2 + fileno;
  445. q2 = q2 + "}')";
  446. query2 = new QSqlQuery();
  447. query2.prepare(q2);
  448. query2.bind(":time", timefield.text);
  449. query2.bind(":annotation", notes.plainText);
  450. query2.bind(":duration", duration.text);
  451. query2.bind(":approval", approval.checked);
  452. query2.exec();
  453. query2 = query2.invalidate();
  454. if(target.checked) {
  455. var q3 = "INSERT INTO item_files (time, item, files) VALUES(:time, :item, '{";
  456. q3 = q3 + fileno;
  457. q3 = q3 + "}')";
  458. query.prepare(q3);
  459. query.bind(":time", timefield.text);
  460. query.bind(":item", roasted.currentData());
  461. query.exec();
  462. }
  463. query = query.invalidate();
  464. batch.windowModified = false;
  465. batch.close();
  466. });
  467. ]]>
  468. </program>
  469. </window>