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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. <window id="schedule">
  2. <menu name="File">
  3. <item id="print" shortcut="Ctrl+P">Print...</item>
  4. </menu>
  5. <menu name="Schedule">
  6. <item id="deleteall">Delete All</item>
  7. </menu>
  8. <layout type="horizontal">
  9. <splitter type="horizontal" id="splitter">
  10. <widget>
  11. <layout type="vertical">
  12. <layout type="horizontal">
  13. <button name="Delete" type="push" id="deletebutton" />
  14. <stretch />
  15. <button name="New" type="push" id="newbutton" />
  16. </layout>
  17. <sqlview id="batches" />
  18. </layout>
  19. </widget>
  20. <widget>
  21. <layout type="stack" id="editingstack">
  22. <page>
  23. </page>
  24. <page>
  25. <layout type="vertical">
  26. <layout type="horizontal">
  27. <label>Scheduled Batch ID</label>
  28. <line id="batch" writable="false" />
  29. </layout>
  30. <tabbar id="tabs" />
  31. <layout type="stack" id="pages">
  32. <page>
  33. <layout type="vertical">
  34. <layout type="horizontal">
  35. <label>Unit:</label>
  36. <sqldrop id="unit" />
  37. <stretch />
  38. </layout>
  39. <layout type="horizontal">
  40. <label>Roasted Coffee:</label>
  41. <sqldrop data="0" display="1" showdata="true" id="roasted">
  42. <null />
  43. <query>SELECT id, name FROM items WHERE category='Coffee: Roasted' AND id IN (SELECT item FROM current_items) ORDER BY name</query>
  44. </sqldrop>
  45. <stretch />
  46. </layout>
  47. <label>Green Coffee:</label>
  48. <sqltablearray columns="3" id="greens">
  49. <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>
  50. <column name="Weight" delegate="numeric" />
  51. <column name="Remaining" />
  52. </sqltablearray>
  53. <layout type="horizontal">
  54. <label>Green Weight:</label>
  55. <line id="green" writable="false">0.0</line>
  56. </layout>
  57. </layout>
  58. </page>
  59. <page>
  60. <layout type="vertical">
  61. <sqltablearray columns="2" id="filters">
  62. <column name="Filter" />
  63. <column name="Value" />
  64. </sqltablearray>
  65. </layout>
  66. </page>
  67. <page>
  68. <layout type="vertical">
  69. <webview id="batchTag" />
  70. </layout>
  71. </page>
  72. </layout>
  73. <layout type="horizontal">
  74. <printerselector id="printerlist" />
  75. <button name="Print" id="printbutton" type="push" />
  76. </layout>
  77. <layout type="horizontal">
  78. <stretch />
  79. <button name="Save" id="savebutton" type="push" />
  80. </layout>
  81. </layout>
  82. </page>
  83. </layout>
  84. </widget>
  85. </splitter>
  86. </layout>
  87. <program>
  88. <![CDATA[
  89. var window = this;
  90. var saved = false;
  91. this.windowTitle = TTR("schedule", "Typica - Roasting Schedule");
  92. var splitter = findChildObject(this, 'splitter');
  93. window.aboutToClose.connect(function() {
  94. splitter.saveState("script/schedule/splitter");
  95. });
  96. splitter.restoreState("script/schedule/splitter");
  97. var deletebutton = findChildObject(this, 'deletebutton');
  98. deletebutton.enabled = false;
  99. var editingstack = findChildObject(this, 'editingstack');
  100. var newbutton = findChildObject(this, 'newbutton');
  101. var batch = findChildObject(this, 'batch');
  102. var roasted = findChildObject(this, 'roasted');
  103. var filters = findChildObject(this, 'filters');
  104. var table = findChildObject(this, 'greens');
  105. var green = findChildObject(this, 'green');
  106. var pages = findChildObject(this, 'pages');
  107. newbutton.clicked.connect(function() {
  108. saved = false;
  109. roasted.setCurrentIndex(0);
  110. filters.clear();
  111. table.clear();
  112. green.text = "0.0";
  113. pages.setCurrentIndex(0);
  114. editingstack.setCurrentIndex(1);
  115. var query = new QSqlQuery();
  116. query.exec("SELECT nextval('scheduled_roasts_id_seq'::regclass)");
  117. if(query.next()) {
  118. batch.text = query.value(0);
  119. }
  120. query = query.invalidate();
  121. drawTag();
  122. });
  123. var batches = findChildObject(this, 'batches');
  124. batches.setQuery("SELECT id, (SELECT name FROM items WHERE id = (data#>>'{roasted}')::numeric), (data#>>'{green_weight}')::numeric FROM scheduled_roasts WHERE machine IS NULL");
  125. batches.setHeaderData(0, TTR("schedule", "ID"));
  126. batches.setHeaderData(1, TTR("schedule", "Roasted Coffee"));
  127. batches.setHeaderData(2, TTR("schedule", "Green Weight"));
  128. batches.alternatingRowColors = true;
  129. batches.selectionMode = 1;
  130. batches.selectionBehavior = 1;
  131. batches.editTriggers = 0;
  132. var model = table.model();
  133. var convertToPounds = function(w, u) {
  134. switch(u) {
  135. case "g":
  136. return w * 0.0022;
  137. case "oz":
  138. return w * 0.0625;
  139. case "Kg":
  140. return w * 2.2;
  141. }
  142. return w;
  143. };
  144. var convertFromPounds = function(w, u) {
  145. switch(u) {
  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. batches.selectEntry.connect(function(id) {
  156. saved = true;
  157. pages.setCurrentIndex(0);
  158. editingstack.setCurrentIndex(1);
  159. batch.text = id;
  160. var query = new QSqlQuery();
  161. query.prepare("SELECT data FROM scheduled_roasts WHERE id = :id");
  162. query.bind(":id", id);
  163. query.exec();
  164. if(query.next()) {
  165. var d = JSON.parse(query.value(0));
  166. if(d.roasted) {
  167. roasted.currentIndex = roasted.findData(d.roasted);
  168. }
  169. if(d.greens) {
  170. query.prepare("SELECT name FROM items WHERE id = :id");
  171. for(var i = 0; i < d.greens.length; i++) {
  172. query.bind(":id", d.greens[i].id);
  173. query.exec();
  174. if(query.next()) {
  175. table.setData(i, 0, query.value(0), 0);
  176. table.setData(i, 0, d.greens[i].id, 32);
  177. switch(unitBox.currentIndex) {
  178. case 0:
  179. table.setData(i, 1, convertFromPounds(d.greens[i].weight, "g"), 0);
  180. break;
  181. case 1:
  182. table.setData(i, 1, convertFromPounds(d.greens[i].weight, "Kg"), 0);
  183. break;
  184. case 2:
  185. table.setData(i, 1, convertFromPounds(d.greens[i].weight, "oz"), 0);
  186. break;
  187. default:
  188. table.setData(i, 1, d.greens[i].weight, 0);
  189. break;
  190. }
  191. }
  192. }
  193. }
  194. if(d.filters) {
  195. for(var i = 0; i < d.filters.length; i++) {
  196. filters.setData(i, 0, d.filters[i].key, 0);
  197. filters.setData(i, 1, d.filters[i].value, 0);
  198. }
  199. }
  200. }
  201. query = query.invalidate();
  202. drawTag();
  203. deletebutton.enabled = true;
  204. });
  205. var tabs = findChildObject(this, 'tabs');
  206. tabs.addTab(TTR("schedule", "Coffee"));
  207. tabs.addTab(TTR("schedule", "Filters"));
  208. tabs.addTab(TTR("schedule", "Batch Tag"));
  209. tabs.currentChanged.connect(function(index) {
  210. pages.setCurrentIndex(index);
  211. });
  212. var unitBox = findChildObject(this, 'unit');
  213. unitBox.addItem("g");
  214. unitBox.addItem("Kg");
  215. unitBox.addItem("oz");
  216. unitBox.addItem("lb");
  217. unitBox.currentIndex = QSettings.value("script/batch_unit", unitBox.findText("lb"));
  218. unitBox['currentIndexChanged(int)'].connect(function() {
  219. QSettings.setValue("script/batch_unit", unitBox.currentIndex);
  220. });
  221. var remainingStock = new Array();
  222. var query = new QSqlQuery();
  223. query.exec("SELECT id, quantity, (SELECT conversion FROM lb_bag_conversion WHERE item = id) FROM coffees WHERE quantity <> 0");
  224. while(query.next()) {
  225. remainingStock.push({id: query.value(0),
  226. quantity: query.value(1),
  227. conversion: query.value(2)});
  228. }
  229. query = query.invalidate();
  230. var updateGreenTable = function() {
  231. var deleteRow = -1;
  232. while((deleteRow = table.findData("delete", 0)) > -1) {
  233. if(table.data(deleteRow, 0, 0) == "Delete") {
  234. table.removeRow(table.findData("delete", 0));
  235. } else {
  236. break;
  237. }
  238. }
  239. green.text = table.columnSum(1, 0);
  240. table.resizeColumnToContents(0);
  241. var gid = 0;
  242. var r = 0;
  243. while(gid >= 0) {
  244. gid = Number(table.data(r, 0, 32));
  245. if(isNaN(gid)) {
  246. gid = -1;
  247. break;
  248. }
  249. var bagConversion = 1;
  250. for(var i = 0; i < remainingStock.length; i++) {
  251. if(gid == Number(remainingStock[i].id)) {
  252. var displayValue = Number(remainingStock[i].quantity);
  253. bagConversion = Number(remainingStock[i].conversion);
  254. if(!isNaN(Number(table.data(r, 1, 0)))) {
  255. var change = Number(table.data(r, 1, 0));
  256. switch(unitBox.currentIndex) {
  257. case 0:
  258. change = convertToPounds(change, "g");
  259. break;
  260. case 1:
  261. change = convertToPounds(change, "Kg");
  262. break;
  263. case 2:
  264. change = convertToPounds(change, "oz");
  265. break;
  266. }
  267. displayValue -= change;
  268. }
  269. var bagCount = (displayValue / bagConversion).toFixed(2);
  270. switch(unitBox.currentIndex) {
  271. case 0:
  272. displayValue = convertFromPounds(displayValue, "g");
  273. break;
  274. case 1:
  275. displayValue = convertFromPounds(displayValue, "Kg");
  276. break;
  277. case 2:
  278. displayValue = convertFromPounds(displayValue, "oz");
  279. break;
  280. }
  281. displayValue = "" + Number(displayValue).toFixed(3) + " (" + Number(bagCount).toFixed(3) + " bags)";
  282. if(table.data(r, 2, 0) != displayValue) {
  283. table.setData(r, 2, displayValue, 0);
  284. table.setData(r, 2, displayValue, 2);
  285. table.resizeColumnToContents(2);
  286. }
  287. }
  288. }
  289. r++;
  290. }
  291. drawTag();
  292. };
  293. model.dataChanged.connect(updateGreenTable);
  294. unitBox['currentIndexChanged(int)'].connect(updateGreenTable);
  295. roasted['currentIndexChanged(int)'].connect(function() {
  296. table.clear();
  297. var query = new QSqlQuery();
  298. var q = "SELECT unroasted_id FROM roasting_log WHERE roasted_id = ";
  299. q += roasted.currentData();
  300. q += " AND time = (SELECT max(time) FROM roasting_log WHERE roasted_id = ";
  301. q += roasted.currentData();
  302. q += ")";
  303. query.exec(q);
  304. if(query.next()) {
  305. var unroasted_items = sqlToArray(query.value(0));
  306. var names = [];
  307. q = "SELECT name FROM items WHERE id = :id AND quantity <> 0";
  308. query.prepare(q);
  309. var allInStock = true;
  310. for(var i = 0; i < unroasted_items.length; i++) {
  311. query.bind("id", unroasted_items[i]);
  312. query.exec();
  313. if(query.next()) {
  314. names[i] = query.value(0);
  315. } else {
  316. allInStock = false;
  317. }
  318. }
  319. if(allInStock) {
  320. for(var i = 0; i < unroasted_items.length; i++) {
  321. table.setData(i, 0, names[i], 0);
  322. table.setData(i, 0, unroasted_items[i], 32);
  323. }
  324. }
  325. }
  326. query = query.invalidate();
  327. drawTag();
  328. });
  329. var batchTag = findChildObject(this, 'batchTag');
  330. var filtersModel = filters.model();
  331. filtersModel.dataChanged.connect(function() {
  332. drawTag()
  333. });
  334. function drawTag() {
  335. var buffer = new QBuffer;
  336. buffer.open(3);
  337. var output = new XmlWriter(buffer);
  338. output.writeStartDocument("1.0");
  339. output.writeDTD('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg.dtd">');
  340. output.writeStartElement("html");
  341. output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
  342. output.writeStartElement("head");
  343. var styleFile = new QFile(QSettings.value("config") + "/Scripts/batchtag.css");
  344. styleFile.open(1);
  345. output.writeTextElement("style", styleFile.readToString());
  346. styleFile.close();
  347. styleFile = new QFile(QSettings.value("config") + "/Scripts/barcode.css");
  348. styleFile.open(1);
  349. output.writeTextElement("style", styleFile.readToString());
  350. styleFile.close();
  351. output.writeStartElement("script");
  352. scriptFile = new QFile(QSettings.value("config") + "/Scripts/barcode.js");
  353. scriptFile.open(1);
  354. output.writeCDATA(scriptFile.readToString());
  355. scriptFile.close();
  356. output.writeEndElement();
  357. output.writeEndElement();
  358. output.writeStartElement("body");
  359. output.writeStartElement("h1");
  360. output.writeCharacters(roasted.currentText);
  361. output.writeEndElement();
  362. output.writeStartElement("div");
  363. output.writeAttribute("id", "barcode");
  364. output.writeAttribute("class", "barcode128h");
  365. output.writeAttribute("align", "center");
  366. output.writeEndElement();
  367. output.writeStartElement("script");
  368. output.writeCharacters("var strBarcodeHTML = code128('00" + batch.text + "', 'C'); document.getElementById('barcode').innerHTML = strBarcodeHTML;");
  369. output.writeEndElement();
  370. output.writeStartElement("p");
  371. output.writeAttribute("class", "code-text");
  372. output.writeCharacters("00" + batch.text);
  373. output.writeEndElement();
  374. var cdt = new Date(Date.now());
  375. output.writeTextElement("p", cdt.toLocaleDateString(TTR("reports", "en-US")));
  376. for(var i = 0; table.data(i, 1, 0).value != ""; i++) {
  377. output.writeStartElement("p");
  378. output.writeCharacters(table.data(i, 1, 0) + unitBox.currentText);
  379. output.writeCharacters(" ");
  380. output.writeCharacters(table.data(i, 0, 0));
  381. output.writeEndElement();
  382. }
  383. output.writeTextElement("p", "Total Green Weight: " + green.text + unitBox.currentText);
  384. for(var i = 0; filters.data(i, 0, 0).value != ""; i++) {
  385. output.writeStartElement("p");
  386. output.writeCharacters(filters.data(i, 0, 0) + "=" + filters.data(i, 1, 0));
  387. output.writeEndElement();
  388. }
  389. output.writeEndElement();
  390. output.writeEndElement();
  391. output.writeEndDocument();
  392. batchTag.setContent(buffer);
  393. buffer.close();
  394. };
  395. drawTag();
  396. var printMenu = findChildObject(this, 'print');
  397. printMenu.triggered.connect(function() {
  398. batchTag.print();
  399. });
  400. var printers = findChildObject(this, 'printerlist');
  401. printers.currentIndex = printers.findText(QSettings.value("script/batchtagprinter"));
  402. printers['currentIndexChanged(int)'].connect(function() {
  403. QSettings.setValue("script/batchtagprinter", printers.currentText);
  404. });
  405. var printbutton = findChildObject(this, 'printbutton');
  406. printbutton.clicked.connect(function() {
  407. batchTag.print(printers.currentText);
  408. });
  409. var savebutton = findChildObject(this, 'savebutton');
  410. savebutton.clicked.connect(function() {
  411. var data = new Object;
  412. data.roasted = Number(roasted.currentData());
  413. data.green_weight = Number(green.text);
  414. var greensdata = new Array;
  415. for(var i = 0; table.data(i, 1, 0).value != ""; i++) {
  416. var greendata = new Object;
  417. greendata.id = Number(table.data(i, 0, 32));
  418. switch(unitBox.currentIndex) {
  419. case 0:
  420. greendata.weight = convertToPounds(table.data(i, 1, 0), "g");
  421. break;
  422. case 1:
  423. greendata.weight = convertToPounds(table.data(i, 1, 0), "Kg");
  424. break;
  425. case 2:
  426. greendata.weight = convertToPounds(table.data(i, 1, 0), "oz");
  427. break;
  428. default:
  429. greendata.weight = Number(table.data(i, 1, 0));
  430. break;
  431. }
  432. greensdata.push(greendata);
  433. }
  434. data.greens = greensdata;
  435. var filtersdata = new Array;
  436. for(var i = 0; filters.data(i, 0, 0).value != ""; i++) {
  437. var filterdata = new Object;
  438. filterdata.key = filters.data(i, 0, 0).value;
  439. filterdata.value = filters.data(i, 1, 0).value;
  440. filtersdata.push(filterdata);
  441. }
  442. data.filters = filtersdata;
  443. var query = QSqlQuery();
  444. if(saved) {
  445. query.prepare("UPDATE scheduled_roasts SET data = :data WHERE id = :id");
  446. } else {
  447. query.prepare("INSERT INTO scheduled_roasts(id, data) VALUES (:id, :data)");
  448. }
  449. query.bind(":id", batch.text);
  450. query.bind(":data", JSON.stringify(data));
  451. query.exec();
  452. editingstack.setCurrentIndex(0);
  453. saved = false;
  454. deletebutton.enabled = false;
  455. });
  456. deletebutton.clicked.connect(function() {
  457. if(saved) {
  458. var query = QSqlQuery();
  459. query.prepare("DELETE FROM scheduled_roasts WHERE id = :id");
  460. query.bind(":id", Number(batch.text));
  461. query.exec();
  462. query = query.invalidate();
  463. }
  464. editingstack.setCurrentIndex(0);
  465. saved = false;
  466. deletebutton.enabled = false;
  467. });
  468. var notifier = Application.subscribe("scheduledroastschange");
  469. notifier.notify.connect(function() {
  470. batches.setQuery("SELECT id, (SELECT name FROM items WHERE id = (data#>>'{roasted}')::numeric), (data#>>'{green_weight}')::numeric FROM scheduled_roasts WHERE machine IS NULL");
  471. });
  472. ]]>
  473. </program>
  474. </window>