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.

newbatch.xml 44KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919
  1. <window id="batchWindow">
  2. <menu name="File">
  3. <item id="print" shortcut="Ctrl+P">Print...</item>
  4. </menu>
  5. <menu name="Batch">
  6. <item id="new" shortcut="Ctrl+N">New Batch...</item>
  7. </menu>
  8. <layout type="vertical">
  9. <layout type="horizontal">
  10. <label>Scheduled Batch ID:</label>
  11. <line id="search" />
  12. <stretch />
  13. </layout>
  14. <tabbar id="tabs" />
  15. <layout type="stack" id="pages">
  16. <page>
  17. <layout type="vertical">
  18. <layout type="horizontal">
  19. <label>Filter:</label>
  20. <line id="filter" />
  21. <label>Value:</label>
  22. <line id="filtervalue" />
  23. <stretch />
  24. </layout>
  25. <sqlview id="batches" />
  26. </layout>
  27. </page>
  28. <page>
  29. <layout type="vertical">
  30. <splitter type="horizontal" id="splitter">
  31. <widget id="required">
  32. <layout type="vertical">
  33. <layout type="horizontal">
  34. <label>Machine:</label>
  35. <line id="machine" writable="false" />
  36. <label>Unit:</label>
  37. <sqldrop id="unit" />
  38. <stretch />
  39. </layout>
  40. <layout type="horizontal">
  41. <label>Roasted Coffee:</label>
  42. <sqldrop data="0" display="1" showdata="true" id="roasted">
  43. <null />
  44. <query>SELECT id, name FROM items WHERE category = 'Coffee: Roasted' AND id IN (SELECT item FROM current_items) ORDER BY name</query>
  45. </sqldrop>
  46. <stretch />
  47. </layout>
  48. <label>Green Coffee:</label>
  49. <sqltablearray columns="3" id="greens">
  50. <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>
  51. <column name="Weight" delegate="positivenumeric" />
  52. <column name="Remaining" />
  53. </sqltablearray>
  54. <layout type="horizontal">
  55. <label>Green Weight:</label>
  56. <line id="green" writable="false">0.0</line>
  57. </layout>
  58. <layout type="horizontal">
  59. <button name="Load Profile" type="push" id="load" />
  60. <button name="No Profile" type="push" id="noprofile" />
  61. </layout>
  62. <layout type="horizontal">
  63. <label>Time:</label>
  64. <line id="time" writable="false" />
  65. <label>Duration:</label>
  66. <line id="duration" writable="false" />
  67. </layout>
  68. <layout type="horizontal">
  69. <label>Roasted Weight:</label>
  70. <line id="roast" validator="numeric">0.0</line>
  71. <label>Weight Loss:</label>
  72. <line id="wloss" writable="false" />
  73. <button type="check" id="approval" name="Approved" />
  74. <stretch />
  75. </layout>
  76. <layout type="horizontal">
  77. <label>Annotation:</label>
  78. <textarea id="annotation" />
  79. </layout>
  80. <layout type="horizontal">
  81. <button name="Submit" id="submit" type="push" />
  82. <button name="Save log as target profile" type="check" id="target" />
  83. </layout>
  84. </layout>
  85. </widget>
  86. <widget>
  87. <layout type="vertical">
  88. <label>Connected Scales</label>
  89. <layout type="vertical" id="scales" />
  90. <label>Expected Weight Loss</label>
  91. <line id="lossspec" writable="false" />
  92. <label>Expected Roasted Weight</label>
  93. <layout type="horizontal">
  94. <label>Min:</label>
  95. <line id="minroastweight" writable="false" />
  96. </layout>
  97. <layout type="horizontal">
  98. <label>Expected:</label>
  99. <line id="expectedroastweight" writable="false" />
  100. </layout>
  101. <layout type="horizontal">
  102. <label>Max:</label>
  103. <line id="maxroastweight" writable="false" />
  104. </layout>
  105. <label>Degree of Roast</label>
  106. <label>Whole Bean:</label>
  107. <roastcoloredit id="wholecolor" />
  108. <label id="wholespec">-</label>
  109. <label>Ground:</label>
  110. <roastcoloredit id="groundcolor" />
  111. <label id="groundspec">-</label>
  112. <label>Specification Details</label>
  113. <textarea id="specnotes" />
  114. <layout type="horizontal">
  115. <label>File ID:</label>
  116. <line id="filenofield" writable="false" />
  117. </layout>
  118. <stretch />
  119. </layout>
  120. </widget>
  121. <widget id="tagcontainer" ignoreSizePolicy="true">
  122. <layout type="vertical">
  123. <webview id="batchTag" />
  124. <layout type="horizontal">
  125. <printerselector id="printerlist" />
  126. <button name="Print" id="printbutton" type="push" />
  127. </layout>
  128. </layout>
  129. </widget>
  130. </splitter>
  131. </layout>
  132. </page>
  133. </layout>
  134. </layout>
  135. <program>
  136. <![CDATA[
  137. var scheduledID = 0;
  138. var window = this;
  139. var splitter = findChildObject(this, 'splitter');
  140. window.aboutToClose.connect(function() {
  141. splitter.saveState("script/newbatch/splitter");
  142. });
  143. splitter.setCollapsible(0, false);
  144. splitter.restoreState("script/newbatch/splitter");
  145. var tabs = findChildObject(this, 'tabs');
  146. tabs.addTab(TTR("batchWindow", "Schedule"));
  147. tabs.addTab(TTR("batchWindow", "Batch"));
  148. var pages = findChildObject(this, 'pages');
  149. tabs.currentChanged.connect(function(index) {
  150. pages.setCurrentIndex(index);
  151. });
  152. var batches = findChildObject(this, 'batches');
  153. var filter = findChildObject(this, 'filter');
  154. var filtervalue = findChildObject(this, 'filtervalue');
  155. filter.setText(QSettings.value("script/newbatch/filter", ""));
  156. filtervalue.setText(QSettings.value("script/newbatch/filtervalue", ""));
  157. var updateSchedule = function() {
  158. var filterclause = "";
  159. if(filter.text.length > 0) {
  160. filterclause = " AND data->'filters' @> "
  161. filterclause += "'[{\"key\":\"" + filter.text + '"';
  162. if(filtervalue.text.length > 0) {
  163. filterclause += ',"value":"' + filtervalue.text + '"';
  164. }
  165. filterclause += "}]'";
  166. }
  167. var q = "SELECT id, (SELECT name FROM items WHERE id = (data#>>'{roasted}')::numeric), (data#>>'{green_weight}')::numeric FROM scheduled_roasts WHERE machine IS NULL" + filterclause;
  168. batches.setQuery(q);
  169. };
  170. filter.editingFinished.connect(function() {
  171. QSettings.setValue("script/newbatch/filter", filter.text);
  172. updateSchedule();
  173. });
  174. filtervalue.editingFinished.connect(function() {
  175. QSettings.setValue("script/newbatch/filtervalue", filtervalue.text);
  176. updateSchedule();
  177. });
  178. updateSchedule();
  179. if(batches.rows() > 0) {
  180. tabs.setCurrentIndex(0);
  181. } else {
  182. tabs.setCurrentIndex(1);
  183. }
  184. batches.setHeaderData(0, TTR("batchWindow", "ID"));
  185. batches.setHeaderData(1, TTR("batchWindow", "Roasted Coffee"));
  186. batches.setHeaderData(2, TTR("batchWindow", "Green Weight"));
  187. batches.alternatingRowColors = true;
  188. batches.selectionMode = 1;
  189. batches.selectionBehavior = 1;
  190. batches.editTreiggers = 0;
  191. var snotifier = Application.subscribe("scheduledroastschange");
  192. snotifier.notify.connect(function() {
  193. updateSchedule();
  194. });
  195. var unitBox = findChildObject(this, 'unit');
  196. unitBox.addItem("g");
  197. unitBox.addItem("Kg");
  198. unitBox.addItem("oz");
  199. unitBox.addItem("lb");
  200. unitBox.currentIndex = (QSettings.value("script/batch_unit", unitBox.findText("lb")));
  201. unitBox['currentIndexChanged(int)'].connect(function() {
  202. QSettings.setValue("script/batch_unit", unitBox.currentIndex);
  203. });
  204. var machine = findChildObject(this, "machine");
  205. machine.setText(selectedRoasterName + " (" + selectedRoasterID + ")");
  206. var newMenu = findChildObject(this, 'new');
  207. newMenu.triggered.connect(function() {
  208. var bwindow = createWindow("batchWindow");
  209. bwindow.windowTitle = TTR("batchWindow", "Typica - [*]New Batch");
  210. });
  211. var batch = this;
  212. var table = findChildObject(this, 'greens');
  213. var green = findChildObject(this, 'green');
  214. var model = table.model();
  215. var lossField = findChildObject(this, 'wloss');
  216. lossField.maximumWidth = 80;
  217. var roasted = findChildObject(this, 'roasted');
  218. var roastwt = findChildObject(this, 'roast');
  219. roastwt.maximumWidth = 80;
  220. var scalesLayout = findChildObject(this, 'scales');
  221. scalesLayout.spacing = 10;
  222. var batchTag = findChildObject(this, 'batchTag');
  223. if(Windows.loggingWindow.scales.length > 0) {
  224. for(var i = 0; i < Windows.loggingWindow.scales.length; i++) {
  225. var scale = Windows.loggingWindow.scales[i];
  226. var label = new DragLabel();
  227. var weighButton = new QPushButton();
  228. weighButton.text = "Weigh";
  229. weighButton.clicked.connect(scale.weigh);
  230. label.updateMeasurement = function(m, u) {
  231. switch(unitBox.currentIndex) {
  232. case 0:
  233. this.text = Units.convertWeight(m, u, Units.Gram).toFixed(1);
  234. break;
  235. case 1:
  236. this.text = Units.convertWeight(m, u, Units.Kilogram).toFixed(4);
  237. break;
  238. case 2:
  239. this.text = Units.convertWeight(m, u, Units.Ounce).toFixed(3);
  240. break;
  241. case 3:
  242. this.text = Units.convertWeight(m, u, Units.Pound).toFixed(4);
  243. break;
  244. }
  245. };
  246. scalesLayout.addWidget(label);
  247. scalesLayout.addWidget(weighButton);
  248. scale.newMeasurement.connect(function(m, u) {
  249. label.updateMeasurement(m, u);
  250. });
  251. scale.weigh();
  252. unitBox['currentIndexChanged(int)'].connect(scale.weigh);
  253. }
  254. }
  255. var remainingStock = new Array();
  256. var query = new QSqlQuery();
  257. query.exec("SELECT id, quantity, (SELECT conversion FROM lb_bag_conversion WHERE item = id) FROM coffees WHERE quantity <> 0");
  258. while(query.next()) {
  259. remainingStock.push({id: query.value(0),
  260. quantity: query.value(1),
  261. conversion: query.value(2)});
  262. }
  263. query = query.invalidate();
  264. var convertToPounds = function(w, u) {
  265. switch(u) {
  266. case "g":
  267. return w * 0.0022;
  268. case "oz":
  269. return w * 0.0625;
  270. case "Kg":
  271. return w * 2.2;
  272. }
  273. return w;
  274. };
  275. var convertFromPounds = function(w, u) {
  276. switch(u) {
  277. case "g":
  278. return w / 0.0022;
  279. case "oz":
  280. return w / 0.0625;
  281. case "Kg":
  282. return w / 2.2;
  283. }
  284. return w;
  285. };
  286. var specnotes = findChildObject(this, 'specnotes');
  287. specnotes.readOnly = true;
  288. var lossspec = findChildObject(this, 'lossspec');
  289. var minfield = findChildObject(this, 'minroastweight');
  290. var midfield = findChildObject(this, 'expectedroastweight');
  291. var maxfield = findChildObject(this, 'maxroastweight');
  292. var minloss = 0;
  293. var maxloss = 0;
  294. var expectloss = 0;
  295. var updateGreenTable = function() {
  296. var deleteRow = -1;
  297. /* The combo box delegate updates user data before display data
  298. and this code is executed before the model update is fully
  299. complete. Rather than rely on this behavior continuing, we
  300. check that the display value has also been updated and defer
  301. row removal until both updates are complete.
  302. */
  303. while((deleteRow = table.findData("delete", 0)) > -1) {
  304. if(table.data(deleteRow, 0, 0) == "Delete") {
  305. table.removeRow(table.findData("delete", 0));
  306. } else {
  307. break;
  308. }
  309. }
  310. green.text = table.columnSum(1, 0);
  311. table.resizeColumnToContents(0);
  312. var gid = 0;
  313. var r = 0;
  314. while(gid >= 0)
  315. {
  316. gid = Number(table.data(r, 0, 32));
  317. if(isNaN(gid))
  318. {
  319. gid = -1;
  320. break;
  321. }
  322. var bagConversion = 1;
  323. for(var i = 0; i < remainingStock.length; i++)
  324. {
  325. if(gid == Number(remainingStock[i].id))
  326. {
  327. var displayValue = Number(remainingStock[i].quantity);
  328. bagConversion = Number(remainingStock[i].conversion);
  329. if(!isNaN(Number(table.data(r, 1, 0))))
  330. {
  331. var change = Number(table.data(r, 1, 0));
  332. switch(unitBox.currentIndex)
  333. {
  334. case 0:
  335. change = convertToPounds(change, "g");
  336. break;
  337. case 1:
  338. change = convertToPounds(change, "Kg");
  339. break;
  340. case 2:
  341. change = convertToPounds(change, "oz");
  342. break;
  343. }
  344. displayValue -= change;
  345. }
  346. var rowColor = QBrush("black");
  347. if(displayValue < 0)
  348. {
  349. rowColor = QBrush("red");
  350. }
  351. var bagCount = (displayValue / bagConversion).toFixed(2);
  352. switch(unitBox.currentIndex)
  353. {
  354. case 0:
  355. displayValue = convertFromPounds(displayValue, "g");
  356. break;
  357. case 1:
  358. displayValue = convertFromPounds(displayValue, "Kg");
  359. break;
  360. case 2:
  361. displayValue = convertFromPounds(displayValue, "oz");
  362. break;
  363. }
  364. displayValue = "" + Number(displayValue).toFixed(3) + " (" + Number(bagCount).toFixed(3) + " bags)";
  365. if(table.data(r, 2, 0) != displayValue)
  366. {
  367. table.setData(r, 2, displayValue, 0);
  368. table.setData(r, 2, displayValue, 2);
  369. table.resizeColumnToContents(2);
  370. table.setData(r, 0, rowColor, 9);
  371. table.setData(r, 1, rowColor, 9);
  372. table.setData(r, 2, rowColor, 9);
  373. }
  374. }
  375. }
  376. r++;
  377. }
  378. if(parseFloat(green.text) > 0)
  379. {
  380. var expectedLossDesc = "";
  381. if(lossspec.text.length > 0) {
  382. if(minloss != expectloss) {
  383. minfield.text = (-(parseFloat(green.text)) * (maxloss - 1)).toFixed(2);
  384. maxfield.text = (-(parseFloat(green.text)) * (minloss - 1)).toFixed(2);
  385. }
  386. midfield.text = (-(parseFloat(green.text)) * (expectloss - 1)).toFixed(2);
  387. }
  388. if(parseFloat(roastwt.text) > 0)
  389. {
  390. lossField.text = (((parseFloat(green.text) - parseFloat(roastwt.text)) / parseFloat(green.text)) * 100).toFixed(2) + "%";
  391. }
  392. else
  393. {
  394. lossField.text = "100%";
  395. }
  396. }
  397. };
  398. model.dataChanged.connect(updateGreenTable);
  399. unitBox['currentIndexChanged(int)'].connect(updateGreenTable);
  400. roastwt.textChanged.connect(function() {
  401. if(parseFloat(green.text) > 0)
  402. {
  403. if(parseFloat(roastwt.text) > 0)
  404. {
  405. lossField.text = (((parseFloat(green.text) - parseFloat(roastwt.text)) / parseFloat(green.text)) * 100).toFixed(2) + "%";
  406. }
  407. else
  408. {
  409. lossField.text = "100%";
  410. }
  411. }
  412. });
  413. var profilebutton = findChildObject(this, 'load');
  414. profilebutton.setEnabled(false);
  415. var wholespec = findChildObject(this, 'wholespec');
  416. var groundspec = findChildObject(this, 'groundspec');
  417. roasted['currentIndexChanged(int)'].connect(function() {
  418. table.clear();
  419. var query = new QSqlQuery();
  420. var q = "SELECT EXISTS(SELECT 1 FROM item_files WHERE item = ";
  421. q = q + roasted.currentData();
  422. q = q + ")";
  423. query.exec(q);
  424. if(query.next())
  425. {
  426. if(query.value(0) == 'false')
  427. {
  428. profilebutton.setEnabled(false);
  429. }
  430. else
  431. {
  432. profilebutton.setEnabled(true);
  433. }
  434. }
  435. else
  436. {
  437. profilebutton.setEnabled(false);
  438. }
  439. var title = "Typica - [*]New Batch (";
  440. title = title + roasted.currentText;
  441. title = title + ")";
  442. batch.windowTitle = title;
  443. q = "SELECT unroasted_id FROM roasting_log WHERE roasted_id = ";
  444. q = q + roasted.currentData();
  445. q = q + " AND time = (SELECT max(time) FROM roasting_log WHERE roasted_id = ";
  446. q = q + roasted.currentData();
  447. q = q + ")";
  448. query.exec(q);
  449. if(query.next())
  450. {
  451. var unroasted_items = sqlToArray(query.value(0));
  452. var names = [];
  453. q = "SELECT name FROM items WHERE id = :id AND quantity <> 0";
  454. query.prepare(q);
  455. var allInStock = true;
  456. for(var i = 0; i < unroasted_items.length; i++)
  457. {
  458. query.bind("id", unroasted_items[i]);
  459. query.exec();
  460. if(query.next())
  461. {
  462. names[i] = query.value(0);
  463. }
  464. else
  465. {
  466. allInStock = false;
  467. }
  468. }
  469. if(allInStock)
  470. {
  471. for(var i = 0; i < unroasted_items.length; i++)
  472. {
  473. table.setData(i, 0, names[i], 0);
  474. table.setData(i, 0, unroasted_items[i], 32);
  475. }
  476. }
  477. }
  478. query.prepare("SELECT loss, tolerance, notes, spec FROM roasting_specification WHERE item = :id1 AND time = (SELECT max(time) FROM roasting_specification WHERE item = :id2)");
  479. query.bind(":id1", roasted.currentData());
  480. query.bind(":id2", roasted.currentData());
  481. query.exec();
  482. var lossSpecDescription = "";
  483. if(query.next()) {
  484. if(query.value(0).length > 0) {
  485. lossSpecDescription += (Number(query.value(0)) * 100).toFixed(2);
  486. minloss = Number(query.value(0));
  487. maxloss = Number(query.value(0));
  488. expectloss = Number(query.value(0));
  489. }
  490. if(query.value(1).length > 0) {
  491. lossSpecDescription += " +/- " + (Number(query.value(1)) * 100).toFixed(2);
  492. minloss -= Number(query.value(1));
  493. maxloss += Number(query.value(1));
  494. }
  495. if(lossSpecDescription.length > 0) {
  496. lossSpecDescription += "%";
  497. }
  498. lossspec.text = lossSpecDescription;
  499. specnotes.plainText = query.value(2);
  500. if(query.value(3).length > 0) {
  501. var spec = JSON.parse(query.value(3));
  502. var wholespectext = "";
  503. var groundspectext = "";
  504. if(spec.color.whole.expected != undefined) {
  505. wholespectext += spec.color.whole.expected;
  506. if(spec.color.whole.tolerance != undefined) {
  507. wholespectext += " +/- " + spec.color.whole.tolerance;
  508. }
  509. wholespec.text = wholespectext;
  510. } else {
  511. wholespec.text = "";
  512. }
  513. if(spec.color.ground.expected != undefined) {
  514. groundspectext += spec.color.ground.expected;
  515. if(spec.color.ground.tolerance != undefined) {
  516. groundspectext += " +/- " + spec.color.ground.tolerance;
  517. }
  518. groundspec.text = groundspectext;
  519. } else {
  520. groundspec.text = "";
  521. }
  522. } else {
  523. wholespec.text = "";
  524. groundspec.text = "";
  525. }
  526. } else {
  527. lossspec.text = "";
  528. specnotes.plainText = "";
  529. wholespec.text = "";
  530. groundspec.text = "";
  531. }
  532. query = query.invalidate();
  533. drawTag();
  534. });
  535. var validateCapacity = function() {
  536. if(checkCapacity == "true") {
  537. if(convertToPounds(parseFloat(green.text), unitBox.currentText) > poundsCapacity) {
  538. return false;
  539. }
  540. }
  541. return true;
  542. }
  543. var duration = findChildObject(this, 'duration');
  544. var timefield = findChildObject(this, 'time');
  545. var markTaken = function(id) {
  546. var query = new QSqlQuery();
  547. query.prepare("UPDATE scheduled_roasts SET machine = :mid WHERE id = :id");
  548. query.bind(":mid", selectedRoasterID);
  549. query.bind(":id", scheduledID);
  550. query.exec();
  551. query = query.invalidate();
  552. }
  553. profilebutton.clicked.connect(function() {
  554. var proceed = false;
  555. if(validateCapacity()) {
  556. proceed = true;
  557. } else {
  558. proceed = displayWarning(TTR("batchWindow", "Suspicious Input"),
  559. TTR("batchWindow", "Entered green coffee weight exceeds maximum batch size. Continue?"));
  560. }
  561. if((proceed == true) && (timefield.text.length != 0)) {
  562. proceed = displayWarning(TTR("batchWindow", "Batch Already Roasted"),
  563. TTR("batchWindow", "Roasting data already exists for this batch. Roasting another batch from this window will overwrite existing data. Are you sure you want to do this?"));
  564. }
  565. if(proceed) {
  566. doLoadProfile();
  567. }
  568. });
  569. var doLoadProfile = function() {
  570. if(scheduledID > 0) {
  571. markTaken();
  572. }
  573. batch.windowModified = true;
  574. currentBatchInfo = batch;
  575. query = new QSqlQuery();
  576. var q = "SELECT files FROM item_files WHERE item = :item AND time = (SELECT max(time) FROM item_files WHERE item = :again)";
  577. query.prepare(q);
  578. query.bind(":item", Number(roasted.currentData()));
  579. query.bind(":again", Number(roasted.currentData()));
  580. query.exec();
  581. if(query.next())
  582. {
  583. var files = query.value(0);
  584. files = files.replace("{", "(");
  585. files = files.replace("}", ")");
  586. q = "SELECT file, name FROM files WHERE id IN ";
  587. q = q + files;
  588. q = q + " AND type = 'profile'";
  589. query.exec(q);
  590. if(query.next())
  591. {
  592. var buffer = new QBuffer(query.value(0));
  593. var pname = query.value(1);
  594. Windows.loggingWindow.loadPlan(buffer, pname);
  595. }
  596. }
  597. query = query.invalidate();
  598. }
  599. var noprofilebutton = findChildObject(this, 'noprofile');
  600. noprofilebutton.clicked.connect(function() {
  601. var proceed = false;
  602. if(validateCapacity()) {
  603. proceed = true;
  604. } else {
  605. proceed = displayWarning(TTR("batchWindow", "Suspicious Input"),
  606. TTR("batchWindow", "Entered green coffee weight exceeds maximum batch size. Continue?"));
  607. }
  608. if((proceed == true) && (timefield.text.length != 0)) {
  609. proceed = displayWarning(TTR("batchWindow", "Batch Already Roasted"),
  610. TTR("batchWindow", "Roasting data already exists for this batch. Roasting another batch from this window will overwrite existing data. Are you sure you want to do this?"));
  611. }
  612. if(proceed) {
  613. doNoProfile();
  614. }
  615. });
  616. var doNoProfile = function() {
  617. batch.windowModified = true;
  618. currentBatchInfo = batch;
  619. Windows.loggingWindow.clearLog();
  620. Windows.loggingWindow.raise();
  621. Windows.loggingWindow.activateWindow();
  622. if(scheduledID > 0) {
  623. markTaken();
  624. }
  625. }
  626. var submitbutton = findChildObject(this, 'submit');
  627. var notes = findChildObject(this, 'annotation');
  628. var approval = findChildObject(this, 'approval');
  629. approval.checked = true;
  630. var target = findChildObject(this, 'target');
  631. var greenCheck = function() {
  632. var itemArray = table.columnArray(0, 32).split("\\s*,\\s*");
  633. var weightArray = table.columnArray(1, 0).split("\\s*,\\s*");
  634. return (itemArray.length == weightArray.length) && (itemArray.length > 0);
  635. }
  636. var checkSubmitEnable = function () {
  637. if(roasted.currentIndex > 0) {
  638. if(timefield.text.length > 0) {
  639. if(duration.text.length > 0) {
  640. if(batch.tempData.length > 0) {
  641. if(green.text.length > 0) {
  642. if(greenCheck()) {
  643. return true;
  644. }
  645. }
  646. }
  647. }
  648. }
  649. }
  650. return false;
  651. }
  652. submitbutton.clicked.connect(function() {
  653. var proceed = false;
  654. if(validateCapacity()) {
  655. proceed = true;
  656. } else {
  657. proceed = displayWarning(TTR("batchWindow", "Suspicious Input"),
  658. TTR("batchWindow", "Entered green coffee weight exceeds maximum batch size. Continue?"));
  659. }
  660. if(proceed) {
  661. if(checkSubmitEnable()) {
  662. doSubmit();
  663. } else {
  664. displayError(TTR("batchWindow", "Incomplete Input"),
  665. TTR("batchWindow", "Some required information is not available. Please check inputs and try again."));
  666. }
  667. }
  668. });
  669. var filenofield = findChildObject(this, 'filenofield');
  670. this.endBatch = function() {
  671. var q = "INSERT INTO files (id, name, type, note, file) VALUES(default, :name, 'profile', NULL, :data) RETURNING id";
  672. var query = new QSqlQuery();
  673. query.prepare(q);
  674. query.bind(":name", timefield.text + " " + roasted.currentText);
  675. query.bindFileData(":data", batch.tempData);
  676. query.exec();
  677. query.next();
  678. filenofield.text = query.value(0);
  679. var file = new QFile(batch.tempData);
  680. file.remove();
  681. drawTag();
  682. }
  683. var wholecolor = findChildObject(this, 'wholecolor');
  684. var groundcolor = findChildObject(this, 'groundcolor');
  685. var doSubmit = function() {
  686. checkQuery = new QSqlQuery();
  687. checkQuery.exec("SELECT 1 FROM machine WHERE id = " + selectedRoasterID);
  688. if(!checkQuery.next())
  689. {
  690. checkQuery.prepare("INSERT INTO machine (id, name) VALUES(:id, :name)");
  691. checkQuery.bind(":id", selectedRoasterID);
  692. checkQuery.bind(":name", selectedRoasterName);
  693. checkQuery.exec();
  694. }
  695. checkQuery = checkQuery.invalidate();
  696. query = new QSqlQuery();
  697. var fileno = Number(filenofield.text);
  698. 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, person, additional_data) VALUES(:time, ";
  699. q2 = q2 + table.columnArray(0, 32);
  700. q2 = q2 + ", ";
  701. for(var i = 0; table.data(i, 1, 0).value != ""; i++)
  702. {
  703. table.setData(i, 1, convertToPounds(parseFloat(table.data(i, 1, 0)), unitBox.currentText) ,32)
  704. }
  705. q2 = q2 + table.columnArray(1, 32);
  706. q2 = q2 + ", ";
  707. q2 = q2 + convertToPounds(parseFloat(green.text), unitBox.currentText);
  708. q2 = q2 + ", ";
  709. q2 = q2 + roasted.currentData();
  710. q2 = q2 + ", ";
  711. q2 = q2 + convertToPounds(parseFloat(roastwt.text), unitBox.currentText);
  712. q2 = q2 + ", 'ROAST', :annotation, ";
  713. q2 = q2 + selectedRoasterID;
  714. q2 = q2 + ", :duration, :approval, NULL, NULL, NULL, NULL, '{";
  715. q2 = q2 + fileno;
  716. q2 = q2 + "}', :user, :extradata) RETURNING time";
  717. query2 = new QSqlQuery();
  718. query2.prepare(q2);
  719. query2.bind(":time", timefield.text);
  720. query2.bind(":annotation", notes.plainText);
  721. query2.bind(":duration", duration.text);
  722. query2.bind(":approval", approval.checked);
  723. query2.bind(":user", Application.currentTypicaUser());
  724. var extradata = new Object;
  725. var colordata = new Object;
  726. if(wholecolor.value.length > 0) {
  727. colordata.whole = wholecolor.value;
  728. }
  729. if(groundcolor.value.length > 0) {
  730. colordata.ground = groundcolor.value;
  731. }
  732. if(colordata.whole || colordata.ground) {
  733. extradata.color = colordata;
  734. }
  735. query2.bind(":extradata", JSON.stringify(extradata));
  736. query2.exec();
  737. if(!query2.next()) {
  738. displayError(TTR("batchWindow", "Database Insert Failed"), TTR("batchWindow", "Failed to save batch to database. Please check inputs and connection and try again."));
  739. query2 = query2.invalidate();
  740. query = query.invalidate();
  741. return;
  742. }
  743. query2 = query2.invalidate();
  744. if(target.checked) {
  745. var q3 = "INSERT INTO item_files (time, item, files) VALUES(:time, :item, '{";
  746. q3 = q3 + fileno;
  747. q3 = q3 + "}')";
  748. query.prepare(q3);
  749. query.bind(":time", timefield.text);
  750. query.bind(":item", roasted.currentData());
  751. query.exec();
  752. }
  753. if(scheduledID > 0) {
  754. query.prepare("UPDATE scheduled_roasts SET machine = :mid, time = :time WHERE id = :id");
  755. query.bind(":mid", selectedRoasterID);
  756. query.bind(":time", timefield.text);
  757. query.bind(":id", scheduledID);
  758. query.exec();
  759. }
  760. query = query.invalidate();
  761. batch.windowModified = false;
  762. batch.close();
  763. }
  764. function drawTag() {
  765. var buffer = new QBuffer;
  766. buffer.open(3);
  767. var output = new XmlWriter(buffer);
  768. output.writeStartDocument("1.0");
  769. output.writeDTD('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg.dtd">');
  770. output.writeStartElement("html");
  771. output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
  772. output.writeStartElement("head");
  773. var styleFile = new QFile(QSettings.value("config") + "/Scripts/batchtag.css");
  774. styleFile.open(1);
  775. output.writeTextElement("style", styleFile.readToString());
  776. styleFile.close();
  777. styleFile = new QFile(QSettings.value("config") + "/Scripts/barcode.css");
  778. styleFile.open(1);
  779. output.writeTextElement("style", styleFile.readToString());
  780. styleFile.close();
  781. output.writeStartElement("script");
  782. scriptFile = new QFile(QSettings.value("config") + "/Scripts/qrcode.js");
  783. scriptFile.open(1);
  784. output.writeCDATA(scriptFile.readToString());
  785. scriptFile.close();
  786. output.writeEndElement();
  787. output.writeStartElement("script");
  788. scriptFile = new QFile(QSettings.value("config") + "/Scripts/barcode.js");
  789. scriptFile.open(1);
  790. output.writeCDATA(scriptFile.readToString());
  791. scriptFile.close();
  792. output.writeEndElement();
  793. output.writeEndElement();
  794. output.writeStartElement("body");
  795. output.writeStartElement("h1");
  796. output.writeCharacters(roasted.currentText);
  797. output.writeEndElement();
  798. output.writeTextElement("span", "Roasted at: " + timefield.text);
  799. output.writeTextElement("span", "On machine: " + machine.text);
  800. output.writeTextElement("span", "Batch file: " + filenofield.text);
  801. output.writeStartElement("div");
  802. output.writeAttribute("id", "barcode");
  803. output.writeAttribute("class", "barcode128h");
  804. output.writeAttribute("align", "center");
  805. output.writeEndElement();
  806. output.writeStartElement("script");
  807. var c128data = 'var strBarcodeHTML = code128("';
  808. c128data += filenofield.text;
  809. c128data += '", "C");'
  810. c128data += 'document.getElementById("barcode").innerHTML = strBarcodeHTML;';
  811. output.writeCDATA(c128data);
  812. output.writeEndElement();
  813. output.writeStartElement("div");
  814. output.writeAttribute("id", "container");
  815. output.writeEndElement();
  816. output.writeStartElement("script");
  817. var tag = {g: "Typica", m: Number(selectedRoasterID), v: 1};
  818. if(timefield.text.length > 0) {
  819. tag.t = timefield.text;
  820. }
  821. if(filenofield.text.length > 0) {
  822. tag.f = Number(filenofield.text);
  823. }
  824. var scriptData = 'var width = document.getElementById("container").offsetWidth;';
  825. scriptData += 'var qrcode = new QRCode({content: \'';
  826. scriptData += JSON.stringify(tag);
  827. scriptData += '\', width: width, height: width});';
  828. scriptData += 'var svg = qrcode.svg();';
  829. scriptData += 'document.getElementById("container").innerHTML = svg;';
  830. output.writeCDATA(scriptData);
  831. output.writeEndElement();
  832. output.writeEndElement();
  833. output.writeEndElement();
  834. output.writeEndDocument();
  835. batchTag.setContent(buffer);
  836. buffer.close();
  837. };
  838. drawTag();
  839. var printMenu = findChildObject(this, 'print');
  840. printMenu.triggered.connect(function() {
  841. batchTag.print();
  842. });
  843. var printers = findChildObject(this, 'printerlist');
  844. printers.currentIndex = printers.findText(QSettings.value("script/batchtagprinter"));
  845. printers['currentIndexChanged(int)'].connect(function() {
  846. QSettings.setValue("script/batchtagprinter", printers.currentText);
  847. });
  848. var printbutton = findChildObject(this, 'printbutton');
  849. printbutton.clicked.connect(function() {
  850. batchTag.print(printers.currentText);
  851. });
  852. var loadBatch = function(id) {
  853. var query = new QSqlQuery();
  854. query.prepare("SELECT data FROM scheduled_roasts WHERE id = :id");
  855. query.bind(":id", id);
  856. query.exec();
  857. if(query.next()) {
  858. scheduledID = id;
  859. tabs.setCurrentIndex(1);
  860. var d = JSON.parse(query.value(0));
  861. if(d.roasted) {
  862. roasted.currentIndex = roasted.findData(d.roasted);
  863. }
  864. if(d.greens) {
  865. query.prepare("SELECT name FROM items WHERE id = :id");
  866. for(var i = 0; i < d.greens.length; i++) {
  867. query.bind(":id", d.greens[i].id);
  868. query.exec();
  869. if(query.next()) {
  870. table.setData(i, 0, query.value(0), 0);
  871. table.setData(i, 0, d.greens[i].id, 32);
  872. switch(unitBox.currentIndex) {
  873. case 0:
  874. table.setData(i, 1, convertFromPounds(d.greens[i].weight, "g"), 0);
  875. break;
  876. case 1:
  877. table.setData(i, 1, convertFromPounds(d.greens[i].weight, "Kg"), 0);
  878. break;
  879. case 2:
  880. table.setData(i, 1, convertFromPounds(d.greens[i].weight, "oz"), 0);
  881. break;
  882. default:
  883. table.setData(i, 1, d.greens[i].weight, 0);
  884. break;
  885. }
  886. }
  887. }
  888. }
  889. } else {
  890. displayInfo(TTR("batchWindow", "Search Failed"),
  891. TTR("batchWindow", "Scheduled batch ID not found."));
  892. }
  893. query = query.invalidate();
  894. drawTag();
  895. };
  896. batches.selectEntry.connect(function(id) {
  897. loadBatch(id);
  898. });
  899. var search = findChildObject(this, 'search');
  900. search.returnPressed.connect(function() {
  901. if(search.text.length == 0) {
  902. return;
  903. }
  904. if(Number(search.text)) {
  905. loadBatch(Number(search.text));
  906. }
  907. search.text = "";
  908. });
  909. ]]>
  910. </program>
  911. </window>