/* Copyright 2012-2013, Sebastian Reichel * Copyright 2017-2018, Johannes Rudolph * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ public const int day_in_seconds = 24*60*60; [DBus (name = "io.mainframe.shopsystem.Database")] public class DataBase : Object { private class Statement { private Sqlite.Statement stmt; public Statement(Sqlite.Database db, string query) { int rc = db.prepare_v2(query, -1, out stmt); if(rc != Sqlite.OK) { error(_("Error: could not prepare statement: %s"), query); } } public void reset() { stmt.reset(); } public int step() { return stmt.step(); } public int bind_null(int index) { return stmt.bind_null(index); } public int bind_int(int index, int value) { return stmt.bind_int(index, value); } public int bind_text(int index, string value) { return stmt.bind_text(index, value); } public int bind_int64(int index, int64 value) { return stmt.bind_int64(index, value); } public int column_int(int index) { return stmt.column_int(index); } public string column_text(int index) { var result = stmt.column_text(index); return (result != null) ? result : ""; } public int64 column_int64(int index) { return stmt.column_int64(index); } } private Sqlite.Database db; private static Gee.HashMap queries = new Gee.HashMap(); private static Gee.HashMap statements = new Gee.HashMap(); //private static HashTable queries = new HashTable(null, null); //private static HashTable statements = new HashTable(null, null); public DataBase(string file) { int rc; rc = Sqlite.Database.open(file, out db); if(rc != Sqlite.OK) { error(_("Error: could not open database!")); } /* setup queries */ queries["product_name"] = "SELECT name FROM products WHERE id = ?"; queries["product_category"] = "SELECT categories.name FROM categories, products WHERE products.category = categories.id AND products.id = ?"; queries["product_amount"] = "SELECT amount FROM products WHERE id = ?"; queries["product_deprecated"]= "SELECT deprecated FROM products WHERE id = ?"; queries["product_set_deprecated"] = "UPDATE products SET deprecated=? WHERE id = ?"; queries["products"] = "SELECT id, name, amount FROM products ORDER BY name"; queries["purchase"] = "INSERT INTO sales ('user', 'product', 'timestamp') VALUES (?, ?, ?)"; queries["last_purchase"] = "SELECT product FROM sales WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; queries["undo"] = "DELETE FROM sales WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; queries["product_create"] = "INSERT INTO products ('id', 'name', 'category', 'amount') VALUES (?, ?, ?, ?)"; queries["price_create"] = "INSERT INTO prices ('product', 'valid_from', 'memberprice', 'guestprice') VALUES (?, ?, ?, ?)"; queries["stock"] = "INSERT INTO restock ('user', 'product', 'amount', 'price', 'timestamp', 'supplier', 'best_before_date') VALUES (?, ?, ?, ?, ?, ?, ?)"; queries["price"] = "SELECT memberprice, guestprice FROM prices WHERE product = ? AND valid_from <= ? ORDER BY valid_from DESC LIMIT 1"; queries["prices"] = "SELECT valid_from, memberprice, guestprice FROM prices WHERE product = ? ORDER BY valid_from ASC;"; queries["restocks_asc"] = "SELECT timestamp, amount, price, supplier, best_before_date FROM restock WHERE product = ? ORDER BY timestamp ASC;"; queries["restocks_desc"] = "SELECT timestamp, amount, price, supplier, best_before_date FROM restock WHERE product = ? ORDER BY timestamp DESC;"; queries["profit_complex"] = "SELECT SUM(memberprice - (SELECT price FROM purchaseprices WHERE product = purch.product)) FROM sales purch, prices WHERE purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1);"; queries["sales_complex"] = "SELECT SUM(memberprice) FROM sales purch, prices WHERE purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1);"; queries["stock_status"] = "SELECT stock.id, stock.name, categories.name, amount, memberprice, guestprice FROM stock, prices, categories WHERE stock.id = prices.product AND categories.id = stock.category AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = stock.id ORDER BY valid_from DESC LIMIT 1) ORDER BY categories.name, stock.name"; queries["stock_amount"] = "SELECT timestamp, amount FROM restock WHERE product = ? UNION ALL SELECT timestamp, -1 AS amount FROM sales WHERE product = ? ORDER BY timestamp DESC"; queries["session_set"] = "UPDATE authentication SET session=? WHERE user = ?"; queries["session_get"] = "SELECT user FROM authentication WHERE session = ?"; queries["username"] = "SELECT firstname, lastname FROM users WHERE id = ?"; queries["user_theme_get"] = "SELECT CASE WHEN sound_theme IS NULL THEN ? ELSE sound_theme END FROM users WHERE id = ?"; queries["user_theme_set"] = "UPDATE users SET sound_theme=? WHERE id = ?"; queries["password_get"] = "SELECT password FROM authentication WHERE user = ?"; queries["password_set"] = "UPDATE authentication SET password=? WHERE user = ?"; queries["userinfo"] = "SELECT firstname, lastname, email, gender, street, plz, city, pgp, hidden, disabled, sound_theme, joined_at FROM users WHERE id = ?"; queries["userauth"] = "SELECT superuser, auth_users, auth_products, auth_cashbox FROM authentication WHERE user = ?"; queries["userauth_set"] = "UPDATE authentication SET auth_users = ?, auth_products = ?, auth_cashbox = ? WHERE user = ?"; queries["profit_by_product"] = "SELECT name, SUM(memberprice - (SELECT price FROM purchaseprices WHERE product = purch.product)) AS price FROM sales purch, prices, products WHERE purch.product = products.id AND purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1) GROUP BY name ORDER BY price;"; queries["invoice"] = "SELECT timestamp, id AS productid, name AS productname, CASE WHEN user < 0 THEN (SELECT SUM(price * amount) / SUM(amount) FROM restock WHERE restock.product = id AND restock.timestamp <= sales.timestamp) else (SELECT CASE WHEN user=0 THEN guestprice else memberprice END FROM prices WHERE product = id AND valid_from <= timestamp ORDER BY valid_from DESC LIMIT 1) END AS price FROM sales INNER JOIN products ON sales.product = products.id WHERE user = ? AND timestamp >= ? AND timestamp <= ? ORDER BY timestamp"; queries["purchase_first"] = "SELECT timestamp FROM sales WHERE user = ? ORDER BY timestamp ASC LIMIT 1"; queries["purchase_last"] = "SELECT timestamp FROM sales WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; queries["count_articles"] = "SELECT COUNT(*) FROM products"; queries["count_users"] = "SELECT COUNT(*) FROM users"; queries["stock_value"] = "SELECT SUM(amount * price) FROM products INNER JOIN purchaseprices ON products.id = purchaseprices.product"; queries["total_sales"] = "SELECT SUM(price) FROM invoice WHERE user >= 0 AND timestamp >= ?"; queries["total_profit"] = "SELECT SUM(price - (SELECT price FROM purchaseprices WHERE product = productid)) FROM invoice WHERE user >= 0 AND timestamp >= ?"; queries["user_get_ids"] = "SELECT id FROM users WHERE id > 0"; queries["user_replace"] = "INSERT OR REPLACE INTO users ('id', 'email', 'firstname', 'lastname', 'gender', 'street', 'plz', 'city', 'pgp', 'hidden', 'disabled', 'joined_at', 'sound_theme') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, (select sound_theme from users where id = ?))"; queries["user_auth_create"] = "INSERT OR IGNORE INTO authentication (user) VALUES (?)"; queries["user_disable"] = "UPDATE users SET disabled = ? WHERE id = ?"; queries["last_timestamp"] = "SELECT timestamp FROM sales ORDER BY timestamp DESC LIMIT 1"; queries["category_list"] = "SELECT id, name FROM categories"; queries["supplier_list"] = "SELECT id, name, postal_code, city, street, phone, website FROM supplier"; queries["supplier_get"] = "SELECT id, name, postal_code, city, street, phone, website FROM supplier WHERE id = ?"; queries["supplier_add"] = "INSERT INTO supplier('name', 'postal_code', 'city', 'street', 'phone', 'website') VALUES (?, ?, ?, ?, ?, ?)"; queries["users_with_sales"] = "SELECT user FROM sales WHERE timestamp > ? AND timestamp < ? GROUP BY user"; queries["user_invoice_sum"] = "SELECT SUM(CASE WHEN user < 0 THEN (SELECT SUM(price * amount) / SUM(amount) FROM restock WHERE restock.product = id AND restock.timestamp <= sales.timestamp) else (SELECT CASE WHEN user=0 THEN guestprice else memberprice END FROM prices WHERE product = id AND valid_from <= timestamp ORDER BY valid_from DESC LIMIT 1) END) FROM sales INNER JOIN products ON sales.product = products.id WHERE user = ? AND timestamp >= ? AND timestamp <= ? ORDER BY timestamp"; queries["cashbox_status"] = "SELECT amount FROM current_cashbox_status"; queries["cashbox_add"] = "INSERT INTO cashbox_diff ('user', 'amount', 'timestamp') VALUES (?, ?, ?)"; queries["cashbox_history"] = "SELECT user, amount, timestamp FROM cashbox_diff ORDER BY timestamp DESC LIMIT 10"; queries["cashbox_changes"] = "SELECT user, amount, timestamp FROM cashbox_diff WHERE timestamp >= ? and timestamp < ? ORDER BY timestamp ASC"; queries["alias_ean_add"] = "INSERT OR IGNORE INTO ean_aliases (id, real_ean) VALUES (?, ?)"; queries["alias_ean_get"] = "SELECT real_ean FROM ean_aliases WHERE id = ?"; queries["alias_ean_list"] = "SELECT id, real_ean FROM ean_aliases ORDER BY id ASC"; queries["userid_rfid"] = "SELECT user FROM rfid_users WHERE rfid = ?"; queries["rfid_userid"] = "SELECT rfid FROM rfid_users WHERE user = ?"; queries["rfid_insert"] = "INSERT OR REPLACE INTO rfid_users ('user','rfid') VALUES (?,?)"; queries["rfid_delete_user"] = "DELETE FROM rfid_users WHERE user = ?"; /* compile queries into statements */ foreach(var entry in queries.entries) { statements[entry.key] = new Statement(db, entry.value); } #if 0 foreach(var key in queries.get_keys()) { statements[key] = new Statement(db, queries[key]); } #endif } public GLib.HashTable get_products() throws DBusError, IOError, DatabaseError { var result = new GLib.HashTable(null, null); statements["products"].reset(); while(statements["products"].step() == Sqlite.ROW) result[statements["products"].column_text(0)] = statements["products"].column_text(1); return result; } #if 0 public stock get_stats_stock() { var result = new stock(); var now = time_t(); /* init products */ statements["products"].reset(); while(statements["products"].step() == Sqlite.ROW) { var id = uint64.parse(statements["products"].column_text(0)); var name = statements["products"].column_text(1); int amount = int.parse(statements["products"].column_text(2)); var product = new stock.product(id, name); result.add(product); product.add(now, amount); statements["stock_amount"].reset(); statements["stock_amount"].bind_text(1, "%llu".printf(id)); statements["stock_amount"].bind_text(2, "%llu".printf(id)); while(statements["stock_amount"].step() == Sqlite.ROW) { var timestamp = uint64.parse(statements["stock_amount"].column_text(0)); var diff = statements["stock_amount"].column_int(1); product.add(timestamp+1, amount); amount -= diff; product.add(timestamp, amount); } } return result; } #endif #if 0 public profit_per_product get_stats_profit_per_products() { var result = new profit_per_product(); statements["profit_by_product"].reset(); statements["profit_by_product"].bind_int(1, 0); statements["profit_by_product"].bind_text(2, "99999999999999"); while(statements["profit_by_product"].step() == Sqlite.ROW) { var name = statements["profit_by_product"].column_text(0); var profit = statements["profit_by_product"].column_int(1); result.add(name, profit); } return result; } #endif #if 0 public profit_per_weekday get_stats_profit_per_weekday() { var result = new profit_per_weekday(); var now = new DateTime.now_utc(); var today = new DateTime.utc(now.get_year(), now.get_month(), now.get_day_of_month(), 8, 0, 0); var tomorrow = today.add_days(1); var weekday = tomorrow.get_day_of_week()-1; var to = tomorrow.to_unix(); var from = to - day_in_seconds; var weeks = 8; for(int i=0; i 0) statements["stock"].bind_int(6, supplier); else statements["stock"].bind_null(6); if(best_before_date > 0) statements["stock"].bind_int64(7, best_before_date); else statements["stock"].bind_null(7); rc = statements["stock"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } public void new_product(uint64 id, string name, int category, int memberprice, int guestprice) throws DBusError, IOError, DatabaseError { statements["product_create"].reset(); statements["product_create"].bind_text(1, @"$id"); statements["product_create"].bind_text(2, name); statements["product_create"].bind_int(3, category); statements["product_create"].bind_int(4, 0); int rc = statements["product_create"].step(); if(rc == Sqlite.CONSTRAINT) { throw new DatabaseError.CONSTRAINT_FAILED(db.errmsg()); } else if(rc != Sqlite.DONE) { throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } new_price(id, 0, memberprice, guestprice); } public void new_price(uint64 product, int64 timestamp, int memberprice, int guestprice) throws DBusError, IOError, DatabaseError { statements["price_create"].reset(); statements["price_create"].bind_text(1, @"$product"); statements["price_create"].bind_int64(2, timestamp); statements["price_create"].bind_int(3, memberprice); statements["price_create"].bind_int(4, guestprice); int rc = statements["price_create"].step(); if(rc != Sqlite.DONE) { throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } } public bool check_user_password(int32 user, string password) throws DBusError, IOError, DatabaseError { statements["password_get"].reset(); statements["password_get"].bind_int(1, user); if(statements["password_get"].step() == Sqlite.ROW) { var pwhash_db = statements["password_get"].column_text(0); var pwhash_user = Checksum.compute_for_string(ChecksumType.SHA256, password); return pwhash_db == pwhash_user; } else { return false; } } public void set_user_password(int32 user, string password) throws DBusError, IOError, DatabaseError { var pwhash = Checksum.compute_for_string(ChecksumType.SHA256, password); int rc; /* create user auth line if not existing */ statements["user_auth_create"].reset(); statements["user_auth_create"].bind_int(1, user); rc = statements["user_auth_create"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); /* set password */ statements["password_set"].reset(); statements["password_set"].bind_text(1, pwhash); statements["password_set"].bind_int(2, user); rc = statements["password_set"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } public void set_sessionid(int user, string sessionid) throws DBusError, IOError, DatabaseError { statements["session_set"].reset(); statements["session_set"].bind_text(1, sessionid); statements["session_set"].bind_int(2, user); int rc = statements["session_set"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } public int get_user_by_sessionid(string sessionid) throws DBusError, IOError, DatabaseError { statements["session_get"].reset(); statements["session_get"].bind_text(1, sessionid); if(statements["session_get"].step() == Sqlite.ROW) { return statements["session_get"].column_int(0); } else { throw new DatabaseError.SESSION_NOT_FOUND(_("No such session available in database!")); } } public UserInfo get_user_info(int user) throws DBusError, IOError, DatabaseError { var result = UserInfo(); statements["userinfo"].reset(); statements["userinfo"].bind_int(1, user); int rc = statements["userinfo"].step(); if(rc == Sqlite.ROW) { result.id = user; result.firstname = statements["userinfo"].column_text(0); result.lastname = statements["userinfo"].column_text(1); result.email = statements["userinfo"].column_text(2); result.gender = statements["userinfo"].column_text(3); result.street = statements["userinfo"].column_text(4); result.postcode = statements["userinfo"].column_text(5); result.city = statements["userinfo"].column_text(6); result.pgp = statements["userinfo"].column_text(7); result.hidden = statements["userinfo"].column_int(8) == 1; result.disabled = statements["userinfo"].column_int(9) == 1; result.soundTheme = statements["userinfo"].column_text(10); result.joined_at = statements["userinfo"].column_int64(11); } else if(rc == Sqlite.DONE) { throw new DatabaseError.USER_NOT_FOUND(_("user not found")); } else { throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } statements["rfid_userid"].reset(); statements["rfid_userid"].bind_int(1, user); rc = statements["rfid_userid"].step(); string[] rfid = {}; while(rc == Sqlite.ROW) { rfid += statements["rfid_userid"].column_text(0); rc = statements["rfid_userid"].step(); } result.rfid = rfid; return result; } public UserAuth get_user_auth(int user) throws DBusError, IOError, DatabaseError { var result = UserAuth(); result.id = user; result.superuser = false; result.auth_cashbox = false; result.auth_products = false; result.auth_users = false; statements["userauth"].reset(); statements["userauth"].bind_int(1, user); int rc = statements["userauth"].step(); if(rc == Sqlite.ROW) { result.superuser = statements["userauth"].column_int(0) == 1; result.auth_users = statements["userauth"].column_int(1) == 1; result.auth_products = statements["userauth"].column_int(2) == 1; result.auth_cashbox = statements["userauth"].column_int(3) == 1; } else if(rc == Sqlite.DONE) { /* entry not found, we return defaults */ } else { throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } return result; } public void set_user_auth(UserAuth auth) throws DBusError, IOError, DatabaseError { int rc; /* create user auth line if not existing */ statements["user_auth_create"].reset(); statements["user_auth_create"].bind_int(1, auth.id); rc = statements["user_auth_create"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); /* set authentication */ statements["userauth_set"].reset(); statements["userauth_set"].bind_int(1, auth.auth_users ? 1 : 0); statements["userauth_set"].bind_int(2, auth.auth_products ? 1 : 0); statements["userauth_set"].bind_int(3, auth.auth_cashbox ? 1 : 0); statements["userauth_set"].bind_int(4, auth.id); rc = statements["userauth_set"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } public string get_username(int user) throws DBusError, IOError, DatabaseError { statements["username"].reset(); statements["username"].bind_int(1, user); if(statements["username"].step() == Sqlite.ROW) { return statements["username"].column_text(0)+" "+statements["username"].column_text(1); } else { throw new DatabaseError.USER_NOT_FOUND(_("No such user available in database!")); } } public string get_user_theme(int user, string fallback) throws DBusError, IOError, DatabaseError { statements["user_theme_get"].reset(); statements["user_theme_get"].bind_text(1, fallback); statements["user_theme_get"].bind_int(2, user); if(statements["user_theme_get"].step() == Sqlite.ROW) { return statements["user_theme_get"].column_text(0); } else { throw new DatabaseError.USER_NOT_FOUND(_("No such user available in database!")); } } public void set_userTheme(int user, string userTheme) throws DBusError, IOError, DatabaseError { statements["user_theme_set"].reset(); if (userTheme == "") { statements["user_theme_set"].bind_null(1); } else { statements["user_theme_set"].bind_text(1, userTheme); } statements["user_theme_set"].bind_int(2, user); int rc = statements["user_theme_set"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } public InvoiceEntry[] get_invoice(int user, int64 from=0, int64 to=-1) throws DBusError, IOError, DatabaseError { InvoiceEntry[] result = {}; if(to == -1) { to = time_t(); } statements["invoice"].reset(); statements["invoice"].bind_int(1, user); statements["invoice"].bind_int64(2, from); statements["invoice"].bind_int64(3, to); int rc = statements["invoice"].step(); while(rc == Sqlite.ROW) { InvoiceEntry entry = {}; entry.timestamp = statements["invoice"].column_int64(0); entry.product.ean = uint64.parse(statements["invoice"].column_text(1)); entry.product.name = statements["invoice"].column_text(2); entry.price = statements["invoice"].column_int(3); result += entry; rc = statements["invoice"].step(); } if(rc != Sqlite.DONE) { throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } return result; } public int64 get_first_purchase(int user) throws DBusError, IOError, DatabaseError { statements["purchase_first"].reset(); statements["purchase_first"].bind_int(1, user); if(statements["purchase_first"].step() == Sqlite.ROW) return statements["purchase_first"].column_int64(0); else return 0; } public int64 get_last_purchase(int user) throws DBusError, IOError, DatabaseError { statements["purchase_last"].reset(); statements["purchase_last"].bind_int(1, user); if(statements["purchase_last"].step() == Sqlite.ROW) return statements["purchase_last"].column_int64(0); else return 0; } public StatsInfo get_stats_info() throws DBusError, IOError, DatabaseError { var result = StatsInfo(); DateTime now = new DateTime.now_local(); DateTime today = new DateTime.local(now.get_year(), now.get_month(), now.get_hour() < 8 ? now.get_day_of_month()-1 : now.get_day_of_month(), 8, 0, 0); DateTime month = new DateTime.local(now.get_year(), now.get_month(), 1, 0, 0, 0); DateTime last4weeks = now.add_days(-28); DateTime last4months = now.add_months(-4); statements["count_articles"].reset(); if(statements["count_articles"].step() == Sqlite.ROW) result.count_articles = statements["count_articles"].column_int(0); statements["count_users"].reset(); if(statements["count_users"].step() == Sqlite.ROW) result.count_users = statements["count_users"].column_int(0); statements["stock_value"].reset(); if(statements["stock_value"].step() == Sqlite.ROW) result.stock_value = statements["stock_value"].column_int(0); statements["total_sales"].reset(); statements["total_sales"].bind_int64(1, 0); if(statements["total_sales"].step() == Sqlite.ROW) result.sales_total = statements["total_sales"].column_int(0); statements["total_profit"].reset(); statements["total_profit"].bind_int64(1, 0); if(statements["total_profit"].step() == Sqlite.ROW) result.profit_total = statements["total_profit"].column_int(0); statements["total_sales"].reset(); statements["total_sales"].bind_int64(1, today.to_unix()); if(statements["total_sales"].step() == Sqlite.ROW) result.sales_today = statements["total_sales"].column_int(0); statements["total_profit"].reset(); statements["total_profit"].bind_int64(1, today.to_unix()); if(statements["total_profit"].step() == Sqlite.ROW) result.profit_today = statements["total_profit"].column_int(0); statements["total_sales"].reset(); statements["total_sales"].bind_int64(1, month.to_unix()); if(statements["total_sales"].step() == Sqlite.ROW) result.sales_this_month = statements["total_sales"].column_int(0); statements["total_profit"].reset(); statements["total_profit"].bind_int64(1, month.to_unix()); if(statements["total_profit"].step() == Sqlite.ROW) result.profit_this_month = statements["total_profit"].column_int(0); statements["total_sales"].reset(); statements["total_sales"].bind_int64(1, last4weeks.to_unix()); if(statements["total_sales"].step() == Sqlite.ROW) result.sales_per_day = statements["total_sales"].column_int(0) / 28; statements["total_profit"].reset(); statements["total_profit"].bind_int64(1, last4weeks.to_unix()); if(statements["total_profit"].step() == Sqlite.ROW) result.profit_per_day = statements["total_profit"].column_int(0) / 28; statements["total_sales"].reset(); statements["total_sales"].bind_int64(1, last4months.to_unix()); if(statements["total_sales"].step() == Sqlite.ROW) result.sales_per_month = statements["total_sales"].column_int(0) / 4; statements["total_profit"].reset(); statements["total_profit"].bind_int64(1, last4months.to_unix()); if(statements["total_profit"].step() == Sqlite.ROW) result.profit_per_month = statements["total_profit"].column_int(0) / 4; return result; } public int[] get_member_ids() throws DBusError, IOError, DatabaseError { int[] result = {}; statements["user_get_ids"].reset(); while(statements["user_get_ids"].step() == Sqlite.ROW) result += statements["user_get_ids"].column_int(0); return result; } public void user_disable(int user, bool value) throws DBusError, IOError, DatabaseError { int rc; /* create user auth line if not existing */ statements["user_auth_create"].reset(); statements["user_auth_create"].bind_int(1, user); rc = statements["user_auth_create"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); /* set disabled flag */ statements["user_disable"].reset(); statements["user_disable"].bind_int(1, value ? 1 : 0); statements["user_disable"].bind_int(2, user); rc = statements["user_disable"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } public void user_replace(UserInfo u) throws DBusError, IOError, DatabaseError { statements["user_replace"].reset(); statements["user_replace"].bind_int(1, u.id); statements["user_replace"].bind_text(2, u.email); statements["user_replace"].bind_text(3, u.firstname); statements["user_replace"].bind_text(4, u.lastname); statements["user_replace"].bind_text(5, u.gender); statements["user_replace"].bind_text(6, u.street); statements["user_replace"].bind_text(7, u.postcode); statements["user_replace"].bind_text(8, u.city); statements["user_replace"].bind_text(9, u.pgp); statements["user_replace"].bind_int(10, u.hidden ? 1 : 0); statements["user_replace"].bind_int(11, u.disabled ? 1 : 0); statements["user_replace"].bind_int64(12, u.joined_at); statements["user_replace"].bind_int(13, u.id); int rc = statements["user_replace"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); statements["rfid_delete_user"].reset(); statements["rfid_delete_user"].bind_int(1, u.id); rc = statements["rfid_delete_user"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); foreach (string rfid in u.rfid) { statements["rfid_insert"].reset(); statements["rfid_insert"].bind_int(1, u.id); statements["rfid_insert"].bind_text(2, rfid); rc = statements["rfid_insert"].step(); if(rc != Sqlite.DONE) throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } } public bool user_is_disabled(int user) throws DBusError, IOError, DatabaseError { return get_user_info(user).disabled; } public bool user_exists(int user) throws DBusError, IOError, DatabaseError { if(user in get_member_ids()) return true; return false; } public bool user_equals(UserInfo u) throws DBusError, IOError, DatabaseError { var dbu = get_user_info(u.id); return u.equals(dbu); } public int64 get_timestamp_of_last_purchase() throws DBusError, IOError, DatabaseError { statements["last_timestamp"].reset(); if(statements["last_timestamp"].step() != Sqlite.ROW) return 0; return statements["last_timestamp"].column_int64(0); } public Category[] get_category_list() throws DBusError, IOError, DatabaseError { Category[] result = {}; statements["category_list"].reset(); while(statements["category_list"].step() == Sqlite.ROW) { Category entry = { statements["category_list"].column_int(0), statements["category_list"].column_text(1) }; result += entry; } return result; } public Supplier[] get_supplier_list() throws DBusError, IOError, DatabaseError { Supplier[] result = {}; statements["supplier_list"].reset(); while(statements["supplier_list"].step() == Sqlite.ROW) { Supplier entry = { statements["supplier_list"].column_int64(0), statements["supplier_list"].column_text(1), statements["supplier_list"].column_text(2), statements["supplier_list"].column_text(3), statements["supplier_list"].column_text(4), statements["supplier_list"].column_text(5), statements["supplier_list"].column_text(6) }; result += entry; } return result; } public Supplier get_supplier(int id) throws DBusError, IOError, DatabaseError { Supplier result = Supplier(); statements["supplier_get"].reset(); statements["supplier_get"].bind_int(1, id); if(statements["supplier_get"].step() != Sqlite.ROW) { result.id = 0; result.name = "Unknown"; result.postal_code = ""; result.city = ""; result.street = ""; result.phone = ""; result.website = ""; } else { result.id = statements["supplier_get"].column_int64(0); result.name = statements["supplier_get"].column_text(1); result.postal_code = statements["supplier_get"].column_text(2); result.city = statements["supplier_get"].column_text(3); result.street = statements["supplier_get"].column_text(4); result.phone = statements["supplier_get"].column_text(5); result.website = statements["supplier_get"].column_text(6); } return result; } public void add_supplier(string name, string postal_code, string city, string street, string phone, string website) throws DBusError, IOError, DatabaseError { statements["supplier_add"].reset(); statements["supplier_add"].bind_text(1, name); statements["supplier_add"].bind_text(2, postal_code); statements["supplier_add"].bind_text(3, city); statements["supplier_add"].bind_text(4, street); statements["supplier_add"].bind_text(5, phone); statements["supplier_add"].bind_text(6, website); int rc = statements["supplier_add"].step(); if(rc != Sqlite.DONE) { throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } } public int[] get_users_with_sales(int64 timestamp_from, int64 timestamp_to) throws DBusError, IOError, DatabaseError { var result = new int[0]; statements["users_with_sales"].reset(); statements["users_with_sales"].bind_int64(1, timestamp_from); statements["users_with_sales"].bind_int64(2, timestamp_to); while(statements["users_with_sales"].step() == Sqlite.ROW) { result += statements["users_with_sales"].column_int(0); } return result; } public Price get_user_invoice_sum(int user, int64 timestamp_from, int64 timestamp_to) throws DBusError, IOError, DatabaseError { Price result = 0; statements["user_invoice_sum"].reset(); statements["user_invoice_sum"].bind_int(1, user); statements["user_invoice_sum"].bind_int64(2, timestamp_from); statements["user_invoice_sum"].bind_int64(3, timestamp_to); if(statements["user_invoice_sum"].step() == Sqlite.ROW) result = statements["user_invoice_sum"].column_int(0); return result; } public Price cashbox_status() throws DBusError, IOError, DatabaseError { Price result = 0; statements["cashbox_status"].reset(); if(statements["cashbox_status"].step() == Sqlite.ROW) result = statements["cashbox_status"].column_int(0); return result; } public void cashbox_add(int user, Price amount, int64 timestamp) throws DBusError, IOError, DatabaseError { statements["cashbox_add"].reset(); statements["cashbox_add"].bind_int(1, user); statements["cashbox_add"].bind_int(2, amount); statements["cashbox_add"].bind_int64(3, timestamp); int rc = statements["cashbox_add"].step(); if(rc != Sqlite.DONE) { throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } } public CashboxDiff[] cashbox_history() throws DBusError, IOError, DatabaseError { CashboxDiff[] result = {}; statements["cashbox_history"].reset(); while(statements["cashbox_history"].step() == Sqlite.ROW) { CashboxDiff entry = { statements["cashbox_history"].column_int(0), statements["cashbox_history"].column_int(1), statements["cashbox_history"].column_int64(2), }; result += entry; }; return result; } public CashboxDiff[] cashbox_changes(int64 start, int64 stop) throws DBusError, IOError, DatabaseError { CashboxDiff[] result = {}; statements["cashbox_changes"].reset(); statements["cashbox_changes"].bind_int64(1, start); statements["cashbox_changes"].bind_int64(2, stop); while(statements["cashbox_changes"].step() == Sqlite.ROW) { CashboxDiff entry = { statements["cashbox_changes"].column_int(0), statements["cashbox_changes"].column_int(1), statements["cashbox_changes"].column_int64(2), }; result += entry; }; return result; } public void ean_alias_add(uint64 ean, uint64 real_ean) throws DBusError, IOError, DatabaseError { statements["alias_ean_add"].reset(); statements["alias_ean_add"].bind_text(1, "%llu".printf(ean)); statements["alias_ean_add"].bind_text(2, "%llu".printf(real_ean)); int rc = statements["alias_ean_add"].step(); if(rc != Sqlite.DONE) { throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } } public uint64 ean_alias_get(uint64 ean) throws DBusError, IOError, DatabaseError { uint64 result = ean; statements["alias_ean_get"].reset(); statements["alias_ean_get"].bind_text(1, "%llu".printf(ean)); if(statements["alias_ean_get"].step() == Sqlite.ROW) result = statements["alias_ean_get"].column_int64(0); return result; } public EanAlias[] ean_alias_list() throws DBusError, IOError, DatabaseError { EanAlias[] result = {}; statements["alias_ean_list"].reset(); while(statements["alias_ean_list"].step() == Sqlite.ROW) { EanAlias entry = { statements["alias_ean_list"].column_int64(0), statements["alias_ean_list"].column_int64(1), }; result += entry; }; return result; } public static int sortBestBeforeEntry(BestBeforeEntry? a, BestBeforeEntry? b) { if(a.best_before_date == b.best_before_date) return 0; else if(a.best_before_date < b.best_before_date) return -1; else return 1; } public BestBeforeEntry?[] bestbeforelist() throws DBusError, IOError, DatabaseError { var bbdlist = new GLib.GenericArray(); foreach(var product in get_stock()) { var amount = product.amount; var pid = product.ean; if(amount <= 0) continue; foreach(var restock in get_restocks(pid, true)) { if (restock.amount > amount) { BestBeforeEntry entry = { pid, product.name, amount, restock.best_before_date }; bbdlist.add(entry); } else { BestBeforeEntry entry = { pid, product.name, restock.amount, restock.best_before_date }; bbdlist.add(entry); } amount -= restock.amount; if(amount <= 0) break; } } bbdlist.sort(sortBestBeforeEntry); return bbdlist.data; } public int get_userid_for_rfid(string rfid) throws DBusError, IOError, DatabaseError { statements["userid_rfid"].reset(); statements["userid_rfid"].bind_text(1, rfid); int rc = statements["userid_rfid"].step(); switch(rc) { case Sqlite.ROW: return statements["userid_rfid"].column_int(0); case Sqlite.DONE: throw new DatabaseError.RFID_NOT_FOUND(_("unknown rfid: %s"), rfid); default: throw new DatabaseError.INTERNAL_ERROR(_("internal error: %d"), rc); } } }