mainwindow.cpp 30 KB
Newer Older
Hugo Hörnquist's avatar
Hugo Hörnquist committed
1 2 3 4 5
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QtCore>
#include <QSqlQuery>
6
#include <QSqlRecord>
Hugo Hörnquist's avatar
Hugo Hörnquist committed
7 8 9
#include <QSqlTableModel>
#include <QTableView>

Hugo Hörnquist's avatar
work  
Hugo Hörnquist committed
10 11
#include <QModelIndexList>
#include <QSqlError>
12
#include <QSqlIndex>
Hugo Hörnquist's avatar
Hugo Hörnquist committed
13

14
#include <QSqlRelationalDelegate>
Hugo Hörnquist's avatar
Hugo Hörnquist committed
15
#include <QHeaderView>
16

17 18
#include <QMessageBox>

19 20
#include <QFileInfo>

21 22
#include <cstring>

Hugo Hörnquist's avatar
Hugo Hörnquist committed
23
#include "comboboxitemdelegate.h"
24
#include "validatordelegate.h"
Hugo Hörnquist's avatar
Hugo Hörnquist committed
25
#include "moneydelegate.h"
Hugo Hörnquist's avatar
Hugo Hörnquist committed
26
#include "texteditdelegate.h"
27 28 29
#include "maybevalidatordelegate.h"

#include "qintornullvalidator.h"
Hugo Hörnquist's avatar
Hugo Hörnquist committed
30

31 32
#include "serial.h"

Hugo Hörnquist's avatar
Hugo Hörnquist committed
33 34
#define CASH_ACCOUNT "Cash Drawer"
#define VAULT_ACCOUNT "Vault"
Hugo Hörnquist's avatar
Hugo Hörnquist committed
35
#define CASH_NULL NULL
Hugo Hörnquist's avatar
Hugo Hörnquist committed
36

Hugo Hörnquist's avatar
work  
Hugo Hörnquist committed
37
// TODO maybe make it somehow possible to remove product
Hugo Hörnquist's avatar
Hugo Hörnquist committed
38
// or have a button to delete all "dead" products
Hugo Hörnquist's avatar
work  
Hugo Hörnquist committed
39

Hugo Hörnquist's avatar
Hugo Hörnquist committed
40 41 42 43
// TODO magic numbers

// TODO delete buttons in tables should probably also work when only a field is selected

Hugo Hörnquist's avatar
Hugo Hörnquist committed
44 45
// TODO transaction

Hugo Hörnquist's avatar
Hugo Hörnquist committed
46 47 48
// TODO drainage view removal keep selection
//      (the loss is caused by the requery)

Hugo Hörnquist's avatar
Hugo Hörnquist committed
49 50
// acquisitions menu should allow power usage
// - search auto clear?
Hugo Hörnquist's avatar
Hugo Hörnquist committed
51 52 53 54
// - sugested buy from systemet (this is replaced by reading from done acquisitions)

// TODO money diff doesn't update when cash drawer changes elsewhere in program
//      it however still inserts the correct data into the db
55 56
// This binds into most strings not auto updating, and some more needing manual updates

57 58 59 60 61
// TODO stock diff need to exit field before submitting
//      This can lead to a missed field

// TODO stock diff should show null when actual field is null

62 63 64 65
/*
 * All internal prices should be in öre
 * Never trust a front end number unless you KNOW it's good
 */
Hugo Hörnquist's avatar
Hugo Hörnquist committed
66

Hugo Hörnquist's avatar
Hugo Hörnquist committed
67 68 69 70 71 72
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

73
    setbuf(stdout, NULL);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
74

75 76
    QString HOME = QString::fromLocal8Bit(getenv("HOME"));

Hugo Hörnquist's avatar
Hugo Hörnquist committed
77
    QString port = "/dev/ttyACM0";
78 79 80
    QString db_name = HOME + "/.stupan/stupan.db";

    QFile settingsFile(HOME + "/.stupan/config");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
81 82 83 84
    if (settingsFile.open(QFile::ReadOnly | QFile::Text)) {
        QTextStream in(&settingsFile);
        //qDebug() << f.size() << in.readAll();
        foreach (QString str, in.readAll().split('\n', QString::SkipEmptyParts)) {
85 86 87
			if (str.size() == 0 || str.at(0) == '#')
				continue;

Hugo Hörnquist's avatar
Hugo Hörnquist committed
88 89 90 91 92 93 94 95
            QStringList keyvalue = str.split(' ');
            if (keyvalue.at(0) == "port")
                port = keyvalue.at(1);
            else if (keyvalue.at(0) == "database")
                db_name = keyvalue.at(1);
        }
    }

96 97
    port_name = new char[port.length() + 1];
    std::strcpy(port_name, port.toStdString().c_str());
98 99
    port_setup(port_name, 9600);

Hugo Hörnquist's avatar
Hugo Hörnquist committed
100 101
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");

102 103 104 105 106 107 108 109 110 111 112 113
    QFileInfo db_file_info(db_name);
    if (db_file_info.exists() && db_file_info.isFile()) {
        db.setDatabaseName(db_name);
        if(!db.open())
            qDebug() << __LINE__ << "Database connection failed";
    } else {
        QMessageBox::warning(this, "Error",
            "No database file available\n"
            "Please check that the config path is correct,\n"
            "Otherwise recreate the database");
        QApplication::exit(1);
    }
Hugo Hörnquist's avatar
Hugo Hörnquist committed
114 115 116

    QSqlQuery("PRAGMA FOREIGN_KEYS = ON");

Hugo Hörnquist's avatar
Hugo Hörnquist committed
117 118
    MoneyDelegate* monDel = new MoneyDelegate;

Hugo Hörnquist's avatar
Hugo Hörnquist committed
119 120
    // ==================================================

Hugo Hörnquist's avatar
Hugo Hörnquist committed
121 122 123
    model = new QSqlTableModel;
    model->setTable("products");

124
    model->setEditStrategy(QSqlTableModel::OnFieldChange);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
125

Hugo Hörnquist's avatar
work  
Hugo Hörnquist committed
126 127
    // TODO fix column order in database

Hugo Hörnquist's avatar
Hugo Hörnquist committed
128 129
    model->select();
    model->setHeaderData(0, Qt::Horizontal, "id");
130
    model->setHeaderData(1, Qt::Horizontal, "Sorterings-ID");
Hugo Hörnquist's avatar
work  
Hugo Hörnquist committed
131
    model->setHeaderData(2, Qt::Horizontal, "Systembolaget ID");
132
    model->setHeaderData(3, Qt::Horizontal, "Streckkod");
Hugo Hörnquist's avatar
work  
Hugo Hörnquist committed
133 134 135
    model->setHeaderData(4, Qt::Horizontal, "Namn");
    model->setHeaderData(5, Qt::Horizontal, "Pris");
    model->setHeaderData(6, Qt::Horizontal, "Till Salu?");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
136

Hugo Hörnquist's avatar
Hugo Hörnquist committed
137 138 139 140 141
    QObject::connect(
                model, &QSqlTableModel::dataChanged,
                this, &MainWindow::on_productModel_dataChanged
                );

Hugo Hörnquist's avatar
Hugo Hörnquist committed
142 143 144 145 146
    QTableView* view = ui->tableView;
    view->setModel(model);
    view->hideColumn(0); // don't show ID
    view->setEditTriggers(QAbstractItemView::AllEditTriggers);

Hugo Hörnquist's avatar
Hugo Hörnquist committed
147 148
    QIntValidator* anyIntVal = new QIntValidator(INT_MIN, INT_MAX);
    ValidatorDelegate* valDel = new ValidatorDelegate(anyIntVal);
149
    ComboBoxItemDelegate* cbiDel = new ComboBoxItemDelegate(/*view*/);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
150 151 152

    view->setItemDelegateForColumn(1, valDel);
    view->setItemDelegateForColumn(2, valDel);
153 154 155 156
    // TODO this can result in prices with öre, but hidden
    // update sell log to fix in post
    view->setItemDelegateForColumn(5, new MoneyDelegate(0));
    view->setItemDelegateForColumn(6, cbiDel);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
157 158
    view->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
    view->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Stretch);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
159

160 161
    setProductFilter();

Hugo Hörnquist's avatar
Hugo Hörnquist committed
162 163 164 165 166
    // ==================================================

    QSqlRelationalTableModel* stockModel = new QSqlRelationalTableModel();
    stockModel->setTable("stock");
    stockModel->setHeaderData(0, Qt::Horizontal, "Namn"); // foreign
167 168
    stockModel->setHeaderData(1, Qt::Horizontal, "Lager");
    stockModel->setHeaderData(2, Qt::Horizontal, "Annat lager"); // hidden
Hugo Hörnquist's avatar
Hugo Hörnquist committed
169 170 171 172 173 174 175 176 177
    stockModel->setRelation(0, QSqlRelation("products", "id", "name"));
    stockModel->setSort(0, Qt::AscendingOrder);
    stockModel->select();
    ui->stockView->setModel(stockModel);
    ui->stockView->hideColumn(2);
    ui->stockView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
    ui->stockView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
    ui->stockView->setEditTriggers(QAbstractItemView::NoEditTriggers);

178 179
    // ==================================================

Hugo Hörnquist's avatar
Hugo Hörnquist committed
180 181 182 183
    QSqlQuery("UPDATE my_db_settings SET value = 0 WHERE name = 'acquisitions_temp_transfer'");
    QSqlQuery("DELETE FROM acquisitions_temp");
    QSqlQuery("UPDATE my_db_settings SET value = 1 WHERE name = 'acquisitions_temp_transfer'");

Hugo Hörnquist's avatar
Hugo Hörnquist committed
184 185 186 187 188
    QSqlTableModel* listModel = new QSqlTableModel;
    listModel->setTable("products");
    listModel->setFilter("sale_status IN (0, 1)");
    listModel->setSort(5, Qt::AscendingOrder);
    listModel->setHeaderData(4, Qt::Horizontal, "Namn");
189
    listModel->setHeaderData(5, Qt::Horizontal, "Försäljningspris");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
190 191
    listModel->setHeaderData(6, Qt::Horizontal, "Försäljningsstatus");
    listModel->select();
192

Hugo Hörnquist's avatar
Hugo Hörnquist committed
193 194
    ui->productSearch->setModel(listModel);
    ui->productSearch->hideColumn(0);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
195 196 197 198 199 200 201 202
    ui->productSearch->hideColumn(1);
    ui->productSearch->hideColumn(2);
    ui->productSearch->hideColumn(3);
    ui->productSearch->setItemDelegateForColumn(5, monDel);
    ui->productSearch->setItemDelegateForColumn(6, cbiDel);
    ui->productSearch->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
    ui->productSearch->header()->setSectionResizeMode(4, QHeaderView::Stretch);
    ui->productSearch->setEditTriggers(QAbstractItemView::NoEditTriggers);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
203

204 205
    // TODO decide on how to best do input for the price field
    // TODO clear this table on startup, or update the sum below
Hugo Hörnquist's avatar
Hugo Hörnquist committed
206

207 208
    QList<int> disabledColumns;
    disabledColumns << 0 << 1 << 4;
Hugo Hörnquist's avatar
Hugo Hörnquist committed
209
    tempAcquisitionsModel = new DisablingModel(disabledColumns);
210 211
    tempAcquisitionsModel->setTable("acquisitions_temp");
    tempAcquisitionsModel->setHeaderData(0, Qt::Horizontal, "id");
212
    tempAcquisitionsModel->setHeaderData(1, Qt::Horizontal, "Namn"); // foreign
Hugo Hörnquist's avatar
Hugo Hörnquist committed
213
    tempAcquisitionsModel->setHeaderData(2, Qt::Horizontal, "Antal");
214 215
    tempAcquisitionsModel->setHeaderData(3, Qt::Horizontal, "Pris");
    tempAcquisitionsModel->setHeaderData(4, Qt::Horizontal, "Totalt Pris");
216

Hugo Hörnquist's avatar
Hugo Hörnquist committed
217 218
    tempAcquisitionsModel->setEditStrategy(QSqlTableModel::OnFieldChange);

219
    QObject::connect(
Hugo Hörnquist's avatar
Hugo Hörnquist committed
220
                tempAcquisitionsModel, &DisablingModel::dataChanged,
Hugo Hörnquist's avatar
Hugo Hörnquist committed
221
                this, &MainWindow::on_acquisitionModel_dataChanged
222 223 224 225 226 227 228 229 230
                );

    int product_column = tempAcquisitionsModel->fieldIndex("product_id");
    tempAcquisitionsModel->setRelation(product_column, QSqlRelation("products", "id", "name"));

    tempAcquisitionsModel->select();

    QTableView* acView = ui->purchaseView;
    acView->setModel(tempAcquisitionsModel);
231
    acView->hideColumn(0);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
232
    acView->setItemDelegateForColumn(2, valDel);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
233 234
    acView->setItemDelegateForColumn(3, monDel);
    acView->setItemDelegateForColumn(4, monDel);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
235 236
    acView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
    acView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
237

Hugo Hörnquist's avatar
Hugo Hörnquist committed
238
    // ==================================================
239

240
    clear_stockDiffTemp();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
241

Hugo Hörnquist's avatar
Hugo Hörnquist committed
242
    QList<int> inventoryCheckDisabled;
243
    inventoryCheckDisabled << 0 << 1 << 2 << 3 << 4 << 6;
Hugo Hörnquist's avatar
Hugo Hörnquist committed
244
    DisablingModel* inventoryModel = new DisablingModel(inventoryCheckDisabled);
245
    inventoryModel->setTable("diff_view");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
246
    inventoryModel->setHeaderData(0, Qt::Horizontal, "id");
247 248 249 250 251 252 253
    inventoryModel->setHeaderData(1, Qt::Horizontal, "product id");
    inventoryModel->setHeaderData(2, Qt::Horizontal, "sale status");
    inventoryModel->setHeaderData(3, Qt::Horizontal, "Namn");
    inventoryModel->setHeaderData(4, Qt::Horizontal, "Förväntat");
    inventoryModel->setHeaderData(5, Qt::Horizontal, "Faktiskt");
    inventoryModel->setHeaderData(6, Qt::Horizontal, "Diff");

Hugo Hörnquist's avatar
Hugo Hörnquist committed
254 255
    inventoryModel->setEditStrategy(QSqlTableModel::OnFieldChange);

256 257
    QObject::connect(inventoryModel, &QAbstractItemModel::dataChanged,
                     this, &MainWindow::on_inventoryModel_dataChanged);
258

Hugo Hörnquist's avatar
Hugo Hörnquist committed
259 260 261 262
    inventoryModel->select();
    ui->inventoryCheckView->setModel(inventoryModel);
    ui->inventoryCheckView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
    ui->inventoryCheckView->setEditTriggers(QAbstractItemView::AllEditTriggers);
263 264
    QIntOrNullValidator* intOrNullVal = new QIntOrNullValidator(INT_MIN, INT_MAX);
    MaybeValidatorDelegate* maybeValDel = new MaybeValidatorDelegate(intOrNullVal);
265
    ui->inventoryCheckView->setItemDelegateForColumn(5, maybeValDel);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
266
    ui->inventoryCheckView->hideColumn(0);
267 268 269
    ui->inventoryCheckView->hideColumn(1);
    ui->inventoryCheckView->hideColumn(2);

270 271
    ui->quickInventoryCheckBox->setChecked(true);

Hugo Hörnquist's avatar
Hugo Hörnquist committed
272 273 274 275 276
    // --------------------------------------------------

    setMoneyDiffLabels();

    ui->diffDrawer->setValidator(anyIntVal);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
277

Hugo Hörnquist's avatar
Hugo Hörnquist committed
278 279
    // ==================================================

Hugo Hörnquist's avatar
Hugo Hörnquist committed
280 281
    // TODO some of these things possibly should requery under usage
    // Autoupdate, as it's called
282 283


284
    // TODO set column names in frontend instead of in query
285 286 287 288
    QSqlQueryModel* drainageModel = new QSqlQueryModel();
    drainageModel->setQuery("SELECT id, "
                            "    datetime(start_time, 'localtime') AS Starttid, "
                            "    datetime(end_time, 'localtime') AS Sluttid, "
289 290 291
                            "    decrease AS [Kr/Tid],"
                            "    how_often_minutes AS [Hur ofta], "
                            "    min_price AS [Minimipris] "
292 293 294 295
                            "FROM drainage "
                            "WHERE strftime('%s', start_time) > strftime('%s', 'now') "
                            "ORDER BY start_time");
    ui->drainageView->setModel(drainageModel);
296
    ui->drainageView->hideColumn(0);
297 298
    ui->drainageView->setItemDelegateForColumn(3, monDel);
    ui->drainageView->setItemDelegateForColumn(5, monDel);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
299
    ui->drainageView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
300

301 302
    // TODO making the end smaller can make the start smaller

Hugo Hörnquist's avatar
Hugo Hörnquist committed
303 304 305 306 307 308 309 310
    QDateTime now = QDateTime::currentDateTime();
    //ui->startTimeDateTimeEdit->setMinimumDateTime(now);
    ui->endTimeDateTimeEdit->setMinimumDateTime(now);
    ui->startTimeDateTimeEdit->setDateTime(now);
    ui->endTimeDateTimeEdit->setDateTime(now);

    updateCurrentDrainageStatus();

311 312 313 314
    // TODO past drainages

    // ==================================================

Hugo Hörnquist's avatar
Hugo Hörnquist committed
315 316 317
    QIntValidator* pIntVal = new QIntValidator(0, INT_MAX);
    ui->moneyMoveAmount->setValidator(pIntVal);
    ui->addMoneyEdit->setValidator(pIntVal);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
318
    updateMoneyString();
319

320
    setStockValues();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
321 322 323 324 325 326 327
}

MainWindow::~MainWindow()
{
    delete ui;
}

328 329 330 331 332 333 334 335 336
/*
 * This updates the QSqlTableModel to contain the same data as the database.
 * This is needed since it looks at a view, and can therefore not find the
 * primary key. Requiring all the fields in the front-end and back-end database
 * to have the exact same fields.
 *
 * So, the calculations here are also done in the database, and those are what
 * should be used when doing further stuff with the data.
 */
337
void MainWindow::on_inventoryModel_dataChanged(const QModelIndex &topLeft, const QModelIndex& /*bottomRight*/, const QVector<int>& /*roles*/)
Hugo Hörnquist's avatar
Hugo Hörnquist committed
338
{
339 340 341 342 343 344 345 346 347 348
    QSqlTableModel* model = (QSqlTableModel*) ui->inventoryCheckView->model();

    if (model->isDirty()) {
        int row = topLeft.row();

        bool number;
        int expected = model->data(model->index(row, 4)).toInt();
        int actual = model->data(model->index(row, 5)).toInt(&number);

        model->setData(model->index(row, 6),
349
                       number ? actual - expected
350 351
                              : QVariant());
    }
Hugo Hörnquist's avatar
Hugo Hörnquist committed
352 353
}

Hugo Hörnquist's avatar
Hugo Hörnquist committed
354
/*
355 356 357
 * This updates acquisitions with the new names when products are edited.
 * TODO inventeringssystemet uppdateras inte här, bör det göra det?
 * TODO inte heller lager-status, dock har den en refresh knapp.
Hugo Hörnquist's avatar
Hugo Hörnquist committed
358
 */
359
void MainWindow::on_productModel_dataChanged(const QModelIndex& /*topLeft*/, const QModelIndex& /*bottmRight*/, const QVector<int>& /*roles*/)
Hugo Hörnquist's avatar
Hugo Hörnquist committed
360
{
361 362 363
    // TODO this is possibly quite slow, test on slow system.
    ((QSqlTableModel*) ui->productSearch->model())->select();
    ((QSqlTableModel*) ui->purchaseView->model())->select();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
364 365 366 367 368 369
}

/*
 * Calculates the total price in the acquisitions menu
 */
void MainWindow::on_acquisitionModel_dataChanged(QModelIndex topLeft, QModelIndex /* bottomRight */) {
370 371 372
    int row = topLeft.row();
    int col = topLeft.column();

373
    // only the amount and price should be editable anyways
374
    if (col == 2 || col == 3) {
375 376
        int amount = tempAcquisitionsModel->data(tempAcquisitionsModel->index(row, 2)).toInt();
        int price = tempAcquisitionsModel->data(tempAcquisitionsModel->index(row, 3)).toInt();
377
        tempAcquisitionsModel->setData(tempAcquisitionsModel->index(row, 4), amount * price);
378
    }
379 380 381 382 383

    QSqlQuery query("SELECT sum(total_price) FROM acquisitions_temp");
    query.next();
    double total = query.value(0).toDouble() / 100.0;
    ui->totalPriceLabel->setText(QString("%L1").arg(total));
384 385
}

386
void MainWindow::on_checkBox_toggled(bool /*checked*/)
Hugo Hörnquist's avatar
Hugo Hörnquist committed
387
{
388
    setProductFilter();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
389 390
}

Hugo Hörnquist's avatar
work  
Hugo Hörnquist committed
391 392 393
// Add new row for new product
void MainWindow::on_newItemButton_clicked()
{
Hugo Hörnquist's avatar
Hugo Hörnquist committed
394
    // trigger creates stock entry
Hugo Hörnquist's avatar
Hugo Hörnquist committed
395
    QSqlQuery("INSERT INTO products DEFAULT VALUES");
Hugo Hörnquist's avatar
work  
Hugo Hörnquist committed
396
    model->select();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
397 398

    ui->buySearch->setText("");
Hugo Hörnquist's avatar
work  
Hugo Hörnquist committed
399 400
}

Hugo Hörnquist's avatar
Hugo Hörnquist committed
401 402
// TODO can't remove item which is used elsewhere,
//      currently fails silently.
Hugo Hörnquist's avatar
work  
Hugo Hörnquist committed
403 404 405 406 407 408
void MainWindow::on_deleteButton_clicked()
{
    QItemSelectionModel* selection = ui->tableView->selectionModel();
    if (!selection->hasSelection()) return;
    QModelIndexList lst = selection->selectedRows();
    foreach ( QModelIndex index, lst ) {
409
        model->removeRow(index.row());
Hugo Hörnquist's avatar
work  
Hugo Hörnquist committed
410 411
    }
}
Hugo Hörnquist's avatar
Hugo Hörnquist committed
412 413 414 415 416 417

void MainWindow::on_buyAddBtn_clicked()
{
    // Only a single line should really be able to be selected at a time
    QItemSelectionModel* model = ui->productSearch->selectionModel();
    foreach (QModelIndex index, model->selectedRows(0)) {
Hugo Hörnquist's avatar
Hugo Hörnquist committed
418
        QVariant id = index.data();
419 420 421 422
        QSqlQuery query;
        query.prepare("INSERT INTO acquisitions_temp (product_id) VALUES (:id)");
        query.bindValue(":id", id);
        query.exec();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
423 424 425 426 427

        tempAcquisitionsModel->select();

        QSqlQuery price_query;
        price_query.prepare("SELECT item_price FROM acquisitions "
428 429 430
                            "WHERE product_id = :id "
                            "ORDER BY time DESC "
                            "LIMIT 1");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
431 432 433 434 435 436 437
        price_query.bindValue(":id", id);
        price_query.exec();
        if (price_query.next())
            tempAcquisitionsModel->setData(
                tempAcquisitionsModel->index(
                    tempAcquisitionsModel->rowCount() - 1, 3),
                price_query.value(0));
Hugo Hörnquist's avatar
Hugo Hörnquist committed
438 439
    }

Hugo Hörnquist's avatar
Hugo Hörnquist committed
440

441 442
}

Hugo Hörnquist's avatar
Hugo Hörnquist committed
443 444
// TODO possibly updated sale status from hidden to not_for_sale
// when search is numeric (barcode)
445
void MainWindow::on_buySearch_textChanged(const QString &arg1)
446
{
Hugo Hörnquist's avatar
Hugo Hörnquist committed
447 448 449 450 451 452 453 454 455 456 457
    bool isNumber;
    arg1.toInt(&isNumber, 10);
    QString filter = QString(
                isNumber
                ? "bar_code = '%1'"
                : "name LIKE '\%%1\%' AND sale_status IN (0, 1)"
                ).arg(arg1);

    QSqlTableModel* model = ((QSqlTableModel*) ui->productSearch->model());
    model->setFilter(filter);
    model->select();
458 459
}

Hugo Hörnquist's avatar
Hugo Hörnquist committed
460 461
/*
 * data is transfered from acquisitions_temp to acquisitions on delete via trigger
462
 *
Hugo Hörnquist's avatar
Hugo Hörnquist committed
463
 */
464 465
void MainWindow::on_submitBuyButton_clicked()
{
466
    QSqlQuery("DELETE FROM acquisitions_temp");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
467

468
    tempAcquisitionsModel->select();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
469
    updateMoneyString();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
470
    ((QSqlRelationalTableModel*) ui->stockView->model())->select();
471 472

    open_till();
473 474 475 476 477 478 479 480
}

void MainWindow::on_buyRemoveButton_clicked()
{
    foreach (QModelIndex row, ui->purchaseView->selectionModel()->selectedRows()) {
        tempAcquisitionsModel->removeRow(row.row());
    }
    tempAcquisitionsModel->select();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
481
}
Hugo Hörnquist's avatar
Hugo Hörnquist committed
482 483 484

void MainWindow::updateMoneyString()
{
Hugo Hörnquist's avatar
Hugo Hörnquist committed
485 486 487 488
    QSqlQuery query("SELECT name, amount / 100.0 "
                    "FROM money "
                    "WHERE name in ('Cash Drawer', 'Vault') "
                    "ORDER BY name ASC");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
489

Hugo Hörnquist's avatar
Hugo Hörnquist committed
490 491 492
    // TODO also push the names from db to gui
    query.next();
    ui->drawerLabel->setText(QString("%1").arg(query.value(1).toDouble(), 0, 'f', 2));
Hugo Hörnquist's avatar
Hugo Hörnquist committed
493
    query.next();
494
    ui->vaultLabel->setText (QString("%1").arg(query.value(1).toDouble(), 0, 'f', 2));
Hugo Hörnquist's avatar
Hugo Hörnquist committed
495 496 497 498
}

void MainWindow::on_toVaultButton_clicked()
{
499
    toVaultDrawer_shared(CASH_ACCOUNT, VAULT_ACCOUNT);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
500 501 502
}

void MainWindow::on_toDrawerButton_clicked()
503 504 505 506 507
{
    toVaultDrawer_shared(VAULT_ACCOUNT, CASH_ACCOUNT);
}

void MainWindow::toVaultDrawer_shared(QString from, QString to)
Hugo Hörnquist's avatar
Hugo Hörnquist committed
508 509 510
{
    int amount = ui->moneyMoveAmount->text().toInt();

511
    transferMoney(from, to, amount);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
512 513 514
    ui->moneyMoveAmount->setText("");
    updateMoneyString();
}
Hugo Hörnquist's avatar
Hugo Hörnquist committed
515

Hugo Hörnquist's avatar
Hugo Hörnquist committed
516
void MainWindow::transferMoney(QString from, QString to, int amount, QString message)
Hugo Hörnquist's avatar
Hugo Hörnquist committed
517
{
Hugo Hörnquist's avatar
Hugo Hörnquist committed
518
    if ((from == "" && to == "") || amount == 0)
Hugo Hörnquist's avatar
Hugo Hörnquist committed
519 520
        return;

Hugo Hörnquist's avatar
Hugo Hörnquist committed
521
    QSqlQuery query;
Hugo Hörnquist's avatar
Hugo Hörnquist committed
522 523
    query.prepare("INSERT INTO money_transfers (change, note, from_acc, to_acc) "
                  "VALUES (:amount, :message, "
Hugo Hörnquist's avatar
Hugo Hörnquist committed
524 525 526 527 528 529 530 531
                  "(SELECT CASE "
                  "  WHEN count(1) != 0 THEN id "
                  "  ELSE 0 END "
                  "  FROM money WHERE name = :from), "
                  "(SELECT CASE "
                  "  WHEN count(1) != 0 THEN id "
                  "  ELSE 0 END "
                  "  FROM money WHERE name = :to))");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
532
    query.bindValue(":amount", amount);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
533
    query.bindValue(":message", message);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
534 535
    query.bindValue(":from", from);
    query.bindValue(":to", to);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
536 537
    if (!query.exec())
        qDebug() << query.lastError();
538 539

    open_till();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
540
}
Hugo Hörnquist's avatar
Hugo Hörnquist committed
541

Hugo Hörnquist's avatar
Hugo Hörnquist committed
542 543 544 545 546
// invalid account names results in the outside world
void MainWindow::transferMoney(QString from, QString to, int amount)
{
    transferMoney(from, to, amount, NULL);
}
Hugo Hörnquist's avatar
Hugo Hörnquist committed
547 548 549

void MainWindow::on_addMoneySubmitButton_clicked()
{
Hugo Hörnquist's avatar
Hugo Hörnquist committed
550
    addRemoveCommon(CASH_NULL, CASH_ACCOUNT);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
551 552 553 554
}

void MainWindow::on_removeMoneySubmitButton_clicked()
{
Hugo Hörnquist's avatar
Hugo Hörnquist committed
555 556 557 558 559 560 561 562 563
    addRemoveCommon(CASH_ACCOUNT, CASH_NULL);
}

void MainWindow::addRemoveCommon(QString from, QString to)
{
    QString msg = ui->addMoneyReason->toPlainText();
    QString message = msg == "" ? NULL : msg;
    int amount = ui->addMoneyEdit->text().toInt();
    transferMoney(from, to, amount, message);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
564
    ui->addMoneyEdit->setText("");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
565
    ui->addMoneyReason->setPlainText("");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
566
    updateMoneyString();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
567

Hugo Hörnquist's avatar
Hugo Hörnquist committed
568 569 570 571
}

void MainWindow::on_addDrainageButton_clicked()
{
572 573 574
    // Products to go from crown to öre
    QVariant per_hour = ui->amountPerHourSpinBox->value() * 100;
    QVariant min_price = ui->minPriceSpinBox->value() * 100;
Hugo Hörnquist's avatar
Hugo Hörnquist committed
575
    QVariant how_often = ui->howOftenSpinBox->value();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
576

577 578 579
    QDateTime start_time = ui->startTimeDateTimeEdit->dateTime().toUTC();
    QDateTime end_time = ui->endTimeDateTimeEdit->dateTime().toUTC();

580
	/*
581
    if (end_time < QDateTime::currentDateTime().toUTC()) {
Hugo Hörnquist's avatar
Hugo Hörnquist committed
582
        QMessageBox::warning(this, "Error", "Kan inte skapa tömmning helt i det förflutna");
583 584 585 586 587
        return;
    }

    // This shouldn't be able to happen due to the end_time min being set to start_time
    if (start_time > end_time) {
Hugo Hörnquist's avatar
Hugo Hörnquist committed
588
        QMessageBox::warning(this, "Error", "Jag går inte med på krökningar i rumtiden!");
589 590
        return;
    }
591
	*/
592

593 594
    // drainages always start and end on a whole minute
    QString timeStr = "yyyy-MM-dd hh:mm:00";
595 596
    QVariant start_str = start_time.toString(timeStr);
    QVariant end_str = end_time.toString(timeStr);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
597 598 599 600

    QSqlQuery query;
    query.prepare ("INSERT INTO drainage (start_time, end_time, decrease, how_often_minutes, min_price) "
                   "VALUES (:start, :end, :dec, :often, :min)");
601 602
    query.bindValue(":start", start_str);
    query.bindValue(":end", end_str);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
603 604 605 606
    query.bindValue(":min", min_price);
    query.bindValue(":dec", per_hour);
    query.bindValue(":often", how_often);

Hugo Hörnquist's avatar
Hugo Hörnquist committed
607
    if (!query.exec()) {
Hugo Hörnquist's avatar
Hugo Hörnquist committed
608
        qDebug() << query.lastError();
609
		// Drainages in the past, overlapping, or with negative time spans
Hugo Hörnquist's avatar
Hugo Hörnquist committed
610 611
        QMessageBox::warning(this, "Error", query.lastError().databaseText());
    }
Hugo Hörnquist's avatar
Hugo Hörnquist committed
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660

    updateDrainageView();
    updateCurrentDrainageStatus();

}

void MainWindow::updateDrainageView()
{
    QSqlQueryModel* model = (QSqlQueryModel*) ui->drainageView->model();
    QString queryStr = model->query().executedQuery();
    model->clear();
    model->setQuery(queryStr);
}

void MainWindow::on_timeZoneComboBox_currentIndexChanged(int index)
{
    // TODO magic number
    Qt::TimeSpec spec = index == 0 ? Qt::LocalTime : Qt::UTC;
    QDateTimeEdit* startEdit = ui->startTimeDateTimeEdit;
    QDateTimeEdit* endEdit   = ui->endTimeDateTimeEdit;

    startEdit->setTimeSpec(spec);
    endEdit->setTimeSpec(spec);

    // This is to update the ui
    startEdit->setDateTime(startEdit->dateTime());
    endEdit->setDateTime(endEdit->dateTime());
}

void MainWindow::on_startTimeDateTimeEdit_dateTimeChanged(const QDateTime &dateTime)
{
    ui->endTimeDateTimeEdit->setMinimumDateTime(dateTime);
}

void MainWindow::on_removeDrainageButton_clicked()
{
    // TODO batch, even though only a single value can be selected at a time currently
    foreach ( QModelIndex i, ui->drainageView->selectionModel()->selectedRows(0) ) {
        QSqlQuery query;
        query.prepare("DELETE FROM drainage WHERE id = :id");
        query.bindValue(":id", i.data());
        if (!query.exec())
            qDebug() << query.lastError();
    }
    updateDrainageView();
}

void MainWindow::on_cancelDrainageButton_clicked()
{
Hugo Hörnquist's avatar
Hugo Hörnquist committed
661
    QSqlQuery("UPDATE drainage SET end_time = CURRENT_TIMESTAMP "
Hugo Hörnquist's avatar
Hugo Hörnquist committed
662
              "WHERE CURRENT_TIMESTAMP BETWEEN start_time AND end_time");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
663 664

    // TODO this should be done 1 secound later
Hugo Hörnquist's avatar
Hugo Hörnquist committed
665 666 667 668 669 670 671 672 673 674 675 676 677
    updateCurrentDrainageStatus();
}

void MainWindow::on_updateCurrentDrainageButton_clicked()
{
    updateCurrentDrainageStatus();
}

// TODO this should preferably also be called when a drainage starts
// due to the passage of time

void MainWindow::updateCurrentDrainageStatus()
{
678
    // division to get crowns
Hugo Hörnquist's avatar
Hugo Hörnquist committed
679 680
    QSqlQuery currentDrainageQuery("SELECT datetime(start_time, 'localtime'), "
                                   "       datetime(end_time, 'localtime'), "
681
                                   "       decrease / 100.0, "
Hugo Hörnquist's avatar
Hugo Hörnquist committed
682
                                   "       how_often_minutes, "
683
                                   "       min_price / 100.0 "
Hugo Hörnquist's avatar
Hugo Hörnquist committed
684 685 686 687 688 689 690
                                   "FROM drainage "
                                   "WHERE CURRENT_TIMESTAMP BETWEEN start_time AND end_time "
                                   "LIMIT 1");

    if (currentDrainageQuery.next()) {
        QString startStr    = currentDrainageQuery.value(0).toString();
        QString endStr      = currentDrainageQuery.value(1).toString();
691
        double decreaseStr = currentDrainageQuery.value(2).toDouble();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
692
        QString minutesStr  = currentDrainageQuery.value(3).toString();
693
        double minStr      = currentDrainageQuery.value(4).toDouble();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
694
        ui->drainageLabel->setText(QString("%1 kr/%2 min, ner till %3 kr\nMellan %4 och %5 lokal tid")
695
                                   .arg(decreaseStr, 0, 'f', 2)
Hugo Hörnquist's avatar
Hugo Hörnquist committed
696
                                   .arg(minutesStr)
697
                                   .arg(minStr, 0, 'f', 2)
Hugo Hörnquist's avatar
Hugo Hörnquist committed
698 699 700 701
                                   .arg(startStr)
                                   .arg(endStr));
        ui->cancelDrainageButton->setVisible(true);
    } else {
702
        ui->drainageLabel->setText("Ingen pågående tömning");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
703 704 705
        ui->cancelDrainageButton->setVisible(false);
    }
}
Hugo Hörnquist's avatar
Hugo Hörnquist committed
706

Hugo Hörnquist's avatar
Hugo Hörnquist committed
707 708 709 710
void MainWindow::on_stockRefreshButton_clicked()
{
    ((QSqlRelationalTableModel*) ui->stockView->model())->select();
}
711

Hugo Hörnquist's avatar
Hugo Hörnquist committed
712 713
void MainWindow::on_diffSubmit_clicked()
{
Hugo Hörnquist's avatar
Hugo Hörnquist committed
714 715 716
    QString str = ui->diffDrawer->text();
    if (str == "") return;
    int total = str.toInt() * 100;
Hugo Hörnquist's avatar
Hugo Hörnquist committed
717
    QSqlQuery query;
718 719
    query.prepare("INSERT INTO money_diffs (actual, expected, account) "
                  "SELECT :total, amount, id "
Hugo Hörnquist's avatar
Hugo Hörnquist committed
720 721 722 723 724
                  " FROM money WHERE name = 'Cash Drawer'");
    query.bindValue(":total", total);
    if (!query.exec())
        qDebug() << query.lastError();

725

Hugo Hörnquist's avatar
Hugo Hörnquist committed
726 727
    ui->diffDrawer->setText("");
    setMoneyDiffLabels();
Hugo Hörnquist's avatar
Hugo Hörnquist committed
728 729 730 731
}

void MainWindow::on_diffDrawer_textChanged(const QString &arg1)
{
Hugo Hörnquist's avatar
Hugo Hörnquist committed
732 733
    int new_total = arg1.toInt() * 100;
    setMoneyDiffLabels(new_total);
Hugo Hörnquist's avatar
Hugo Hörnquist committed
734
}
Hugo Hörnquist's avatar
Hugo Hörnquist committed
735 736 737

void MainWindow::on_submitStockDiff_clicked()
{
Hugo Hörnquist's avatar
Hugo Hörnquist committed
738
    QSqlQuery("DELETE FROM diff_view");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
739 740
    ((QSqlRelationalTableModel*) ui->inventoryCheckView->model())->select();
}
Hugo Hörnquist's avatar
Hugo Hörnquist committed
741 742 743 744 745 746 747

int MainWindow::setMoneyDiffLabels()
{
    QSqlQuery query("SELECT amount FROM money WHERE name = 'Cash Drawer'");
    query.next();
    int expected = query.value(0).toInt();
    QString diff_text = QString("%1 kr").arg(expected / 100.0, 0, 'f', 2);
748 749 750 751 752

    // This might seem like a mistake at first sight, though it isn't.
    // This just sets a default value for diffTotal. The new value is
    // calculated and set in setMoneyDiffLabels(int new_total)

Hugo Hörnquist's avatar
Hugo Hörnquist committed
753 754 755 756 757 758 759 760 761 762 763
    ui->diffExpected->setText(diff_text);
    ui->diffTotal->setText(diff_text);

    return expected;
}

void MainWindow::setMoneyDiffLabels(int new_total)
{
    double diff = new_total - setMoneyDiffLabels();
    ui->diffTotal->setText(QString("%1 kr").arg(diff / 100.0, 0, 'f', 2));
}
764 765 766 767 768 769 770 771

void MainWindow::on_openTillButton_clicked()
{
    open_till();
}

void MainWindow::open_till()
{
772
    port_write(port_name, '1');
773
}
774 775 776 777 778

void MainWindow::on_stockDiffReset_clicked()
{
    clear_stockDiffTemp();

Hugo Hörnquist's avatar
Hugo Hörnquist committed
779
    ((QSqlTableModel*) ui->inventoryCheckView->model())->select();
780 781
}

Hugo Hörnquist's avatar
Hugo Hörnquist committed
782
// this also inserts the new needed data into diff_help
783 784 785 786
void MainWindow::clear_stockDiffTemp()
{
    // TODO transaction
    QSqlQuery("UPDATE my_db_settings SET value = 0 WHERE name = 'stock_diff_temp_transfer'");
Hugo Hörnquist's avatar
Hugo Hörnquist committed
787
    QSqlQuery("DELETE FROM diff_help");
788 789
    QSqlQuery("UPDATE my_db_settings SET value = 1 WHERE name = 'stock_diff_temp_transfer'");
}
790 791 792 793 794 795

/*
 * This is incredibly broken at the moment.
 */
QVariant MainWindow::get_acquistion_stock_value()
{
796
    qlonglong total_price = 0;
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853
    QSqlQuery tQuery("SELECT s.product_id as id, s.active as active, p.name "
                     "FROM stock s INNER JOIN products p "
                     "ON s.product_id = p.id");
    while (tQuery.next()) {
        qDebug() << __LINE__;
        long target = tQuery.value("active").toLongLong();

        if (target < 0) continue;

        QSqlQuery query;
        query.prepare("SELECT id, time, product_id, item_price, amount "
                      "FROM acquisitions WHERE product_id = :id "
                      "ORDER BY time DESC");
        query.bindValue(":id", tQuery.value("id"));
        query.exec();

        long amount = 0;
        long total_product_price = 0;
        while (query.next()) {
            long sub_amount = query.value("amount").toLongLong();
            long item_price = query.value("item_price").toLongLong();
            qDebug() << __LINE__ << amount << sub_amount;
            if (amount + sub_amount > target) {
                total_product_price += (target - amount) * item_price;
                amount = target;
                break;
            } else {
                amount += sub_amount;
                total_product_price += sub_amount * item_price;
            }

        }
        total_price += total_product_price;
    }

    return QVariant(total_price);
}

QVariant MainWindow::get_projected_stock_value() {
    QSqlQuery query("SELECT SUM(p.price * s.active) "
                    "FROM products p "
                    "INNER JOIN stock s ON "
                    "p.id = s.product_id");
    if (query.next())
        return query.value(0);
    return QVariant::Int;

}

void MainWindow::setStockValues()
{
    double accVal = get_acquistion_stock_value().toDouble() / 100.0;
    ui->valueAccquisitionLabel->setText(QString("%1").arg(accVal, 0, 'f', 2));

    double projVal = get_projected_stock_value().toDouble() / 100.0;
    ui->valueProjectedLabel->setText(QString("%1").arg(projVal, 0, 'f', 2));
}
854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882

void MainWindow::setProductFilter()
{
    QString searchStr = ui->productListSearch->text();

    bool isNumber;
    searchStr.toInt(&isNumber, 10);

    QString filter = QString(
                isNumber
                ? "bar_code = '%1'"
                : "name LIKE '\%%1\%' "
                ).arg(searchStr);

    if (ui->checkBox->isChecked()) {
        filter.append ("AND sale_status >= 0");
    } else {
        filter.append ("AND sale_status IN (0, 1)");
    }

    QSqlTableModel* model = ((QSqlTableModel*) ui->tableView->model());
    model->setFilter(filter);
    model->select();
}

void MainWindow::on_productListSearch_textChanged(const QString &arg1)
{
    setProductFilter();
}
883

884 885 886 887 888
/*
 * Note that the hidden rows can still contain data that WILL be
 * submitted.
 * TODO Possibly have some form of warning when it comes to that.
 */
889 890
void MainWindow::on_quickInventoryCheckBox_toggled(bool checked)
{
891 892 893 894
    ((QSqlRelationalTableModel*) ui->inventoryCheckView->model())
            ->setFilter( checked
                         ? "sale_status = 0 or expected != 0"
                         : "sale_status in (0, 1)");
895
}
Hugo Hörnquist's avatar
Hugo Hörnquist committed
896 897 898 899 900 901

void MainWindow::on_moneyDiffReload_clicked()
{
    ui->diffDrawer->setText("");
    setMoneyDiffLabels();
}