Typica is a free program for professional coffee roasters. https://typica.us
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

manuallogentry.xml 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. <window id="manualLogEntry">
  2. <menu name="File">
  3. <plugins id="pluginMenu" title="Import" src="ImportFilters" preRun="pluginContext.preRun();" postRun="pluginContext.postRun();"/>
  4. <separator />
  5. <item id="quitItem" shortcut="ctrl+Q">Quit</item>
  6. </menu>
  7. <menu name="Log">
  8. <item id="clear" shortcut="Ctrl+L">Clear Log</item>
  9. <separator />
  10. <item id="ms">Millisecond View</item>
  11. <item id="1s">1 Second View</item>
  12. <item id="5s">5 Second View</item>
  13. <item id="10s">10 Second View</item>
  14. <item id="15s">15 Second View</item>
  15. <item id="30s">30 Second View</item>
  16. <item id="1m">1 Minute View</item>
  17. <separator />
  18. <item id="showC">Display Celsius</item>
  19. <item id="showF">Display Fahrenheit</item>
  20. </menu>
  21. <layout type="vertical">
  22. <tabbar id="tabs"/>
  23. <layout type="stack" id="pages">
  24. <page>
  25. <layout type="vertical">
  26. <layout type="horizontal">
  27. <label>Batch Type:</label>
  28. <sqldrop id="batchType" />
  29. <label>Machine:</label>
  30. <sqldrop id="machineSelector" />
  31. <stretch />
  32. </layout>
  33. <label>Green Coffee:</label>
  34. <layout type="stack" id="greenInfoLayout">
  35. <page id="sampleGreen">
  36. <layout type="vertical">
  37. <layout type="grid">
  38. <row>
  39. <column><label>Name:</label></column>
  40. <column><line id="sampleGreenName" /></column>
  41. </row>
  42. <row>
  43. <column><label>Weight:</label></column>
  44. <column><line id="sampleGreenWeight" validator="numeric">0.0</line></column>
  45. <column><sqldrop id="sampleGreenUnit" /></column>
  46. </row>
  47. <row>
  48. <column><label>Vendor:</label></column>
  49. <column><line id="sampleGreenVendor" /></column>
  50. </row>
  51. <row>
  52. <column><label>Arrival Date:</label></column>
  53. <column><calendar id="sampleGreenArrivalDate" /></column>
  54. </row>
  55. </layout>
  56. <label>Additional Details:</label>
  57. <sqltablearray columns="2" id="attributes">
  58. <column name="Attribute" />
  59. <column name="Value" />
  60. </sqltablearray>
  61. </layout>
  62. </page>
  63. <page id="productionGreen">
  64. <layout type="vertical">
  65. <layout type="horizontal">
  66. <label>Unit:</label>
  67. <sqldrop id="productionGreenUnit" />
  68. <stretch />
  69. </layout>
  70. <sqltablearray columns="2" id="productionGreenTable">
  71. <column name="Coffee" delegate="sql" showdata="true" null="true" nulltext="Delete" nulldata="delete" data="0" display="1">
  72. <![CDATA[SELECT id, name FROM coffees WHERE quantity <> 0 ORDER BY name]]>
  73. </column>
  74. <column name="Weight" delegate="numeric" />
  75. </sqltablearray>
  76. </layout>
  77. </page>
  78. </layout>
  79. <label>Roasting Details:</label>
  80. <layout type="grid">
  81. <row>
  82. <column><label>Item:</label></column>
  83. <column>
  84. <sqldrop data="0" display="1" showdata="true" id="roastedItem">
  85. <null />
  86. <query>SELECT id, name FROM items WHERE category = 'Coffee: Roasted' AND id IN (SELECT item FROM current_items) ORDER BY name</query>
  87. </sqldrop>
  88. </column>
  89. </row>
  90. <row>
  91. <column><label>Weight:</label></column>
  92. <column><line id="roastedWeight" validator="numeric">0.0</line></column>
  93. </row>
  94. <row>
  95. <column><label>Time:</label></column>
  96. <column><calendar id="roastTime" time="true"/></column>
  97. </row>
  98. <row>
  99. <column><label>Duration:</label></column>
  100. <column><timeedit id="roastDuration" /></column>
  101. </row>
  102. <row>
  103. <column><label>Notes:</label></column>
  104. <column><textarea id="notes" /></column>
  105. </row>
  106. </layout>
  107. </layout>
  108. </page>
  109. <page>
  110. <layout type="vertical">
  111. <layout type="horizontal">
  112. <label>Time Increment (s):</label>
  113. <line id="timeincrement" validator="numeric">30</line>
  114. <stretch />
  115. <label>Time:</label>
  116. <timeedit id="currenttime" />
  117. <stretch />
  118. <label>Temperature:</label>
  119. <line id="currenttemperature" validator="numeric" />
  120. <label>Note:</label>
  121. <line id="currentnote" />
  122. <button name="Add Measurement" id="addmeasurement" type="push" />
  123. </layout>
  124. <splitter type="horizontal" id="roastdatasplit">
  125. <measurementtable id="log" />
  126. <graph id="graph" />
  127. </splitter>
  128. </layout>
  129. </page>
  130. </layout>
  131. <layout type="horizontal">
  132. <stretch />
  133. <button name="Submit" id="submit" type="push" />
  134. </layout>
  135. </layout>
  136. <program>
  137. <![CDATA[
  138. var window = this;
  139. this.windowTitle = "Typica - Manual Log Entry";
  140. window.windowReady.connect(function() {
  141. if(machineModel.rowCount() == 0) {
  142. displayError(TTR("manualLogEntry", "Configuration Required"),
  143. TTR("manualLogEntry", "Please configure a roaster."));
  144. window.close();
  145. }
  146. });
  147. quitItem = findChildObject(this, 'quitItem');
  148. quitItem.triggered.connect(function() {
  149. Application.quit();
  150. });
  151. pluginContext = {};
  152. pluginContext.table = findChildObject(this, 'log');
  153. pluginContext.table.setHeaderData(1, "Temp");
  154. pluginContext.table.setHeaderData(2, "Note");
  155. pluginContext.graph = findChildObject(this, 'graph');
  156. pluginContext.preRun = function() {
  157. var filename = QFileDialog.getOpenFileName(window, TTR("manualLogEntry", "Import"), QSettings.value('script/lastDir', '') + '/');
  158. var file = new QFile(filename);
  159. if(file.open(1)) {
  160. pluginContext.data = file.readToString();
  161. file.close();
  162. pluginContext.table.clear();
  163. pluginContext.graph.clear();
  164. QSettings.setValue("script/lastDir", dir(filename));
  165. } else {
  166. throw new Error("Failed to open file, aborting import.");
  167. }
  168. };
  169. pluginContext.postRun = function() {
  170. };
  171. pluginContext.newMeasurement = function(m, c) {
  172. pluginContext.table.newMeasurement(m, c);
  173. pluginContext.graph.newMeasurement(m, c);
  174. }
  175. pluginMenu = findChildObject(this, 'pluginMenu');
  176. pluginMenu.setProperty("activationObject", pluginContext);
  177. tabs = findChildObject(this, 'tabs');
  178. tabs.addTab("Batch Data");
  179. tabs.addTab("Roast Data");
  180. pages = findChildObject(this, 'pages');
  181. tabs.currentChanged.connect(function(index) {
  182. pages.setCurrentIndex(index);
  183. });
  184. greenInfoLayout = findChildObject(this, 'greenInfoLayout');
  185. roastedItem = findChildObject(this, 'roastedItem');
  186. batchType = findChildObject(this, 'batchType');
  187. batchType.addItem("Sample");
  188. batchType.addItem("Production");
  189. batchType['currentIndexChanged(int)'].connect(function(batchTypeIndex) {
  190. QSettings.setValue("script/manual_batchType", batchTypeIndex);
  191. greenInfoLayout.setCurrentIndex(batchTypeIndex);
  192. roastedItem.enabled = (batchTypeIndex == 1);
  193. });
  194. batchType.setCurrentIndex(QSettings.value("script/manual_batchType", 1));
  195. var machineSelector = findChildObject(this, 'machineSelector');
  196. var machineModel = new DeviceTreeModel;
  197. machineSelector.setModel(machineModel);
  198. machineSelector.currentIndex = QSettings.value("script/manualMachineSelection", 0);
  199. machineSelector['currentIndexChanged(int)'].connect(function(index) {
  200. QSettings.setValue("script/manualMachineSelection", index);
  201. });
  202. sampleGreenUnit = findChildObject(this, 'sampleGreenUnit');
  203. sampleGreenUnit.addItem("g");
  204. sampleGreenUnit.addItem("Kg");
  205. sampleGreenUnit.addItem("oz");
  206. sampleGreenUnit.addItem("lb");
  207. sampleGreenUnit.currentIndex = (QSettings.value("script/manual_unit", sampleGreenUnit.findText("lb")));
  208. productionGreenUnit = findChildObject(this, 'productionGreenUnit');
  209. productionGreenUnit.addItem("g");
  210. productionGreenUnit.addItem("Kg");
  211. productionGreenUnit.addItem("oz");
  212. productionGreenUnit.addItem("lb");
  213. productionGreenUnit.currentIndex = (QSettings.value("script/manual_unit", productionGreenUnit.findText("lb")));
  214. sampleGreenUnit['currentIndexChanged(int)'].connect(function(greenUnitIndex) {
  215. QSettings.setValue("script/manual_unit", greenUnitIndex);
  216. productionGreenUnit.setCurrentIndex(greenUnitIndex);
  217. });
  218. productionGreenUnit['currentIndexChanged(int)'].connect(function(greenUnitIndex) {
  219. QSettings.setValue("script/manual_unit", greenUnitIndex);
  220. sampleGreenUnit.setCurrentIndex(greenUnitIndex);
  221. });
  222. timeincrement = findChildObject(this, 'timeincrement');
  223. currenttime = findChildObject(this, 'currenttime');
  224. currenttemperature = findChildObject(this, 'currenttemperature');
  225. currentnote = findChildObject(this, 'currentnote');
  226. addmeasurement = findChildObject(this, 'addmeasurement');
  227. var currentUnit = Units.Fahrenheit;
  228. var showC = findChildObject(this, 'showC');
  229. showC.triggered.connect(function() {
  230. pluginContext.table.setDisplayUnits(Units.Celsius);
  231. pluginContext.graph.showC();
  232. QSettings.setValue("temperatureUnit", "C");
  233. currentUnit = Units.Celsius;
  234. });
  235. var showF = findChildObject(this, 'showF');
  236. showF.triggered.connect(function() {
  237. pluginContext.table.setDisplayUnits(Units.Fahrenheit);
  238. pluginContext.graph.showF();
  239. QSettings.setValue("temperatureUnit", "F");
  240. currentUnit = Units.Fahrenheit;
  241. });
  242. if(QSettings.value("temperatureUnit", "F") == "C") {
  243. showC.trigger();
  244. }
  245. addmeasurement.clicked.connect(function() {
  246. var fromUnit =
  247. pluginContext.newMeasurement(new Measurement(Units.convertTemperature(Number(currenttemperature.text), currentUnit, Units.Fahrenheit), currenttime.time), 1);
  248. if(currentnote.text.length > 0) {
  249. pluginContext.table.newAnnotation(currentnote.text, 1, 2);
  250. }
  251. currentnote.text = "";
  252. var t = QTime();
  253. t = t.fromString(currenttime.time, "hh:mm:ss");
  254. t = t.addSecs(30);
  255. currenttime.time = t;
  256. currenttemperature.text = "";
  257. });
  258. currenttemperature.returnPressed.connect(addmeasurement.clicked);
  259. currentnote.returnPressed.connect(addmeasurement.clicked);
  260. var v1 = findChildObject(this, 'ms');
  261. v1.triggered.connect(pluginContext.table.LOD_ms);
  262. var v2 = findChildObject(this, '1s');
  263. v2.triggered.connect(pluginContext.table.LOD_1s);
  264. var v3 = findChildObject(this, '5s');
  265. v3.triggered.connect(pluginContext.table.LOD_5s);
  266. var v4 = findChildObject(this, '10s');
  267. v4.triggered.connect(pluginContext.table.LOD_10s);
  268. var v5 = findChildObject(this, '15s');
  269. v5.triggered.connect(pluginContext.table.LOD_15s);
  270. var v6 = findChildObject(this, '30s');
  271. v6.triggered.connect(pluginContext.table.LOD_30s);
  272. var v7 = findChildObject(this, '1m');
  273. v7.triggered.connect(pluginContext.table.LOD_1m);
  274. var clear = findChildObject(this, 'clear');
  275. clear.triggered.connect(pluginContext.table.clear);
  276. clear.triggered.connect(pluginContext.graph.clear);
  277. clear.triggered.connect(function() {
  278. currenttime.time = QTime(0, 0, 0, 0);
  279. currenttemperature.text = "";
  280. currentnote.text = "";
  281. });
  282. var sampleGreenName = findChildObject(this, 'sampleGreenName');
  283. var sampleGreenWeight = findChildObject(this, 'sampleGreenWeight');
  284. var productionGreenTable = findChildObject(this, 'productionGreenTable');
  285. var greenModel = productionGreenTable.model();
  286. var greenTotal = 0.0;
  287. var updateGreenTable = function() {
  288. var deleteRow = -1;
  289. while((deleteRow = productionGreenTable.findData("delete", 0)) > -1) {
  290. if(productionGreenTable.data(deleteRow, 0, 0) == "Delete") {
  291. productionGreenTable.removeRow(productionGreenTable.findData("delete", 0));
  292. } else {
  293. break;
  294. }
  295. }
  296. greenTotal = productionGreenTable.columnSum(1, 0);
  297. productionGreenTable.resizeColumnToContents(0);
  298. };
  299. greenModel.dataChanged.connect(updateGreenTable);
  300. var validateInputs = function() {
  301. if(batchType.currentIndex == 0) {
  302. /* Sample batch */
  303. if(sampleGreenName.text.length == 0) {
  304. tabs.setCurrentIndex(0);
  305. displayError(TTR("manualLogEntry", "Data Entry Error"),
  306. TTR("manualLogEntry", "Please enter a green coffee name."));
  307. return false;
  308. }
  309. if(Number(sampleGreenWeight.text) <= 0 || isNaN(sampleGreenWeight.text)) {
  310. tabs.setCurrentIndex(0);
  311. displayError(TTR("manualLogEntry", "Data Entry Error"),
  312. TTR("manualLogEntry", "Green coffee weight must be a number greater than 0."));
  313. return false;
  314. }
  315. } else {
  316. /* Production batch */
  317. var itemArray = productionGreenTable.columnArray(0, 32).split("\\s*,\\s*");
  318. var weightArray = productionGreenTable.columnArray(1, 0).split("\\s*,\\s*");
  319. if((itemArray.length != weightArray.length) || (itemArray.length == 0)) {
  320. tabs.setCurrentIndex(0);
  321. displayError(TTR("manualLogEntry", "Data Entry Error"),
  322. TTR("manualLogEntry", "Please check that at least one green coffee has been selected and each green coffee has a valid weight"));
  323. return false;
  324. }
  325. if(Number(greenTotal) <= 0) {
  326. tabs.setCurrentIndex(0);
  327. displayError(TTR("manualLogEntry", "DataEntryError"),
  328. TTR("manualLogEntry", "Total green coffee weight must be a number greater than 0."));
  329. return false;
  330. }
  331. if(roastedItem.currentIndex == 0) {
  332. tabs.setCurrentIndex(0);
  333. displayError(TTR("manualLogEntry", "DataEntryError"),
  334. TTR("manualLogEntry", "Please select a roasted coffee item."));
  335. return false;
  336. }
  337. }
  338. return true;
  339. };
  340. var roastDataExists = function() {
  341. return (pluginContext.table.rowCount() > 0);
  342. }
  343. var roastTime = findChildObject(this, 'roastTime');
  344. var attributes = findChildObject(this, 'attributes');
  345. var sampleGreenArrivalDate = findChildObject(this, 'sampleGreenArrivalDate');
  346. var convertToPounds = function(w, u) {
  347. switch(u) {
  348. case "g":
  349. return w * 0.0022;
  350. case "oz":
  351. return w * 0.0625;
  352. case "Kg":
  353. return w * 2.2;
  354. }
  355. return w;
  356. };
  357. var roastedWeight = findChildObject(this, 'roastedWeight');
  358. var notes = findChildObject(this, 'notes');
  359. var roastDuration = findChildObject(this, 'roastDuration');
  360. var doSubmit = function() {
  361. var fileID = -1;
  362. var query = new QSqlQuery();
  363. if(roastDataExists()) {
  364. var buffer = new QBuffer;
  365. buffer.open(3);
  366. pluginContext.table.saveXML(buffer);
  367. var q = "INSERT INTO files (id, name, type, note, file) VALUES (default, :name, 'profile', NULL, :data) RETURNING id";
  368. query.prepare(q);
  369. query.bind(":name", roastTime.text + " Manual Entry");
  370. query.bindDeviceData(":data", buffer);
  371. query.exec();
  372. query.next();
  373. fileID = Number(query.value(0));
  374. }
  375. var rootIndex = machineModel.index(machineSelector.currentIndex, 0);
  376. var selectedRoasterName = machineModel.data(rootIndex, 0);
  377. var machineReference = machineModel.referenceElement(machineModel.data(rootIndex, 32));
  378. var selectedRoasterID = machineReference.databaseid;
  379. query.exec("SELECT 1 FROM machine WHERE id = " + selectedRoasterID);
  380. if(!query.next()) {
  381. query.prepare("INSERT INTO machine (id, name) VALUES (:id, :name)");
  382. query.bind(":id", selectedRoasterID);
  383. query.bind(":name", selectedRoasterName);
  384. query.exec();
  385. }
  386. if(batchType.currentIndex == 0) {
  387. /* Sample roast */
  388. var attnames = sqlToArray(attributes.columnArray(0, 0));
  389. for(var i = 0; i < attnames.length; i++) {
  390. var attname = attnames[i];
  391. if(attname[0] == '{') {
  392. attname = attname.substr(1);
  393. }
  394. if(attname[0] == ' ') {
  395. attname = attname.substr(1);
  396. }
  397. if(attname[attname.length - 1] == '}') {
  398. attname = attname.substr(0, attname.length - 1);
  399. }
  400. if(attname.length == 0) {
  401. break;
  402. }
  403. query.prepare("SELECT id FROM item_attributes WHERE name = :name");
  404. query.bind(":name", attname);
  405. query.exec();
  406. if(query.next()) {
  407. attributes.setData(i, 0, query.value(0), 32);
  408. } else {
  409. query.prepare("INSERT INTO item_attributes(id, name) VALUES (DEFAULT, :name) RETURNING id");
  410. query.bind(":name", attname);
  411. query.exec();
  412. query.next();
  413. attributes.setData(i, 0, query.value(0), 32);
  414. }
  415. }
  416. query.prepare("INSERT INTO coffee_sample_items(id, name, reference, unit, quantity, category, arrival, vendor, attribute_ids, attribute_values) VALUES (DEFAULT, :name, NULL, 'lb', 0, 'Coffee: Green Sample', :arrival, :vendor, :attrids, :attrvals) RETURNING id");
  417. query.bind(":name", sampleGreenName.text);
  418. query.bind(":arrival", sampleGreenArrivalDate.date);
  419. query.bind(":attrids", attributes.bindableColumnArray(0, 32));
  420. query.bind(":attrvals", attributes.bindableQuotedColumnArray(1, 0));
  421. query.exec();
  422. query.next();
  423. var greenId = query.value(0);
  424. query.prepare("INSERT INTO items (id, name, reference, unit, quantity, category) VALUES (DEFAULT, :name, NULL, 'lb', 0, 'Coffee: Roasted Sample') RETURNING id");
  425. query.bind(":name", sampleGreenName.text + " Roasted Sample");
  426. query.exec();
  427. query.next();
  428. var roastedId = query.value(0);
  429. query.prepare("INSERT INTO roasting_log (time, unroasted_id, unroasted_quantity, unroasted_total_quantity, roasted_id, roasted_quantity, transaction_type, annotation, machine, duration, approval, humidity, barometric, indoor_air, outdoor_air, files, person) VALUES (:time, :unroastedids, :greens, :green, :roastedid, :roasted, 'SAMPLEROAST', :note, :machine, :duration, TRUE, NULL, NULL, NULL, NULL, :files, :user)");
  430. query.bind(":time", roastTime.text);
  431. query.bind(":unroastedids", "{" + greenId + "}");
  432. query.bind(":greens", "{" + convertToPounds(parseFloat(sampleGreenWeight.text), sampleGreenUnit.currentText) + "}");
  433. query.bind(":green", convertToPounds(parseFloat(sampleGreenWeight.text), sampleGreenUnit.currentText));
  434. query.bind("roastedid", Number(roastedId));
  435. query.bind("roasted", convertToPounds(parseFloat(roastedWeight.text), sampleGreenUnit.currentText));
  436. query.bind(":note", notes.plainText);
  437. query.bind(":machine", Number(selectedRoasterID));
  438. query.bind(":duration", roastDuration.text);
  439. if(fileID > 0) {
  440. query.bind(":files", "{" + fileID + "}");
  441. } else {
  442. query.bind(":file", "{}");
  443. }
  444. query.bind(":user", Application.currentTypicaUser());
  445. query.exec();
  446. } else {
  447. var q = "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, person) VALUES (:time, ";
  448. q += productionGreenTable.columnArray(0, 32);
  449. q += ", ";
  450. for(var i = 0; i < productionGreenTable.data(i, 1, 0).value != ""; i++) {
  451. productionGreenTable.setData(i, 1, convertToPounds(parseFloat(productionGreenTable.data(i, 1, 0)), produtionGreenUnit.currentText), 32);
  452. }
  453. q += productionGreenTable.columnArray(1, 32);
  454. q += ", ";
  455. q += roastedItem.currentData();
  456. q += ", ";
  457. q += convertToPounds(parseFloat(roastedWeight.text), productionGreenUnit.currentText);
  458. q += ", 'ROAST', :annotation, ";
  459. q += selectedRoasterID;
  460. q += ", :duration, TRUE, NULL, NULL, NULL, NULL, '{";
  461. q += fileID;
  462. q += "}', :user)";
  463. query.prepare(q);
  464. query.bind(":time", roastTime.text);
  465. query.bind(":annotation", notes.plainText);
  466. query.bind(":duration", roastDuration.text);
  467. query.bind(":user", Application.currentTypicaUser());
  468. query.exec();
  469. }
  470. query = query.invalidate();
  471. window.close();
  472. }
  473. var submit = findChildObject(this, 'submit');
  474. submit.clicked.connect(function() {
  475. if(validateInputs()) {
  476. doSubmit();
  477. }
  478. });
  479. ]]>
  480. </program>
  481. </window>