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

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