Browse Source

Interface for scheduling roasts

Neal Wilson 6 years ago
parent
commit
eb2baace43
4 changed files with 510 additions and 2 deletions
  1. 3
    0
      config/Scripts/batchtag.css
  2. 9
    0
      config/Windows/navigation.xml
  3. 485
    0
      config/Windows/schedule.xml
  4. 13
    2
      src/typica.w

+ 3
- 0
config/Scripts/batchtag.css View File

@@ -36,3 +36,6 @@ span {
36 36
 	margin-right: auto;
37 37
 }
38 38
 
39
+.code-text {
40
+    text-align: center;
41
+}

+ 9
- 0
config/Windows/navigation.xml View File

@@ -5,6 +5,11 @@
5 5
                 <button name="Configure Roasters" id="configure" type="push" />
6 6
             </column>
7 7
         </row>
8
+        <row>
9
+            <column>
10
+                <button name="Roasting Schedule" id="schedule" type="push" />
11
+            </column>
12
+        </row>
8 13
         <row>
9 14
             <column>
10 15
                 <sqldrop id="machineselector" />
@@ -95,6 +100,10 @@
95 100
             QSettings.setValue("database/dbname", "");
96 101
             QSettings.setValue("database/user", "");
97 102
             QSettings.setValue("database/password", "");
103
+        });
104
+        var schedule = findChildObject(this, 'schedule');
105
+        schedule.clicked.connect(function() {
106
+            createWindow("schedule");
98 107
         });
99 108
 		var manual = findChildObject(this, 'manual');
100 109
 		manual.clicked.connect(function() {

+ 485
- 0
config/Windows/schedule.xml View File

@@ -0,0 +1,485 @@
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
+        
348
+        styleFile = new QFile(QSettings.value("config") + "/Scripts/barcode.css");
349
+        styleFile.open(1);
350
+        output.writeTextElement("style", styleFile.readToString());
351
+        styleFile.close();
352
+        
353
+        output.writeStartElement("script");
354
+        scriptFile = new QFile(QSettings.value("config") + "/Scripts/barcode.js");
355
+        scriptFile.open(1);
356
+        output.writeCDATA(scriptFile.readToString());
357
+        scriptFile.close();
358
+        output.writeEndElement();
359
+        
360
+        output.writeEndElement();
361
+        output.writeStartElement("body");
362
+        
363
+        output.writeStartElement("h1");
364
+        output.writeCharacters(roasted.currentText);
365
+        output.writeEndElement();
366
+        
367
+        output.writeStartElement("div");
368
+        output.writeAttribute("id", "barcode");
369
+        output.writeAttribute("class", "barcode128h");
370
+        output.writeAttribute("align", "center");
371
+        output.writeEndElement();
372
+        
373
+        output.writeStartElement("script");
374
+        output.writeCharacters("var strBarcodeHTML = code128('00" + batch.text + "', 'C'); document.getElementById('barcode').innerHTML = strBarcodeHTML;");
375
+        output.writeEndElement();
376
+        
377
+        output.writeStartElement("p");
378
+        output.writeAttribute("class", "code-text");
379
+        output.writeCharacters("00" + batch.text);
380
+        output.writeEndElement();
381
+        
382
+        var cdt = new Date(Date.now());
383
+        output.writeTextElement("p", cdt.toLocaleDateString(TTR("reports", "en-US")));
384
+        
385
+        for(var i = 0; table.data(i, 1, 0).value != ""; i++) {
386
+            output.writeStartElement("p");
387
+            output.writeCharacters(table.data(i, 1, 0) + unitBox.currentText);
388
+            output.writeCharacters(" ");
389
+            output.writeCharacters(table.data(i, 0, 0));
390
+            output.writeEndElement();
391
+        }
392
+        output.writeTextElement("p", "Total Green Weight: " + green.text + unitBox.currentText);
393
+        
394
+        for(var i = 0; filters.data(i, 0, 0).value != ""; i++) {
395
+            output.writeStartElement("p");
396
+            output.writeCharacters(filters.data(i, 0, 0) + "=" + filters.data(i, 1, 0));
397
+            output.writeEndElement();
398
+        }
399
+        
400
+        output.writeEndElement();
401
+        output.writeEndElement();
402
+        output.writeEndDocument();
403
+        batchTag.setContent(buffer);
404
+        buffer.close();
405
+    };
406
+    drawTag();
407
+    var printMenu = findChildObject(this, 'print');
408
+    printMenu.triggered.connect(function() {
409
+        batchTag.print();
410
+    });
411
+    var printers = findChildObject(this, 'printerlist');
412
+    printers.currentIndex = printers.findText(QSettings.value("script/batchtagprinter"));
413
+    printers['currentIndexChanged(int)'].connect(function() {
414
+        QSettings.setValue("script/batchtagprinter", printers.currentText);
415
+    });
416
+    var printbutton = findChildObject(this, 'printbutton');
417
+    printbutton.clicked.connect(function() {
418
+        batchTag.print(printers.currentText);
419
+    });
420
+    var savebutton = findChildObject(this, 'savebutton');
421
+    savebutton.clicked.connect(function() {
422
+        var data = new Object;
423
+        data.roasted = Number(roasted.currentData());
424
+        data.green_weight = Number(green.text);
425
+        var greensdata = new Array;
426
+        for(var i = 0; table.data(i, 1, 0).value != ""; i++) {
427
+            var greendata = new Object;
428
+            greendata.id = Number(table.data(i, 0, 32));
429
+            switch(unitBox.currentIndex) {
430
+                case 0:
431
+                    greendata.weight = convertToPounds(table.data(i, 1, 0), "g");
432
+                    break;
433
+                case 1:
434
+                    greendata.weight = convertToPounds(table.data(i, 1, 0), "Kg");
435
+                    break;
436
+                case 2:
437
+                    greendata.weight = convertToPounds(table.data(i, 1, 0), "oz");
438
+                    break;
439
+                default:
440
+                    greendata.weight = Number(table.data(i, 1, 0));
441
+                    break;
442
+            }
443
+            greensdata.push(greendata);
444
+        }
445
+        data.greens = greensdata;
446
+        var filtersdata = new Array;
447
+        for(var i = 0; filters.data(i, 0, 0).value != ""; i++) {
448
+            var filterdata = new Object;
449
+            filterdata.key = filters.data(i, 0, 0).value;
450
+            filterdata.value = filters.data(i, 1, 0).value;
451
+            filtersdata.push(filterdata);
452
+        }
453
+        data.filters = filtersdata;
454
+        var query = QSqlQuery();
455
+        if(saved) {
456
+            query.prepare("UPDATE scheduled_roasts SET data = :data WHERE id = :id");
457
+        } else {
458
+            query.prepare("INSERT INTO scheduled_roasts(id, data) VALUES (:id, :data)");
459
+        }
460
+        query.bind(":id", batch.text);
461
+        query.bind(":data", JSON.stringify(data));
462
+        query.exec();
463
+        editingstack.setCurrentIndex(0);
464
+        saved = false;
465
+        deletebutton.enabled = false;
466
+    });
467
+    deletebutton.clicked.connect(function() {
468
+        if(saved) {
469
+            var query = QSqlQuery();
470
+            query.prepare("DELETE FROM scheduled_roasts WHERE id = :id");
471
+            query.bind(":id", Number(batch.text));
472
+            query.exec();
473
+            query = query.invalidate();
474
+        }
475
+        editingstack.setCurrentIndex(0);
476
+        saved = false;
477
+        deletebutton.enabled = false;
478
+    });
479
+    var notifier = Application.subscribe("scheduledroastschange");
480
+    notifier.notify.connect(function() {
481
+        batches.setQuery("SELECT id, (SELECT name FROM items WHERE id = (data#>>'{roasted}')::numeric), (data#>>'{green_weight}')::numeric FROM scheduled_roasts WHERE machine IS NULL");
482
+    });
483
+]]>
484
+    </program>
485
+</window>

+ 13
- 2
src/typica.w View File

@@ -14061,12 +14061,15 @@ class SqlQueryView : public QTableView@/
14061 14061
                                   int role = Qt::DisplayRole);@t\2\2@>@/
14062 14062
     signals:@/
14063 14063
         void openEntry(QString key);
14064
-        void openEntryRow(int row);@/
14064
+        void openEntryRow(int row);
14065
+        void selectEntry(QString key);
14066
+        void selectEntryRow(int row);@/
14065 14067
     protected:@/
14066 14068
         virtual void showEvent(QShowEvent *event);@/
14067 14069
     @[private slots@]:@/
14068 14070
         void openRow(const QModelIndex &index);
14069
-        void persistColumnResize(int column, int oldsize, int newsize);@/
14071
+        void persistColumnResize(int column, int oldsize, int newsize);
14072
+        void selectRow(const QModelIndex &index);@/
14070 14073
 };
14071 14074
 
14072 14075
 @ The constructor sets up the communication between the model and the view and
@@ -14081,6 +14084,8 @@ SqlQueryView::SqlQueryView(QWidget *parent) : QTableView(parent)
14081 14084
             this, SLOT(openRow(QModelIndex)));
14082 14085
     connect(horizontalHeader(), SIGNAL(sectionResized(int, int, int)),
14083 14086
             this, SLOT(persistColumnResize(int, int, int)));
14087
+    connect(this, SIGNAL(activated(QModelIndex)),
14088
+            this, SLOT(selectRow(QModelIndex)));
14084 14089
 }
14085 14090
 
14086 14091
 @ Column width persistance requires two methods. First we have a slot
@@ -14154,6 +14159,12 @@ void SqlQueryView::openRow(const QModelIndex &index)
14154 14159
     emit openEntryRow(index.row());
14155 14160
 }
14156 14161
 
14162
+void SqlQueryView::selectRow(const QModelIndex &index)
14163
+{
14164
+    emit selectEntry(((QSqlQueryModel *)model())->record(index.row()).value(0).toString());
14165
+    emit selectEntryRow(index.row());
14166
+}
14167
+
14157 14168
 @ The other functions are wrappers around model methods.
14158 14169
 
14159 14170
 @<SqlQueryView implementation@>=

Loading…
Cancel
Save