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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <window id="sampleRoastingBatch">
  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. <label>Sample Details:</label>
  8. <layout type="grid">
  9. <row>
  10. <column><label>Name:</label></column>
  11. <column><line id="name" /></column>
  12. </row>
  13. <row>
  14. <column><label>Vendor:</label></column>
  15. <column><line id="vendor" /></column>
  16. </row>
  17. <row>
  18. <column><label>Date:</label></column>
  19. <column><calendar id="date" /></column>
  20. </row>
  21. </layout>
  22. <label>Optional Details:</label>
  23. <sqltablearray columns="2" id="attributes">
  24. <column name="Attribute" />
  25. <column name="Value" />
  26. </sqltablearray>
  27. <stretch />
  28. </layout>
  29. <layout type="vertical">
  30. <label>Roasting Details:</label>
  31. <layout type="grid">
  32. <row>
  33. <column><label>Machine:</label></column>
  34. <column><line id="machine" writable="false" /></column>
  35. </row>
  36. <row>
  37. <column><label>Target Profile:</label></column>
  38. <column>
  39. <sqldrop data="0" display="0" showdata="false" editable="true" id="profile">
  40. <query>SELECT DISTINCT profile_name FROM sample_roast_profiles UNION SELECT '' ORDER BY profile_name ASC</query>
  41. </sqldrop>
  42. </column>
  43. </row>
  44. <row>
  45. <column><label>Green Weight:</label></column>
  46. <column><line id="greenWt" /></column>
  47. <column>
  48. <sqldrop data="0" display="0" showdata="false" editable="false" id="Gunits" />
  49. </column>
  50. </row>
  51. <row>
  52. <column><label>Roasted Weight:</label></column>
  53. <column><line id="roastedWt" /></column>
  54. </row>
  55. <row>
  56. <column><label>Weight Loss:</label></column>
  57. <column><line writable="false" id="loss" /></column>
  58. </row>
  59. <row>
  60. <column><label>Time:</label></column>
  61. <column><line id="time" writable="false" /></column>
  62. </row>
  63. <row>
  64. <column><label>Duration:</label></column>
  65. <column><line id="duration" writable="false" /></column>
  66. </row>
  67. </layout>
  68. <label>Notes:</label>
  69. <textarea id="annotation" />
  70. <layout type="horizontal">
  71. <button name="Roast" type="push" id="load" />
  72. <button name="Submit" type="push" id="submit" />
  73. </layout>
  74. <button name="Save log as target profile" type="check" id="target" />
  75. <stretch />
  76. </layout>
  77. <layout type="vertical">
  78. <label>Connected Scales</label>
  79. <layout type="vertical" id="scales" />
  80. <stretch />
  81. </layout>
  82. </layout>
  83. <program>
  84. <![CDATA[
  85. var machine = findChildObject(this, 'machine');
  86. machine.setText(selectedRoasterName + " (" + selectedRoasterID + ")");
  87. var GunitBox = findChildObject(this, 'Gunits');
  88. GunitBox.addItem("g");
  89. GunitBox.addItem("Kg");
  90. GunitBox.addItem("oz");
  91. GunitBox.addItem("lb");
  92. var scalesLayout = findChildObject(this, 'scales');
  93. scalesLayout.spacing = 10;
  94. if(navigationwindow.loggingWindow.scales.length > 0)
  95. {
  96. for(var i = 0; i < navigationwindow.loggingWindow.scales.length; i++)
  97. {
  98. var scale = navigationwindow.loggingWindow.scales[i];
  99. var label = new DragLabel();
  100. var weighButton = new QPushButton();
  101. weighButton.text = TTR("sampleRoastingBatch", "Weigh");
  102. weighButton.clicked.connect(scale.weigh);
  103. label.updateMeasurement = function(m, u) {
  104. switch(GunitBox.currentIndex) {
  105. case 0:
  106. this.text = Units.convertWeight(m, u, Units.Gram).toFixed(1);
  107. break;
  108. case 1:
  109. this.text = Units.convertWeight(m, u, Units.Kilogram).toFixed(4);
  110. break;
  111. case 2:
  112. this.text = Units.convertWeight(m, u, Units.Ounce).toFixed(3);
  113. break;
  114. case 3:
  115. this.text = Units.convertWeight(m, u, Units.Pound).toFixed(4);
  116. break;
  117. }
  118. };
  119. scalesLayout.addWidget(label);
  120. scalesLayout.addWidget(weighButton);
  121. scale.newMeasurement.connect(function(m, u) {
  122. label.updateMeasurement(m, u);
  123. });
  124. scale.weigh();
  125. GunitBox['currentIndexChanged(int)'].connect(scale.weigh);
  126. }
  127. }
  128. var submit = findChildObject(this, 'submit');
  129. submit.setEnabled(false);
  130. this.windowTitle = TTR("sampleRoastingBatch", "Typica - New Sample Roasting Batch");
  131. var newMenu = findChildObject(this, 'new');
  132. newMenu.triggered.connect(function() {
  133. createWindow("sampleRoastingBatch");
  134. });
  135. this.endBatch = function() {};
  136. var batch = this;
  137. batch.submitButton = submit;
  138. var name = findChildObject(this, 'name');
  139. var green = findChildObject(this, 'greenWt');
  140. var roasted = findChildObject(this, 'roastedWt');
  141. var loss = findChildObject(this, 'loss');
  142. var timefield = findChildObject(this, 'time');
  143. var convertToPounds = function(w, u) {
  144. switch(u)
  145. {
  146. case "g":
  147. return w * 0.0022;
  148. case "oz":
  149. return w * 0.0625;
  150. case "Kg":
  151. return w * 2.2;
  152. }
  153. return w;
  154. };
  155. var updateWeightLoss = function() {
  156. var cgreen = parseFloat(green.text);
  157. var croast = parseFloat(roasted.text);
  158. if(cgreen > 0)
  159. {
  160. if(croast > 0)
  161. {
  162. loss.text = (((cgreen - croast) / cgreen) * 100).toFixed(2) + "%";
  163. }
  164. else
  165. {
  166. loss.text = "100%";
  167. }
  168. }
  169. };
  170. green.textChanged.connect(function() {
  171. updateWeightLoss();
  172. });
  173. roasted.textChanged.connect(function() {
  174. updateWeightLoss();
  175. });
  176. var roastButton = findChildObject(this, 'load');
  177. var profileName = findChildObject(this, 'profile');
  178. var greenName = findChildObject(this, 'name');
  179. var stop = findChildObject(navigationwindow.loggingWindow, 'stopbutton');
  180. stop.clicked.connect(function() {
  181. submit.setEnabled(true);
  182. });
  183. var validateCapacity = function() {
  184. if(checkCapacity == "true") {
  185. if(convertToPounds(parseFloat(green.text), GunitBox.currentText) > poundsCapacity) {
  186. return false;
  187. }
  188. }
  189. return true;
  190. }
  191. roastButton.clicked.connect(function() {
  192. var proceed = false;
  193. if(validateCapacity()) {
  194. proceed = true;
  195. } else {
  196. proceed = displayWarning(TTR("sampleRoastingBatch", "Suspicious Input"),
  197. TTR("sampleRoastingBatch", "Entered green coffee weight exceeds maximum batch size. Continue?"));
  198. }
  199. if(proceed) {
  200. doRoast();
  201. }
  202. });
  203. var doRoast = function() {
  204. currentBatchInfo = batch;
  205. query = new QSqlQuery();
  206. var q = "SELECT file FROM sample_roast_profiles WHERE profile_name = :name AND time = (SELECT max(time) FROM sample_roast_profiles WHERE profile_name = :again)";
  207. query.prepare(q);
  208. query.bind(":name", profileName.currentText);
  209. query.bind(":again", profileName.currentText);
  210. query.exec();
  211. if(query.next())
  212. {
  213. var file = query.value(0);
  214. query.prepare("SELECT file FROM files WHERE id = :id");
  215. query.bind(":id", file);
  216. query.exec();
  217. if(query.next())
  218. {
  219. var buffer = new QBuffer(query.value(0));
  220. Windows.loggingWindow.loadPlan(buffer, name.text);
  221. }
  222. }
  223. else
  224. {
  225. Windows.loggingWindow.clearLog();
  226. Windows.loggingWindow.windowTitle = 'Typica - [*]' + name.text;
  227. Windows.loggingWindow.raise();
  228. Windows.loggingWindow.activateWindow();
  229. }
  230. query = query.invalidate();
  231. };
  232. var notes = findChildObject(this, 'annotation');
  233. var machine = findChildObject(this, 'machine');
  234. var duration = findChildObject(this, 'duration');
  235. var arrival = findChildObject(this, 'date');
  236. var vendor = findChildObject(this, 'vendor');
  237. var attributes = findChildObject(this, 'attributes');
  238. var target = findChildObject(this, 'target');
  239. submit.clicked.connect(function() {
  240. var proceed = false;
  241. if(validateCapacity()) {
  242. proceed = true;
  243. } else {
  244. proceed = displayWarning(TTR("sampleRoastingBatch", "Suspicious Input"),
  245. TTR("sampleRoastingBatch", "Entered green coffee weight exceeds maximum batch size. Continue?"));
  246. }
  247. if(proceed) {
  248. doSubmit();
  249. }
  250. });
  251. var doSubmit = function() {
  252. query = new QSqlQuery();
  253. query.prepare("INSERT INTO files (id, name, type, note, file) VALUES(DEFAULT, :name, 'profile', NULL, :data) RETURNING id");
  254. query.bind(":name", timefield.text + " " + name.text + " " + profileName.currentText);
  255. query.bindFileData(":data", batch.tempData);
  256. query.exec();
  257. query.next();
  258. var fileno = query.value(0);
  259. var file = new QFile(batch.tempData);
  260. file.remove();
  261. if(target.checked)
  262. {
  263. query.prepare("INSERT INTO sample_roast_profiles (time, profile_name, file) VALUES(:time, :name, :file)");
  264. query.bind(":time", timefield.text);
  265. query.bind(":name", profileName.currentText);
  266. query.bind(":file", Number(fileno));
  267. query.exec();
  268. }
  269. var attnames = sqlToArray(attributes.columnArray(0, 0));
  270. for(var i = 0; i < attnames.length; i++)
  271. {
  272. var attname = attnames[i];
  273. if(attname[0] == '{') {
  274. attname = attname.substr(1);
  275. }
  276. if(attname[0] == ' ') {
  277. attname = attname.substr(1);
  278. }
  279. if(attname[attname.length -1] == '}') {
  280. attname = attname.substr(0, attname.length - 1);
  281. }
  282. if(attname.length == 0) {
  283. break;
  284. }
  285. query.prepare("SELECT id FROM item_attributes WHERE name = :name");
  286. query.bind(":name", attname);
  287. query.exec();
  288. if(query.next())
  289. {
  290. attributes.setData(i, 0, query.value(0), 32);
  291. }
  292. else
  293. {
  294. query.prepare("INSERT INTO item_attributes (id, name) VALUES(DEFAULT, :name) RETURNING id");
  295. query.bind(":name", attname);
  296. query.exec();
  297. query.next();
  298. attributes.setData(i, 0, query.value(0), 32);
  299. }
  300. }
  301. 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");
  302. query.bind(":name", name.text);
  303. query.bind(":arrival", arrival.date);
  304. query.bind(":vendor", vendor.text);
  305. query.bind(":attrids", attributes.bindableColumnArray(0, 32));
  306. query.bind(":attrvals", attributes.bindableQuotedColumnArray(1, 0));
  307. query.exec();
  308. query.next();
  309. var greenId = query.value(0);
  310. query.prepare("INSERT INTO items (id, name, reference, unit, quantity, category) VALUES(DEFAULT, :name, NULL, 'lb', 0, 'Coffee: Roasted Sample') RETURNING id");
  311. query.bind(":name", name.text + " " + profileName.currentText);
  312. query.exec();
  313. query.next();
  314. var roastedId = query.value(0);
  315. 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)");
  316. query.bind(":time", timefield.text);
  317. query.bind(":unroastedids", "{" + greenId + "}");
  318. query.bind(":greens", "{" + convertToPounds(parseFloat(green.text), GunitBox.currentText) + "}");
  319. query.bind(":green", convertToPounds(parseFloat(green.text), GunitBox.currentText));
  320. query.bind(":roastedid", Number(roastedId));
  321. query.bind(":roasted", convertToPounds(parseFloat(roasted.text), GunitBox.currentText));
  322. query.bind(":note", notes.plainText);
  323. query.bind(":machine", Number(selectedRoasterID));
  324. query.bind(":duration", duration.text);
  325. query.bind(":files", "{" + fileno + "}");
  326. query.bind(":user", Application.currentTypicaUser());
  327. query.exec();
  328. query = query.invalidate();
  329. batch.close();
  330. }
  331. ]]>
  332. </program>
  333. </window>