var window = this;
var navigationwindow = window;
window.loggingWindow = undefined;
var roasterlist = findChildObject(this, 'machineselector');
var model = new DeviceTreeModel;
roasterlist.setModel(model);
roasterlist.currentIndex = QSettings.value("machineSelection", 0);
roasterlist['currentIndexChanged(int)'].connect(function() {
QSettings.setValue("machineSelection", roasterlist.currentIndex);
});
var resetdbconnection = findChildObject(this, 'resetconnection');
resetdbconnection.triggered.connect(function() {
QSettings.setValue("database/exists", false);
QSettings.setValue("database/hostname", "");
QSettings.setValue("database/dbname", "");
QSettings.setValue("database/user", "");
QSettings.setValue("database/password", "");
});
var schedule = findChildObject(this, 'schedule');
schedule.clicked.connect(function() {
createWindow("schedule");
});
var manual = findChildObject(this, 'manual');
manual.clicked.connect(function() {
createWindow("manualLogEntry");
});
var profilehistory = findChildObject(this, 'profilehistory');
profilehistory.clicked.connect(function() {
createWindow("profilehistory");
});
var greensalesbutton = findChildObject(this, 'greensales');
greensalesbutton.clicked.connect(function() {
createWindow("greensales");
});
var sumcup = findChildObject(this, 'sumcupping');
sumcup.clicked.connect(function() {
var sessionlist = createWindow("finsessionlist");
sessionlist.windowTitle = TTR("navwindow", "Typica - Summarize Cupping Session");
});
var ncsbutton = findChildObject(this, 'createcupping')
ncsbutton.clicked.connect(function() {
var ncswindow = createWindow("session");
ncswindow.windowTitle = TTR("navwindow", "Typica - New Cupping Session");
});
var jcsbutton = findChildObject(this, 'joincupping')
jcsbutton.clicked.connect(function() {
var jcswindow = createWindow("sessionlist");
jcswindow.windowTitle = TTR("navwindow", "Typica - Join Cupping Session");
});
/*
var nrbutton = findChildObject(this, 'newroaster');
nrbutton.clicked.connect(function() {
var nrwindow = createWindow("newroaster");
nrwindow.windowTitle = TTR("navwindow", "Typica - New Roaster");
});
*/
var inventory = findChildObject(this, 'inventory');
inventory.clicked.connect(function() {
var invwin = createWindow("inventory");
invwin.windowTitle = TTR("navwindow", "Typica - Inventory");
});
var roastspecbutton = findChildObject(this, 'roastspec');
roastspecbutton.clicked.connect(function() {
var specwindow = createWindow("roastspec");
});
var gbutton = findChildObject(this, 'green');
gbutton.clicked.connect(function() {
var purchasewindow = createWindow("purchase");
});
var nrbutton = findChildObject(this, 'newroasted');
nrbutton.clicked.connect(function() {
var nrwindow = createWindow("newroasted");
nrwindow.windowTitle = TTR("navwindow", "Manage Roasted Coffee Items");
});
var importb = findChildObject(this, 'target');
importb.clicked.connect(function() {
var importWindow = createWindow("importTargets");
importWindow.windowTitle = TTR("navwindow", "Typica - Import Target Roast Profiles");
});
var roastbutton = findChildObject(this, 'roast');
roastbutton.clicked.connect(function() {
if(typeof(window.loggingWindow) == "undefined")
{
window.loggingWindow = createWindow("basicWindow");
window.loggingWindow.windowTitle = "Typica [*]";
window.loggingWindow.navigationWindow = window;
}
else
{
window.loggingWindow.raise();
window.loggingWindow.activateWindow();
}
});
var configurebutton = findChildObject(this, 'configure');
configurebutton.clicked.connect(function() {
var confwindow = new SettingsWindow;
confwindow.show();
});
0 THEN RETURN (SELECT current_date - min(time)::date + 1 FROM use WHERE item = $1); ELSE RETURN (SELECT max(time)::date - min(time)::date + 1 FROM use WHERE item = $1); END IF; END; $$ LANGUAGE plpgsql STRICT");
query.exec("CREATE TABLE IF NOT EXISTS items(id bigint NOT NULL, name text NOT NULL, reference text, unit text NOT NULL, quantity numeric DEFAULT 0, category text)");
query.exec("CREATE SEQUENCE items_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1");
query.exec("CREATE TABLE IF NOT EXISTS coffees(origin text NOT NULL, region text, producer text, grade text, milling text, drying text) INHERITS (items)");
query.exec("CREATE VIEW coffee_history AS SELECT coffees.id, coffees.name, coffees.origin, coffees.quantity AS stock, (SELECT sum(use.quantity) AS sum FROM use WHERE (use.item = coffees.id)) AS used, time_range(coffees.id) AS \"interval\", ((SELECT (sum(use.quantity) / (time_range(use.item))::numeric) FROM use WHERE (use.item = coffees.id) GROUP BY use.item))::numeric(10,2) AS rate, (SELECT (('now'::text)::date + ((coffees.quantity / (SELECT (sum(use.quantity) / (time_range(use.item))::numeric) FROM use WHERE (use.item = coffees.id) GROUP BY use.item)))::integer)) AS \"out\" FROM coffees WHERE (coffees.id IN (SELECT use.item FROM use)) ORDER BY coffees.origin");
query.exec("CREATE TABLE IF NOT EXISTS current_items (item bigint NOT NULL)");
query.exec("CREATE TABLE IF NOT EXISTS decaf_coffees (decaf_method text NOT NULL) INHERITS (coffees)");
query.exec("CREATE TABLE IF NOT EXISTS files (id bigint NOT NULL, name text NOT NULL, type text NOT NULL, note text, file bytea NOT NULL)");
query.exec("CREATE TABLE IF NOT EXISTS item_files(\"time\" timestamp without time zone NOT NULL, item bigint NOT NULL, files bigint[] NOT NULL)");
query.exec("CREATE TYPE item_transaction_with_balance AS (\"time\" timestamp without time zone, item bigint, quantity numeric, cost numeric, vendor text, reason text, customer text, type text, balance numeric)");
query.exec("CREATE TABLE IF NOT EXISTS lb_bag_conversion (item bigint NOT NULL, conversion numeric NOT NULL)");
query.exec("CREATE TABLE IF NOT EXISTS machine (id bigint NOT NULL, name text NOT NULL)");
query.exec("CREATE VIEW regular_coffees AS SELECT coffees.id, coffees.name, coffees.reference, coffees.unit, coffees.quantity, coffees.category, coffees.origin, coffees.region, coffees.producer, coffees.grade, coffees.milling, coffees.drying FROM coffees WHERE (NOT (coffees.id IN (SELECT decaf_coffees.id FROM decaf_coffees)))");
query.exec("CREATE TABLE IF NOT EXISTS roasting_log (\"time\" timestamp without time zone NOT NULL, unroasted_id bigint[], unroasted_quantity numeric[], unroasted_total_quantity numeric, roasted_id bigint, roasted_quantity numeric, transaction_type text NOT NULL, annotation text, machine bigint NOT NULL, duration interval, approval boolean, humidity numeric, barometric numeric, indoor_air numeric, outdoor_air numeric, files bigint[])");
query.exec("CREATE VIEW short_log AS SELECT roasting_log.\"time\", (SELECT items.name FROM items WHERE (items.id = roasting_log.roasted_id)) AS name, roasting_log.unroasted_total_quantity, roasting_log.roasted_quantity, ((((roasting_log.unroasted_total_quantity - roasting_log.roasted_quantity) / roasting_log.unroasted_total_quantity) * (100)::numeric))::numeric(12,2) AS weight_loss, roasting_log.duration FROM roasting_log ORDER BY roasting_log.\"time\"");
query.exec("CREATE FUNCTION add_inventory() RETURNS trigger AS $$ BEGIN UPDATE items SET quantity = quantity + NEW.quantity WHERE id = NEW.item; RETURN NEW; END; $$ LANGUAGE plpgsql");
query.exec("CREATE FUNCTION bags_in_stock(bigint) RETURNS numeric AS $_$SELECT quantity / (SELECT conversion FROM lb_bag_conversion WHERE item = id) FROM items WHERE id = $1;$_$ LANGUAGE sql IMMUTABLE STRICT");
query.exec("CREATE FUNCTION log_use() RETURNS trigger AS $$ DECLARE i integer := array_lower(NEW.unroasted_id, 1); u integer := array_upper(NEW.unroasted_id, 1); BEGIN WHILE i <= u LOOP INSERT INTO use VALUES(NEW.time, NEW.unroasted_id[i], NEW.unroasted_quantity[i]); i := i + 1; END LOOP; RETURN NEW; END; $$ LANGUAGE plpgsql");
query.exec("CREATE FUNCTION replace_inventory() RETURNS trigger AS $$ BEGIN UPDATE items SET quantity = NEW.quantity WHERE id = NEW.item; RETURN NEW; END; $$ LANGUAGE plpgsql");
query.exec("CREATE FUNCTION subtract_inventory() RETURNS trigger AS $$ BEGIN UPDATE items SET quantity = quantity - NEW.quantity WHERE id = NEW.item; RETURN NEW; END; $$ LANGUAGE plpgsql");
query.exec("CREATE SEQUENCE files_id_seq START WITH 1 INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1");
query.exec("ALTER TABLE files ALTER COLUMN id SET DEFAULT nextval('files_id_seq'::regclass)");
query.exec("ALTER TABLE items ALTER COLUMN id SET DEFAULT nextval('items_id_seq'::regclass)");
query.exec("ALTER TABLE ONLY files ADD CONSTRAINT file_pkey PRIMARY KEY (id)");
query.exec("ALTER TABLE ONLY items ADD CONSTRAINT items_pkey PRIMARY KEY (id)");
query.exec("ALTER TABLE ONLY lb_bag_conversion ADD CONSTRAINT lb_bag_conversion_item_key UNIQUE (item)");
query.exec("ALTER TABLE ONLY roasting_log ADD CONSTRAINT roasting_log_pkey PRIMARY KEY (\"time\", machine)");
query.exec("CREATE INDEX itemcategories ON items USING btree (category)");
query.exec("CREATE INDEX itemnames ON items USING btree (name)");
query.exec("CREATE INDEX roasting_log_index ON roasting_log USING btree (\"time\")");
query.exec("CREATE INDEX transactionitems ON transactions USING btree (item)");
query.exec("CREATE INDEX transactiontimes ON transactions USING btree (\"time\")");
query.exec("CREATE TRIGGER add_inventory_trigger AFTER INSERT ON purchase FOR EACH ROW EXECUTE PROCEDURE add_inventory()");
query.exec("CREATE TRIGGER add_inventory_trigger AFTER INSERT ON make FOR EACH ROW EXECUTE PROCEDURE add_inventory()");
query.exec("CREATE TRIGGER log_use_trigger AFTER INSERT ON roasting_log FOR EACH ROW EXECUTE PROCEDURE log_use()");
query.exec("CREATE TRIGGER replace_inventory_trigger AFTER INSERT ON inventory FOR EACH ROW EXECUTE PROCEDURE replace_inventory()");
query.exec("CREATE TRIGGER subtract_inventory_trigger AFTER INSERT ON loss FOR EACH ROW EXECUTE PROCEDURE subtract_inventory()");
query.exec("CREATE TRIGGER subtract_inventory_trigger AFTER INSERT ON sale FOR EACH ROW EXECUTE PROCEDURE subtract_inventory()");
query.exec("CREATE TRIGGER subtract_inventory_trigger AFTER INSERT ON use FOR EACH ROW EXECUTE PROCEDURE subtract_inventory()");
query.exec("ALTER TABLE ONLY item_files ADD CONSTRAINT item_files_item_fkey FOREIGN KEY (item) REFERENCES items(id)");
query.exec("ALTER TABLE ONLY transactions ADD CONSTRAINT transactions_item_fkey FOREIGN KEY (item) REFERENCES items(id)");
query.exec("INSERT INTO TypicaFeatures (feature, enabled, version) VALUES('base-features', TRUE, 1)");
query = query.invalidate();
};
/* Some changes to the database are required for sample roasting features in
Typica 1.6 and later. */
var DBCreateSampleRoasting = function() {
var query = new QSqlQuery;
query.exec("CREATE TABLE IF NOT EXISTS item_attributes (id bigserial PRIMARY KEY NOT NULL, name text NOT NULL)");
query.exec("CREATE TABLE IF NOT EXISTS coffee_sample_items(arrival timestamp without time zone, vendor text, attribute_ids bigint[], attribute_values text[], item_id bigint) INHERITS (items)");
query.exec("INSERT INTO TypicaFeatures (feature, enabled, version) VALUES('sample-roasting', TRUE, 1)");
query = query.invalidate();
};
/* Some changes to the database are required to log who performed what tasks.
This adds logging for all transaction types and also for the roasting log.*/
var DBUpdateMultiUser = function() {
var query = new QSqlQuery;
query.exec("ALTER TABLE transactions ADD COLUMN person text DEFAULT NULL");
query.exec("ALTER TABLE roasting_log ADD COLUMN person text DEFAULT NULL");
query.exec("CREATE OR REPLACE FUNCTION log_session_user() RETURNS trigger AS $$ BEGIN NEW.person := session_user; RETURN NEW; END; $$ LANGUAGE plpgsql");
query.exec("CREATE TRIGGER log_person BEFORE INSERT ON inventory FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
query.exec("CREATE TRIGGER log_person BEFORE INSERT ON loss FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
query.exec("CREATE TRIGGER log_person BEFORE INSERT ON make FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
query.exec("CREATE TRIGGER log_person BEFORE INSERT ON purchase FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
query.exec("CREATE TRIGGER log_person BEFORE INSERT ON sale FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
query.exec("CREATE TRIGGER log_person BEFORE INSERT ON use FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
query.exec("CREATE TRIGGER log_person BEFORE INSERT ON roasting_log FOR EACH ROW EXECUTE PROCEDURE log_session_user()");
query.exec("UPDATE TypicaFeatures SET version = 2 WHERE feature = 'base-features'");
query = query.invalidate();
};
/* Bug fix and optimization for item_history */
var DBUpdateHistory = function() {
var query = new QSqlQuery;
query.exec("CREATE TYPE transaction_type AS (type text, quantity numeric)");
query.exec("CREATE FUNCTION update_balance(numeric, transaction_type) RETURNS numeric AS $$ BEGIN CASE $2.type WHEN 'PURCHASE', 'MAKE' THEN RETURN $1 + $2.quantity; WHEN 'INVENTORY' THEN RETURN $2.quantity; WHEN 'USE', 'SALE', 'LOSS' THEN RETURN $1 - $2.quantity; END CASE; END; $$ LANGUAGE plpgsql STRICT");
query.exec("CREATE AGGREGATE transaction_balance (BASETYPE = transaction_type, SFUNC = update_balance, STYPE = numeric, INITCOND = '0')");
query.exec("CREATE OR REPLACE FUNCTION item_history(bigint) RETURNS SETOF item_transaction_with_balance AS $$ SELECT time, item, quantity, cost, vendor, reason, customer, type, transaction_balance((type, quantity)::transaction_type) OVER (PARTITION BY item ORDER BY time ASC) AS balance FROM all_transactions WHERE item = $1; $$ LANGUAGE SQL");
query.exec("DROP FUNCTION calculate_inventory_balance()");
query = query.invalidate();
};
/* Asynchronous notifications */
var DBUpdateNotifications = function() {
var query = new QSqlQuery;
query.exec("CREATE OR REPLACE FUNCTION notify_roasting_log_change() RETURNS TRIGGER AS $$ BEGIN NOTIFY RoastingLogChange; RETURN NULL; END; $$ LANGUAGE plpgsql");
query.exec("CREATE TRIGGER notify_roasting_log_change AFTER INSERT OR UPDATE ON roasting_log FOR EACH STATEMENT EXECUTE PROCEDURE notify_roasting_log_change()");
query = query.invalidate();
};
/* Update trigger functions to make column names explicit */
var DBUpdateTriggers = function() {
var query = new QSqlQuery;
query.exec("CREATE OR REPLACE FUNCTION log_make_update() RETURNS trigger AS $$ BEGIN IF NEW.roasted_quantity <> OLD.roasted_quantity AND NEW.roasted_quantity IS NOT NULL THEN INSERT INTO make (time, item, quantity) VALUES(NEW.time, NEW.roasted_id, NEW.roasted_quantity); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql");
query.exec("UPDATE TypicaFeatures SET version = 4 WHERE feature = 'base-features'");
};
var DBUpdateReminders = function() {
var query = new QSqlQuery;
query.exec("CREATE TABLE IF NOT EXISTS reminders (id bigserial PRIMARY KEY NOT NULL, reminder text NOT NULL)");
query.exec("UPDATE TypicaFeatures SET version = 5 WHERE feature = 'base-features'");
query = query.invalidate();
};
var DBUpdateSpecification = function() {
var query = new QSqlQuery;
query.exec("CREATE TABLE IF NOT EXISTS roasting_specification (\"time\" timestamp without time zone NOT NULL, item bigint NOT NULL, loss numeric, tolerance numeric, notes text)");
query.exec("UPDATE TypicaFeatures SET version = 6 WHERE feature = 'base-features'");
query = query.invalidate();
};
/* Updates for Typica version 1.8 */
var DBUpdate18 = function() {
var query = new QSqlQuery;
/* Create a table for Typica users login data */
query.exec("CREATE TABLE IF NOT EXISTS typica_users(name TEXT PRIMARY KEY NOT NULL, password TEXT, active boolean NOT NULL, auto_login boolean NOT NULL)");
/* Update session user logging to only use the database user when a Typica user is not explicitly provided. This maintains compatibility for mixed version use. */
query.exec("CREATE OR REPLACE FUNCTION log_session_user() RETURNS trigger AS $$ BEGIN IF NEW.person IS NULL THEN NEW.person := session_user; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql");
query.exec("CREATE OR REPLACE FUNCTION log_use() RETURNS trigger AS $$ DECLARE i integer := array_lower(NEW.unroasted_id, 1); u integer := array_upper(NEW.unroasted_id, 1); BEGIN IF NEW.transaction_type = 'ROAST' THEN WHILE i <= u LOOP INSERT INTO use (time, item, quantity, person) VALUES(NEW.time, NEW.unroasted_id[i], NEW.unroasted_quantity[i], NEW.person); i := i + 1; END LOOP; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql");
query.exec("CREATE OR REPLACE FUNCTION log_make() RETURNS trigger AS $$ BEGIN IF NEW.roasted_quantity IS NOT NULL THEN INSERT INTO make (time, item, quantity, person) VALUES(NEW.time, NEW.roasted_id, NEW.roasted_quantity, NEW.person); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql");
query.exec("CREATE TABLE IF NOT EXISTS sample_roast_profiles(\"time\" timestamp without time zone NOT NULL, profile_name TEXT NOT NULL, file bigint NOT NULL)");
query.exec("UPDATE TypicaFeatures SET version = 7 WHERE feature = 'base-features'");
query = query.invalidate();
};
/* Updates for Typica version 1.9 */
var DBUpdate19 = function() {
var query = new QSqlQuery;
query.exec("ALTER TABLE roasting_specification ADD COLUMN spec jsonb");
query.exec("ALTER TABLE roasting_log ADD COLUMN additional_data jsonb");
query.exec("CREATE TABLE IF NOT EXISTS scheduled_roasts (id bigserial PRIMARY KEY, machine bigint, \"time\" timestamp without time zone, data jsonb NOT NULL)");
query.exec("CREATE OR REPLACE FUNCTION notify_scheduled_roasts_changed() RETURNS trigger AS $$ BEGIN NOTIFY ScheduledRoastsChange; RETURN NULL; END; $$ LANGUAGE plpgsql");
query.exec("CREATE TRIGGER notify_scheduled_roasts_changed AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE ON scheduled_roasts FOR EACH STATEMENT EXECUTE PROCEDURE notify_scheduled_roasts_changed()");
query.exec("CREATE OR REPLACE FUNCTION notify_transactions_changed() RETURNS trigger AS $$ BEGIN NOTIFY TransactionsChange; RETURN NULL; END; $$ LANGUAGE plpgsql");
query.exec("CREATE TRIGGER notify_transactions_changed AFTER INSERT OR UPDATE OR DELETE ON transactions FOR EACH STATEMENT EXECUTE PROCEDURE notify_transactions_changed()");
query.exec("CREATE OR REPLACE FUNCTION notify_purchase_changed() RETURNS trigger AS $$ BEGIN NOTIFY PurchaseChange; RETURN NULL; END; $$ LANGUAGE plpgsql");
query.exec("CREATE TRIGGER notify_purchase_changed AFTER INSERT OR UPDATE OR DELETE ON purchase FOR EACH STATEMENT EXECUTE PROCEDURE notify_purchase_changed()");
query.exec("CREATE OR REPLACE FUNCTION notify_sale_changed() RETURNS trigger AS $$ BEGIN NOTIFY SaleChange; RETURN NULL; END; $$ LANGUAGE plpgsql");
query.exec("CREATE TRIGGER notify_sale_changed AFTER INSERT OR UPDATE OR DELETE ON sale FOR EACH STATEMENT EXECUTE PROCEDURE notify_sale_changed()");
query.exec("CREATE OR REPLACE FUNCTION notify_invoices_changed() RETURNS trigger AS $$ BEGIN NOTIFY InvoicesChange; RETURN NULL; END; $$ LANGUAGE plpgsql");
query.exec("CREATE TRIGGER nofify_invoices_changed AFTER INSERT OR UPDATE OR DELETE ON invoices FOR EACH STATEMENT EXECUTE PROCEDURE notify_invoices_changed()");
query.exec("CREATE TRIGGER notify_invoices_changed AFTER INSERT OR UPDATE OR DELETE ON invoice_items FOR EACH STATEMENT EXECUTE PROCEDURE notify_invoices_changed()");
query.exec("CREATE TRIGGER notify_transactions_changed AFTER INSERT OR UPDATE OR DELETE ON inventory FOR EACH STATEMENT EXECUTE PROCEDURE notify_transactions_changed()");
query.exec("CREATE TRIGGER notify_transactions_changed AFTER INSERT OR UPDATE OR DELETE ON loss FOR EACH STATEMENT EXECUTE PROCEDURE notify_transactions_changed()");
query.exec("CREATE TRIGGER notify_transactions_changed AFTER INSERT OR UPDATE OR DELETE ON make FOR EACH STATEMENT EXECUTE PROCEDURE notify_transactions_changed()");
query.exec("CREATE TRIGGER notify_transactions_changed AFTER INSERT OR UPDATE OR DELETE ON purchase FOR EACH STATEMENT EXECUTE PROCEDURE notify_transactions_changed()");
query.exec("CREATE TRIGGER notify_transactions_changed AFTER INSERT OR UPDATE OR DELETE ON sale FOR EACH STATEMENT EXECUTE PROCEDURE notify_transactions_changed()");
query.exec("CREATE TRIGGER notify_transactions_changed AFTER INSERT OR UPDATE OR DELETE ON use FOR EACH STATEMENT EXECUTE PROCEDURE notify_transactions_changed()");
query.exec("CREATE OR REPLACE FUNCTION notify_reminders_changed() RETURNS trigger AS $$ BEGIN NOTIFY RemindersChange; RETURN NULL; END; $$ LANGUAGE plpgsql");
query.exec("CREATE TRIGGER notify_reminders_changed AFTER INSERT OR UPDATE OR DELETE ON reminders FOR EACH STATEMENT EXECUTE PROCEDURE notify_reminders_changed()");
query.exec("UPDATE TypicaFeatures SET version = 8 WHERE feature = 'base-features'");
query = query.invalidate();
};
if(Application.databaseConnected()) {
query = new QSqlQuery();
/* A table keeps track of database versioning information. This
table is created
if required. */
query.exec("CREATE TABLE IF NOT EXISTS TypicaFeatures (feature TEXT PRIMARY KEY, enabled boolean, version bigint)");
query.exec("SELECT feature, enabled, version FROM TypicaFeatures WHERE feature = 'base-features'");
if(query.next())
{
if(query.value(2) < 1)
{
DBCreateBase();
}
if(query.value(2) < 2)
{
DBUpdateMultiUser();
DBUpdateHistory();
}
if(query.value(2) < 3)
{
DBUpdateNotifications();
}
if(query.value(2) < 4)
{
DBUpdateTriggers();
}
if(query.value(2) < 5)
{
DBUpdateReminders();
}
if(query.value(2) < 6)
{
DBUpdateSpecification();
}
if(query.value(2) < 7) {
DBUpdate18();
}
if(query.value(2) < 8) {
DBUpdate19();
}
}
else
{
DBCreateBase();
DBUpdateMultiUser();
DBUpdateHistory();
DBUpdateNotifications();
DBUpdateTriggers();
DBUpdateReminders();
DBUpdateSpecification();
DBUpdate18();
DBUpdate19();
}
query.exec("SELECT feature, enabled, version FROM TypicaFeatures WHERE feature = 'sample-roasting'");
if(query.next())
{
if(query.value(2) < 1)
{
DBCreateSampleRoasting();
}
}
else
{
DBCreateSampleRoasting();
}
var promptNewUsers = true;
query.exec("SELECT count(1) FROM typica_users");
if(query.next()) {
if(Number(query.value(0)) > 0) {
promptNewUsers = false;
}
}
if(promptNewUsers) {
var newUserDialog = new NewTypicaUser();
newUserDialog.exec();
}
if(!Application.autoLogin()) {
var loginDialog = new LoginDialog();
loginDialog.exec();
}
query = query.invalidate();
}
var switchuser = findChildObject(this, 'switchuser');
switchuser.triggered.connect(function() {
var loginDialog = new LoginDialog();
loginDialog.exec();
});
var createuser = findChildObject(this, 'createuser');
createuser.triggered.connect(function() {
var newUserDialog = new NewTypicaUser();
newUserDialog.exec();
});
var dashboard = findChildObject(this, 'dashboard');
var refresh = function() {
var buffer = new QBuffer;
buffer.open(3);
var output = new XmlWriter(buffer);
output.writeStartDocument("1.0");
output.writeDTD('');
output.writeStartElement("html");
output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
output.writeStartElement("head");
output.writeStartElement("link");
output.writeAttribute("rel", "stylesheet");
output.writeAttribute("href", QSettings.value("config") + "/Scripts/dashboard.css");
output.writeEndElement();
output.writeEndElement(); // head
output.writeStartElement("body");
output.writeStartElement("div");
output.writeAttribute("class", "container");
var query = new QSqlQuery;
drawReminders(output, query);
drawSchedule(output, query);
drawProductionTrends(output, query);
drawLatest(output, query);
drawRecentlyOut(output, query);
drawLeastAvailable(output, query);
drawUnused(output, query);
drawMostRoasted(output, query);
drawLeastRoasted(output, query);
query = query.invalidate();
output.writeEndElement(); // End of container
output.writeEndElement(); // body
output.writeEndElement(); // html
output.writeEndDocument();
dashboard.setContent(buffer);
buffer.close();
}
var startCell = function(output, title) {
output.writeStartElement("div");
output.writeAttribute("class", "cell");
output.writeStartElement("div");
output.writeAttribute("class", "cell-wrapper");
output.writeStartElement("div");
output.writeAttribute("class", "cell-title");
output.writeCharacters(title);
output.writeEndElement();
}
var startStage = function(output) {
output.writeStartElement("div");
output.writeAttribute("class", "cell-stage");
}
var endStage = function(output) {
output.writeEndElement();
}
var endCell = function(output, summary) {
if(arguments.length > 1) {
output.writeStartElement("div");
output.writeAttribute("class", "cell-notes");
output.writeCharacters(summary);
output.writeEndElement();
}
output.writeEndElement();
output.writeEndElement();
}
var drawReminders = function(output, query) {
query.exec("SELECT id, reminder FROM reminders");
e = new Array();
while(query.next()) {
var reminder = JSON.parse(query.value(1));
reminder.dbid = query.value(0);
var start_time = "" + reminder.start_year + "-" + reminder.start_month + "-" + reminder.start_day + " " + reminder.start_time;
if(reminder.condition == "PRODUCTIONWEIGHT") {
var convert = 1;
var unittext = TTR("navwindow", " Lb");
if(reminder.unit == "KG") {
convert = 2.2;
unittext = TTR("navwindow", " Kg");
}
var dq = new QSqlQuery;
dq.prepare("SELECT sum(roasted_quantity)/:conversion FROM roasting_log WHERE time > :since");
dq.bind(":conversion", convert);
dq.bind(":since", start_time);
dq.exec();
dq.next();
var proportion;
var remain;
if(reminder.value == 0 || (reminder.value < Number(dq.value(0)))) {
proportion = 1;
} else {
proportion = Number(dq.value(0)) / reminder.value;
}
remain = (reminder.value - Number(dq.value(0))).toFixed(0);
reminder.completion = proportion;
reminder.detail = remain + unittext;
dq = dq.invalidate();
} else if (reminder.condition == "DAYS") {
var dq = new QSqlQuery;
dq.prepare("SELECT 'now'::date - :since::date");
dq.bind(":since", start_time);
dq.exec();
dq.next();
var proportion;
var remain;
if(reminder.value == 0 || (reminder.value < Number(dq.value(0)))) {
proportion = 1;
} else {
proportion = Number(dq.value(0)) / reminder.value;
}
remain = reminder.value - Number(dq.value(0));
reminder.completion = proportion;
reminder.detail = remain + TTR("navwindow", " Days");
dq = dq.invalidate();
} else if (reminder.condition == "PRODUCTIONBATCHES") {
var dq = new QSqlQuery;
dq.prepare("SELECT count(1) FROM roasting_log WHERE time > :since");
dq.bind(":since", start_time);
dq.exec();
dq.next();
var proportion;
var remain;
if(reminder.value == 0 || (reminder.value < Number(dq.value(0)))) {
proportion = 1;
} else {
proportion = Number(dq.value(0)) / reminder.value;
}
remain = reminder.value - Number(dq.value(0));
reminder.completion = proportion;
reminder.detail = remain + TTR("navwindow", " Batches");
dq = dq.invalidate();
} else if (reminder.condition == "PRODUCTIONHOURS") {
var dq = new QSqlQuery;
dq.prepare("SELECT extract(epoch FROM (SELECT sum(duration) FROM roasting_log WHERE time > :since) / 3600)");
dq.bind(":since", start_time);
dq.exec();
dq.next();
var proportion;
var remain;
if(reminder.value == 0 || (reminder.value < Number(dq.value(0)))) {
proportion = 1;
} else {
proportion = Number(dq.value(0)) / reminder.value;
}
remain = reminder.value - Number(dq.value(0));
reminder.completion = proportion;
reminder.detail = remain.toFixed(1) + TTR("navwindow", " Hours");
dq = dq.invalidate();
}
e[reminder.dbid] = reminder;
}
var s = e.filter(function(n) {
return n.hasOwnProperty("completion")}).sort(function(a, b) {
return b.completion - a.completion});
var c = 0;
var so = 0;
s.forEach(function(item) {
if(item.completion >= 1) {
c += 1;
} else if (item.completion >= 0.8) {
so += 1;
}
});
if(c > 0 || s > 0) {
output.writeStartElement("a");
output.writeAttribute("href", "typica://script/reminders");
startCell(output, TTR("navwindow", "Reminders"));
startStage(output);
var summaryText;
if(c > 0) {
summaryText = "" + c + TTR("navwindow", " reminders due");
} else {
summaryText = "" + s + TTR("navwindow", " reminders due soon");
}
output.writeTextElement("p", s[0].title);
output.writeTextElement("p", Math.floor(s[0].completion * 100) + "%");
output.writeTextElement("p", s[0].detail);
endStage(output);
endCell(output, summaryText);
output.writeEndElement();
}
}
var kilounit = TTR("navwindow", "Kg");
var poundunit = TTR("navwindow", "Lb");
var unitData = function() {
var retval = new Object;
if(Number(QSettings.value("script/report_unit")) == 0) {
retval.conversion = 2.2;
retval.unittext = "Kg";
} else {
retval.conversion = 1;
retval.unittext = "Lb";
}
return retval;
}
var drawSchedule = function(output, query) {
var c = 0;
var u = unitData();
query.prepare("SELECT (SELECT name FROM items WHERE id = (data#>>'{roasted}')::numeric), (data#>>'{green_weight}')::numeric/:conversion FROM scheduled_roasts WHERE machine IS NULL");
query.bind(":conversion", u.conversion);
query.exec();
if(query.next()) {
output.writeStartElement("a");
output.writeAttribute("href", "typica://script/schedule");
startCell(output, TTR("navwindow", "Scheduled Roasts"));
startStage(output);
do {
c += 1;
output.writeTextElement("p", query.value(1) + u.unittext + " " + query.value(0));
} while(query.next());
endStage(output);
endCell(output, "" + c + TTR("navwindow", " batches scheduled"));
output.writeEndElement();
}
}
var drawLatest = function(output, query) {
var u = unitData();
query.prepare("SELECT time, roasted_quantity/:conversion, (SELECT name FROM items WHERE id = roasted_id), approval FROM roasting_log ORDER BY time DESC LIMIT 5");
query.bind(":conversion", u.conversion);
query.exec();
if(query.next()) {
output.writeStartElement("a");
output.writeAttribute("href", "typica://script/log");
startCell(output, TTR("navwindow", "Latest Batches"));
startStage(output);
do {
output.writeStartElement("p");
if(query.value(3) == "false") {
output.writeAttribute("style", "color: #FF0000;");
}
output.writeCharacters(query.value(0).replace("T", " ") + " " + query.value(1) + u.unittext + " " + query.value(2));
output.writeEndElement();
} while(query.next());
endStage(output);
endCell(output);
output.writeEndElement();
}
}
var drawMostRoasted = function(output, query) {
var u = unitData();
query.prepare("SELECT (sum(roasted_quantity)/:conversion) AS sum, (SELECT name FROM items WHERE id = roasted_id) FROM roasting_log WHERE time > 'now'::date - '28 days'::interval AND approval = true GROUP BY roasted_id ORDER BY sum DESC LIMIT 5");
query.bind(":conversion", u.conversion);
query.exec();
if(query.next()) {
startCell(output, TTR("navwindow", "Most Roasted Coffees (Last 28 Days)"));
startStage(output);
do {
output.writeTextElement("p", query.value(0) + u.unittext + " " + query.value(1));
} while(query.next());
endStage(output);
endCell(output);
}
}
var drawLeastRoasted = function(output, query) {
var u = unitData();
query.prepare("SELECT (sum(roasted_quantity)/:conversion) AS sum, (SELECT name FROM items WHERE id = roasted_id) FROM roasting_log WHERE time > 'now'::date - '28 days'::interval AND approval = true GROUP BY roasted_id ORDER BY sum ASC LIMIT 5");
query.bind(":conversion", u.conversion);
query.exec();
if(query.next()) {
startCell(output, TTR("navwindow", "Least Roasted Coffees (Last 28 Days)"));
startStage(output);
do {
output.writeTextElement("p", query.value(0) + u.unittext + " " + query.value(1));
} while(query.next());
endStage(output);
endCell(output);
}
}
var drawLeastAvailable = function(output, query) {
var u = unitData();
query.prepare("SELECT name, (quantity / :conversion)::numeric(12,2) AS quantity, (SELECT out FROM coffee_history WHERE coffee_history.id = items.id) AS out FROM items WHERE quantity > 0 ORDER BY out ASC LIMIT 5");
query.bind(":conversion", u.conversion);
query.exec();
if(query.next()) {
startCell(output, TTR("navwindow", "Least Available Coffes"));
startStage(output);
output.writeStartElement("table");
do {
output.writeStartElement("tr");
output.writeTextElement("td", query.value(1) + u.unittext);
output.writeTextElement("td", query.value(0));
output.writeTextElement("td", query.value(2));
output.writeEndElement();
} while(query.next());
output.writeEndElement();
endStage(output);
endCell(output);
}
}
var drawRecentlyOut = function(output, query) {
query.exec("SELECT (SELECT name FROM items WHERE id = item), max(time)::date AS last_transaction FROM transactions WHERE item IN (SELECT id FROM items WHERE quantity = 0) GROUP BY item ORDER BY last_transaction DESC LIMIT 5");
if(query.next()) {
startCell(output, TTR("navwindow", "Latest Out of Stock Coffees"));
startStage(output);
output.writeStartElement("table");
do {
output.writeStartElement("tr");
output.writeTextElement("td", query.value(1));
output.writeTextElement("td", query.value(0));
output.writeEndElement();
} while(query.next());
output.writeEndElement();
endStage(output);
endCell(output);
}
}
var drawUnused = function(output, query) {
query.exec("SELECT name FROM coffees WHERE id NOT IN (SELECT item FROM use) AND quantity > 0");
var c = 0;
if(query.next()) {
startCell(output, TTR("navwindow", "Unused Coffees"));
startStage(output);
do {
c += 1;
output.writeTextElement("p", query.value(0));
} while(query.next());
endStage(output);
endCell(output, "" + c + TTR("navwindow", " unused coffees"));
}
}
var calculateChange = function(current, previous) {
var difference = Number(current) - Number(previous);
var retval;
if(difference > 0) {
retval = "+";
} else if(difference < 0) {
retval = "-";
}
retval += ((difference/Number(previous)) * 100).toFixed(1);
retval += "%";
return retval;
}
var outputChange = function(output, value) {
output.writeStartElement("td");
if(value[0] == '+') {
output.writeAttribute("style", "color: #008B00");
} else if(value[0] == '-') {
output.writeAttribute("style", "color: #8B0000");
}
output.writeCharacters(value);
output.writeEndElement();
}
var outputChangeRow = function(output, query, p1, p2) {
var u = unitData();
query.prepare("SELECT sum(roasted_quantity)/:conversion FROM roasting_log WHERE approval = true AND time > 'now'::date - '" + p1 + " days'::interval");
query.bind(":conversion", u.conversion);
query.exec();
query.next();
var current = Number(query.value(0));
output.writeTextElement("td", "" + current.toFixed(0) + u.unittext);
query.prepare("SELECT sum(roasted_quantity)/:conversion FROM roasting_log WHERE approval = true AND time > 'now'::date - '" + p2 + " days'::interval AND time < 'now'::date - '" + p1 + " days'::interval");
query.bind(":conversion", u.conversion);
query.exec();
query.next();
var previous = Number(query.value(0));
output.writeTextElement("td", "" + previous.toFixed(0) + u.unittext);
outputChange(output, calculateChange(current, previous));
}
var drawProductionTrends= function(output, query) {
startCell(output, TTR("navwindow", "Production Trends"));
startStage(output);
output.writeStartElement("table");
output.writeStartElement("tr");
output.writeTextElement("th", TTR("navwindow", "Period"));
output.writeTextElement("th", TTR("navwindow", "Production"));
output.writeTextElement("th", TTR("navwindow", "Previous"));
output.writeTextElement("th", TTR("navwindow", "Change"));
output.writeEndElement();
output.writeStartElement("tr");
output.writeTextElement("th", TTR("navwindow", "Today"));
outputChangeRow(output, query, 0, 1);
output.writeEndElement();
output.writeStartElement("tr");
output.writeTextElement("th", TTR("navwindow", "Last 7 Days"));
outputChangeRow(output, query, 6, 13);
output.writeEndElement();
output.writeStartElement("tr");
output.writeTextElement("th", TTR("navwindow", "Last 30 Days"));
outputChangeRow(output, query, 29, 59);
output.writeEndElement();
output.writeStartElement("tr");
output.writeTextElement("th", TTR("navwindow", "Last 90 Days"));
outputChangeRow(output, query, 89, 179);
output.writeEndElement();
output.writeStartElement("tr");
output.writeTextElement("th", TTR("navwindow", "Last 365 Days"));
outputChangeRow(output, query, 364, 729);
output.writeEndElement();
output.writeEndElement();
endStage(output);
endCell(output);
}
refresh();
dashboard.scriptLinkClicked.connect(function(url) {
if(url == "reminders") {
createReport("reminders.xml");
} else if(url == "log") {
createReport("historyreport.xml");
} else if(url == "schedule") {
createWindow("schedule");
}
});
var remindernotification = Application.subscribe("reminderschange");
remindernotification.notify.connect(function() {
refresh();
});
var schedulenotification = Application.subscribe("scheduledroastschange");
schedulenotification.notify.connect(function() {
refresh();
});
var lognotification = Application.subscribe("transactionschange");
lognotification.notify.connect(function() {
refresh();
});
]]>