From 2fc74c06778ce1aec0a8058387ceb0b2e0bb5a5f Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Wed, 16 Sep 2026 19:07:10 +0200 Subject: [PATCH] Fixes for webgui and more native gui development --- INSTALL | 12 +- db/mariadb.sql | 3 +- db/minimal.sql | 5 +- db/minimal_en.sql | 5 +- debian/postinst | 10 + debian/postrm | 13 + debian/templates | 9 + webgui/config.inc-sample | 5 +- webgui/globals.inc | 13 +- webgui/inventory.php | 11 +- webgui/lib/signalk.inc | 31 ++ webgui/storage.php | 1 + yms/combobox.py | 2 + yms/lookup.py | 130 +++++ yms/model/box.py | 2 +- yms/model/vessel.py | 12 +- yms/treeview.py | 2 + yms/ui/base.ui | 1125 +++++++++++++++++++++++++------------- yms/ui/boxes.ui | 844 ++++++++++++++++++++++++++++ yms/ui/storage.ui | 111 +++- yms/view/base.py | 35 +- yms/view/boxes.py | 94 ++++ yms/view/cables.py | 6 + yms/view/documents.py | 6 + yms/view/fuses.py | 6 + yms/view/maintenance.py | 6 + yms/view/projects.py | 6 + yms/view/status.py | 2 +- yms/view/storage.py | 19 +- yms/view/view.py | 12 +- ymsgui.py | 22 +- 31 files changed, 2143 insertions(+), 417 deletions(-) create mode 100644 debian/postrm create mode 100644 webgui/lib/signalk.inc create mode 100644 yms/lookup.py create mode 100644 yms/ui/boxes.ui create mode 100644 yms/view/boxes.py diff --git a/INSTALL b/INSTALL index dc67059..72c916d 100644 --- a/INSTALL +++ b/INSTALL @@ -62,10 +62,10 @@ Optional import some sample data 4. Initialize document storage Customize path and access rights to your needs - mkdir /var/local/yms - mkdir /var/local/yms/thumb - chown -R www-data /var/local/yms - chmod -R 750 /var/local/yms + mkdir /var/local/lib/yms + mkdir /var/local/lib/yms/thumb + chown -R www-data /var/local/lib/yms + chmod -R 750 /var/local/lib/yms To grant access for the local gui the directory has to be writeable by a special group (here as example 'yms'). The user which runs the local gui (here @@ -75,8 +75,8 @@ group (here as example 'yms'). The user which runs the local gui (here groupadd yms usermod -aG yms www-data usermod -aG yms guiuser - chgrp yms /var/local/yms - chgrp yms /var/local/yms/thumb + chgrp yms /var/local/lib/yms + chgrp yms /var/local/lib/yms/thumb chmod -R 770 /var/local/yms 4. Setup local GUI diff --git a/db/mariadb.sql b/db/mariadb.sql index 7f3bd60..b9ea87c 100644 --- a/db/mariadb.sql +++ b/db/mariadb.sql @@ -222,6 +222,7 @@ CREATE TABLE equipment ( CREATE TABLE inventory ( invid int(10) UNSIGNED NOT NULL AUTO_INCREMENT, invname varchar(80) NOT NULL, + icat tinyint(3) DEFAULT NULL, conttype enum('storage','box') NOT NULL DEFAULT 'storage', sid smallint(6) UNSIGNED DEFAULT NULL, boxid smallint(6) UNSIGNED DEFAULT NULL, @@ -470,7 +471,7 @@ CREATE TABLE note ( annotation text NOT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by smallint(6) DEFAULT NULL, - PRIMARY KEY (noteid) + PRIMARY KEY (noteid), FOREIGN KEY fk_note_user (created_by) REFERENCES user(userid) ON DELETE RESTRICT diff --git a/db/minimal.sql b/db/minimal.sql index a24f5ee..9a13a8a 100644 --- a/db/minimal.sql +++ b/db/minimal.sql @@ -14,7 +14,7 @@ INSERT INTO user (userid, login, pass, displayname, role) VALUES INSERT INTO settings (userid, sno, valint, valstr) VALUES (0, 0, 1, NULL), -- Database schema version (0, 1, 0, NULL), -- No vessel assignment to SYSTEM -(1, 1, 0), NULL, -- No vessel assignment to user "captain" +(1, 1, 0, NULL), -- No vessel assignment to user "captain" (0, 20, 1280, NULL), -- Image width limit (0, 21, 1024, NULL), -- Image height limit (0, 4, NULL, '€'), -- Currency symbol @@ -72,7 +72,8 @@ INSERT INTO dropdown (ddid, ddval, ddtext) VALUES (0, 9, 'Kistentypen'), (0, 10, 'Sicherungsblöcke'), -- fuse locations (0, 11, 'Kabeltypen'), -(0, 12, 'Technische Kabel-Bezeichnung'); +(0, 12, 'Technische Kabel-Bezeichnung'), +(0, 13, 'Inventarkategorien'); INSERT INTO dropdown (ddid, ddval, ddtext, flags) VALUES (2, 1, 'Lieferant', 'fixed'), diff --git a/db/minimal_en.sql b/db/minimal_en.sql index 8a3f0c1..85dd6b4 100644 --- a/db/minimal_en.sql +++ b/db/minimal_en.sql @@ -14,7 +14,7 @@ INSERT INTO user (userid, login, pass, displayname, role) VALUES INSERT INTO settings (userid, sno, valint, valstr) VALUES (0, 0, 1, NULL), -- Database schema version (0, 1, 0, NULL), -- No vessel assignment to SYSTEM -(1, 1, 0), NULL, -- No vessel assignment to user "captain" +(1, 1, 0, NULL), -- No vessel assignment to user "captain" (0, 20, 1280, NULL), -- Image width limit (0, 21, 1024, NULL), -- Image height limit (0, 4, NULL, '€'), -- Currency symbol @@ -71,7 +71,8 @@ INSERT INTO dropdown (ddid, ddval, ddtext) VALUES (0, 9, 'box types'), (0, 10, 'fuse blocks'), (0, 11, 'cable types'), -(0, 12, 'cable desc. technical'); +(0, 12, 'cable desc. technical'), +(0, 13, 'inventory category'); INSERT INTO dropdown (ddid, ddval, ddtext, flags) VALUES (2, 1, 'Supplier', 'fixed'), diff --git a/debian/postinst b/debian/postinst index 2e90c36..709ef4c 100755 --- a/debian/postinst +++ b/debian/postinst @@ -52,6 +52,16 @@ else mariadb -e "GRANT SELECT, INSERT, UPDATE, DELETE ON \`${DB_NAME}\`.* TO '${DB_USER}'@'localhost'" fi +echo "Initializing document storage..." +if ! getent group yms >/dev/null; then + addgroup --system yms +fi +if ! id -nG www-data | tr ' ' '\n' | grep -qx yms; then + adduser www-data yms +fi +mkdir -p /var/lib/yms/thumb +chown -R www-data:yms /var/lib/yms + db_get yms/webgui if [ "$RET" = "true" ]; then diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..54fbf44 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,13 @@ +#!/bin/sh +set -e + +. /usr/share/debconf/confmodule + +case "$1" in + remove|purge) + db_input high yms/remove-info || true + db_go || true + ;; +esac + +exit 0 diff --git a/debian/templates b/debian/templates index 2675e96..5359418 100644 --- a/debian/templates +++ b/debian/templates @@ -23,3 +23,12 @@ Description: Enable the YMS web interface? by a web server. . Do you want to enable the web interface? + +Template: yms/remove-info +Type: note +Description: YMS document storage was not removed + The YMS package has been removed, but the document storage + remains on the system. + . + The directory /var/lib/yms and its contents have not been + deleted. diff --git a/webgui/config.inc-sample b/webgui/config.inc-sample index 834b23b..1e9afd6 100644 --- a/webgui/config.inc-sample +++ b/webgui/config.inc-sample @@ -29,13 +29,13 @@ $g_mailto_webmaster = 'webmaster@example.com'; // localization $g_textdomain = 'yms'; $g_timezone = 'Europe/Berlin'; -$g_locale = 'de_DE.UTF-8'; +$g_locale = 'en_GB.UTF-8'; // vessel base shapes directory $g_shapedir = '/usr/local/share/yms/shapes'; // documents and images -$g_doc_basepath = '/var/local/yms'; +$g_doc_basepath = '/var/local/lib/yms'; $g_doc_maxsize = 1024*1024*16; // 16 MB, please adjust php.ini accordingly! $g_doc_typemap = array( 'generic' => 'doc', @@ -50,4 +50,3 @@ $g_storage_limit = 1024; // MB $g_thumb_size = 120; $g_rows_pp = 10; // pagination limit - diff --git a/webgui/globals.inc b/webgui/globals.inc index 471fb3a..bb506f9 100644 --- a/webgui/globals.inc +++ b/webgui/globals.inc @@ -136,11 +136,14 @@ $g_currsym = $sth->fetchColumn(); // allowed date formats $g_opt_datefmt = array( - 'Y-m-d' => _('YYYY-MM-DD'), - 'Y/m/d' => _('YYYY/MM/DD'), - 'd.m.Y' => _('DD.MM.YYYY'), - 'd/m/Y' => _('DD/MM/YYYY'), - 'm-d-Y' => _('MM-DD-YYYY') + 'Y-m-d' => _('YYYY-MM-DD'), // ISO / international /technical + 'd.m.Y' => _('DD.MM.YYYY'), // Germany / Central & Northern Europe + 'd/m/Y' => _('DD/MM/YYYY'), // UK / France / Spain / Italy / Australia / NZ + 'd-m-Y' => _('DD-MM-YYYY'), // Netherlands / Denmark / Belgium + 'd M Y' => _('DD Mon YYYY'), // nautical documents / logbooks / unambiguous + 'm.d.Y' => _('MM.DD.YYYY'), // USA + 'm/d/Y' => _('MM/DD/YYYY'), // USA + 'Y/m/d' => _('YYYY/MM/DD') // Japan / China ); $user = User::GetInstance(); // There can be only one diff --git a/webgui/inventory.php b/webgui/inventory.php index 1a4e7c9..323f377 100644 --- a/webgui/inventory.php +++ b/webgui/inventory.php @@ -18,6 +18,7 @@ $opt_storage = db_get_opt_storage($user->vid); $opt_box = db_get_opt_box($user->vid); $opt_invcond = db_load_enum('inventory', 'invcond', true, true); $opt_equipment = db_get_opt_equip($user->vid, [-1 => _('― none ―')]); +$opt_icat = db_get_options(13); // ========== ACTIONS START =================================================== @@ -95,6 +96,8 @@ switch ($submit = form_get_action()) { $p[':price'] = gpc_get_currency($_POST, 'price'); $p[':purchdate'] = gpc_get_date($_POST, 'purchdate'); $p[':invcond'] = gpc_get_string($_POST, 'invcond'); + $p[':icat'] = gpc_get_int($_POST, 'icat'); + if ($p[':icat'] <= 0) $p[':icat'] = NULL; $p[':remarks'] = gpc_get_string($_POST, 'remarks', 150); $p[':eid'] = gpc_get_int($_POST, 'eid'); if ($p[':eid'] <= 0) $p[':eid'] = NULL; @@ -343,7 +346,7 @@ elseif ($action == ACT_VIEW): // ========== VARIANT: view single record ===================================== $sql = "SELECT invname, conttype, sid, boxid, number, weight, price, purchdate," - . " invcond, remarks " + . " invcond, icat, remarks " . "FROM inventory " . "WHERE invid=?"; $sth = $pdo->prepare($sql); @@ -397,6 +400,7 @@ echo "", _('Weight'), "", format_float($inventory->weight, 2, ' echo "", _('Price'), "", format_currency($inventory->price), "\n"; echo "", _('Purchase date'), "", format_date($inventory->purchdate), "\n"; echo "", _('Condition'), "", $opt_invcond[$inventory->invcond], "\n"; +echo "", _('Category'), "", $opt_icat[$inventory->icat], "\n"; echo "", _('Remarks'), "", h($inventory->remarks, br:true), "\n"; $tags = db_load_taglist('inv', $id); @@ -462,7 +466,10 @@ foreach ($cond as $k => $v) { ?> -eid); ?> +icat); +form_create_select('eid', _('Equipment'), $opt_equipment, $inventory->eid); +?>
diff --git a/webgui/lib/signalk.inc b/webgui/lib/signalk.inc new file mode 100644 index 0000000..3563755 --- /dev/null +++ b/webgui/lib/signalk.inc @@ -0,0 +1,31 @@ + true, + CURLOPT_CONNECTTIMEOUT => 3, + CURLOPT_TIMEOUT => 5, + ]); + $json = curl_exec($ch); + if ($json === false) { + throw new RuntimeException('Signal K error: ' . curl_error($ch)); + } + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($httpCode !== 200) { + throw new RuntimeException('Signal K HTTP-error: ' . $httpCode); + } + return json_decode($json, true, 512, JSON_THROW_ON_ERROR); +} diff --git a/webgui/storage.php b/webgui/storage.php index a42e26b..637b693 100644 --- a/webgui/storage.php +++ b/webgui/storage.php @@ -291,6 +291,7 @@ if (count($res) > 0) { echo "", $row['provname'], ""; echo "", $row['number'], ' ', $opt_unit[$row['unit']], ""; echo "", $row['storedate'], ' - ', $row['shelflife'], ""; + echo ''; echo "\n"; } echo "\n"; diff --git a/yms/combobox.py b/yms/combobox.py index b705f1e..5d8100b 100644 --- a/yms/combobox.py +++ b/yms/combobox.py @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: WTFPL + from gi.repository import Gtk def load_from_dict(combobox, dictionary, selected=None, sort=False): diff --git a/yms/lookup.py b/yms/lookup.py new file mode 100644 index 0000000..e97ec5a --- /dev/null +++ b/yms/lookup.py @@ -0,0 +1,130 @@ +# SPDX-License-Identifier: WTFPL + +""" +Dropdowns and database enums + + dropdowns can change + enums are static + +""" + +class Dropdown: + HULL_TYPE = 1 + COMPANY_TYPE = 2 + STORAGE_TYPE = 3 + EQUIPMENT_CATEGORY = 4 + PROVISION_CATEGORY = 5 + PROVISION_UNIT = 6 + FUSE_TYPE = 7 + UNIT = 8 + BOX_TYPE = 9 + FUSE_BLOCK = 10 + CABLE_TYPE = 11 + CABLE_TECHNAME = 12 + +class Lookup: + + def __init__(self, db): + self.db = db + self._enums = {} + self._dropdowns = {} + + def load(self): + self._load_enums() + self._load_dropdowns() + + def _load_enums(self): + """ + TODO translation via gettext needed + """ + db_enums = { + 'user.role': ('user', 'role'), + 'dropdown.flags': ('dropdown', 'flags'), + 'vessel.vtype': ('vessel', 'vtype'), + 'storage.capaunit': ('storage', 'capaunit'), + 'tagref.objtype': ('tagref', 'objtype'), + 'inventory.conttype': ('inventory', 'conttype'), + 'inventory.invcond': ('inventory', 'invcond'), + 'cable.cablecond': ('cable', 'cablecond'), + 'measurement.accuracy': ('measurement', 'accuracy'), + 'document.doctype': ('document', 'doctype'), + 'document.roleaccess': ('document', 'roleaccess'), + 'docref.reftype': ('docref', 'reftype'), + 'maintenance.maintstate': ('maintenance', 'maintstate'), + 'project.projstate': ('project', 'projstate'), + 'task.priority': ('task', 'priority'), + 'task.taskstate': ('task', 'taskstate'), + 'note.notetype': ('note', 'notetype'), + 'checklist.clstate': ('checklist', 'clstate'), + 'checkitem.checkresult': ('checkitem', 'checkresult'), + 'signalk.objtype': ('signalk', 'objtype'), + 'signalk.datatype': ('signalk', 'datatype') + } + for key, (table, column) in db_enums.items(): + self._enums[key] = self.db.load_enum(table, column) + + def _load_dropdowns(self): + self._dropdowns.clear() + sql = ( + "SELECT ddid, ddval, ddtext " + "FROM dropdown " + "WHERE ddid>0 " + "ORDER BY ddid, sort, ddval" + ) + self.db.cur.execute(sql) + for ddid, ddval, ddtext in self.db.cur.fetchall(): + if ddid not in self._dropdowns: + self._dropdowns[ddid] = {} + self._dropdowns[ddid][ddval] = ddtext + + def enum(self, name): + return self._enums[name] + + def dropdown(self, ddid): + return self._dropdowns[ddid] + + def refresh(self): + # TODO ugly hack please improve + self._load_dropdowns() + +class ListDefinition: + pass + +""" +EQUIPMENT_LIST = ListDefinition( + name="equipment", + + query= + "SELECT + e.eid, + e.ename, + c.name AS manufacturer, + e.model, + e.ecat, + e.weight + FROM equipment e + LEFT JOIN company c + ON c.cid = e.manufacturer" + , + + columns=[ + Column("ename", _("Name")), + Column("manufacturer", _("Manufacturer")), + Column( + "model", + _("Model") + ), + Column( + "ecat", + _("Category"), + formatter=dropdown(Dropdown.EQUIPMENT_CATEGORY) + ), + Column( + "weight", + _("Weight"), + formatter=weight() + ), + ] +) + +""" diff --git a/yms/model/box.py b/yms/model/box.py index bfe0180..2c3cf7c 100644 --- a/yms/model/box.py +++ b/yms/model/box.py @@ -7,7 +7,7 @@ class Box(DbRecord): table = "box" pk_field = "boxid" fields = ('boxid', 'sid', 'boxtype', 'parent', 'label', 'color', 'content', - 'weight', 'remarks') + 'wx', 'wy', 'wz', 'weight', 'weight_net', 'price', 'remarks') def __init__(self, db, boxid=None): super().__init__(db, boxid) diff --git a/yms/model/vessel.py b/yms/model/vessel.py index 11add82..36e92ea 100644 --- a/yms/model/vessel.py +++ b/yms/model/vessel.py @@ -22,14 +22,24 @@ class Vessel(DbRecord): "WHERE v1.vid>=0 AND v2.vid IS NULL" ) self.dcur.execute(sql) - row = self.cur.fetchone() + row = self.dcur.fetchone() if row is None: # vessel table empty: create first vessel self.pk_value = 1 else: # create additional vessel self.pk_value = row['newid'] + self.clear() + self.vid = self.pk_value self.vesselname = f"Vessel #{self.pk_value}" self.vtype = 1 # monohull self._insert() self.conn.commit() + + def save(self): + super().save() + self.conn.commit() + + def delete(self): + # there must be no assiciated data! + pass diff --git a/yms/treeview.py b/yms/treeview.py index baa9a1c..056a86f 100644 --- a/yms/treeview.py +++ b/yms/treeview.py @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: WTFPL + from gi.repository import Gtk def add_column(treeview, label, index, xalign=None, col_fg=None, col_bg=None, visible=True): diff --git a/yms/ui/base.ui b/yms/ui/base.ui index 8501b00..2b23c80 100644 --- a/yms/ui/base.ui +++ b/yms/ui/base.ui @@ -51,399 +51,790 @@ True True - + True False - vertical - + True False - empty - - - False - True - 0 - - - - - True - False - 4 - 4 - - - - False - True - 1 - - - - - - True - False - 4 - 4 - 4 - 8 + vertical True False - Name + Ghost - 0 - 0 - - - - - True - True - 30 - 30 - 20 - alpha - - - 1 - 0 - - - - - True - False - Model - - - 0 - 1 - - - - - True - True - - - 1 - 1 - - - - - True - False - LoA - - - 0 - 3 - - - - - True - False - LWL - - - 0 - 4 - - - - - True - False - Beam - - - 0 - 5 - - - - - True - False - Draught - - - 0 - 6 - - - - - True - False - Draught, min - - - 0 - 7 - - - - - True - True - adj_loa - 2 - - - 1 - 3 - - - - - True - True - adj_lwl - 2 - - - 1 - 4 - - - - - True - True - adj_beam - 2 - - - 1 - 5 - - - - - True - True - adj_draught - 2 - - - 1 - 6 - - - - - True - True - adj_draught_min - 2 - - - 1 - 7 - - - - - True - False - Displacement - - - 0 - 9 - - - - - True - False - Ballast - - - 0 - 10 - - - - - True - True - adj_displacement - - - 1 - 9 - - - - - True - True - adj_ballast - - - 1 - 10 - - - - - True - False - Remarks - - - 0 - 12 - - - - - True - False - - - 1 - 11 - - - - - True - False - Default Storage - - - 0 - 11 - - - - - True - False - Shipyard - - - 0 - 8 - - - - - True - False - - - 1 - 8 - - - - - True - True - in - - - True - True - True - natural - natural - word - 1 - 1 - 1 - 1 - - - - - - - 1 - 12 - - - - - True - False - Year built - - - 0 - 2 - - - - - True - True - 0 - adj_year_built - - - 1 - 2 - - - - - True - True - 4 - 2 - - - - - True - False - end - - - Add - True - True - True - - - - True + False True 0 - - Save + + True - True - True - + False + + + True + False + Name + + + 0 + 1 + + + + + True + False + Model + + + 0 + 2 + + + + + True + False + Year built + + + 0 + 3 + + + + + True + False + LoA + + + 0 + 4 + + + + + True + False + Draught min + + + 0 + 8 + + + + + True + False + Draught + + + 0 + 7 + + + + + True + False + Beam + + + 0 + 6 + + + + + True + False + LwL + + + 0 + 5 + + + + + True + False + Shipyard + + + 0 + 9 + + + + + True + False + Displacement + + + 0 + 10 + + + + + True + False + Ballast + + + 0 + 11 + + + + + True + False + Default Storage + + + 0 + 12 + + + + + True + False + Remarks + + + 0 + 13 + + + + + True + False + True + + + 1 + 1 + + + + + True + False + True + + + 1 + 2 + + + + + True + False + True + + + 1 + 3 + + + + + True + False + True + + + 1 + 4 + + + + + True + False + True + + + 1 + 5 + + + + + True + False + True + + + 1 + 6 + + + + + True + False + True + + + 1 + 7 + + + + + True + False + True + + + 1 + 8 + + + + + True + False + True + + + 1 + 9 + + + + + True + False + True + + + 1 + 10 + + + + + True + False + True + + + 1 + 11 + + + + + True + False + True + + + 1 + 12 + + + + + True + False + True + + + 1 + 13 + + + + + True + False + 4 + 4 + True + + + + 0 + 0 + 2 + + True True + 1 + + + + + True + False + end + + + Delete + True + True + True + + + + True + True + 0 + True + + + + + Add + True + True + True + + + + True + True + 1 + + + + + Edit + True + True + True + + + + True + True + 2 + + + + + False + True 2 - False - True - 3 + page_detail + page_detail + + + + + True + False + vertical + + + True + False + Ghost + + + False + True + 0 + + + + + + + + + True + False + 4 + 4 + 4 + 8 + + + True + False + Name + + + 0 + 0 + + + + + True + True + 30 + 30 + 20 + alpha + + + 1 + 0 + + + + + True + False + Model + + + 0 + 1 + + + + + True + True + + + 1 + 1 + + + + + True + False + LoA + + + 0 + 3 + + + + + True + False + LWL + + + 0 + 4 + + + + + True + False + Beam + + + 0 + 5 + + + + + True + False + Draught + + + 0 + 6 + + + + + True + False + Draught, min + + + 0 + 7 + + + + + True + True + adj_loa + 2 + + + 1 + 3 + + + + + True + True + adj_lwl + 2 + + + 1 + 4 + + + + + True + True + adj_beam + 2 + + + 1 + 5 + + + + + True + True + adj_draught + 2 + + + 1 + 6 + + + + + True + True + adj_draught_min + 2 + + + 1 + 7 + + + + + True + False + Displacement + + + 0 + 9 + + + + + True + False + Ballast + + + 0 + 10 + + + + + True + True + adj_displacement + + + 1 + 9 + + + + + True + True + adj_ballast + + + 1 + 10 + + + + + True + False + Remarks + + + 0 + 12 + + + + + True + False + + + 1 + 11 + + + + + True + False + Default Storage + + + 0 + 11 + + + + + True + False + Shipyard + + + 0 + 8 + + + + + True + False + + + 1 + 8 + + + + + True + True + in + + + True + True + True + natural + natural + word + 1 + 1 + 1 + 1 + + + + + + + 1 + 12 + + + + + True + False + Year built + + + 0 + 2 + + + + + True + True + 0 + adj_year_built + + + 1 + 2 + + + + + True + True + 4 + 2 + + + + + True + False + end + + + Save + True + True + True + + + + True + True + 1 + + + + + Cancel + True + True + True + + + + True + True + 2 + + + + + False + True + 3 + + + + + page_edit + page_edit + 1 diff --git a/yms/ui/boxes.ui b/yms/ui/boxes.ui new file mode 100644 index 0000000..2bfd4d2 --- /dev/null +++ b/yms/ui/boxes.ui @@ -0,0 +1,844 @@ + + + + + + 90 + 1 + 10 + + + 100 + 1 + 10 + + + 100 + 1 + 10 + + + 9999 + 10 + 100 + + + 9999 + 10 + 100 + + + 9999 + 10 + 100 + + + True + False + 8 + + + True + False + vertical + + + True + True + + + + True + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + False + Filter Box + + + + + False + True + 0 + + + + + True + True + in + + + True + True + + + + + + + + True + True + 1 + + + + + True + False + end + + + New + True + True + True + + + + True + True + 0 + + + + + Edit + True + True + True + + + + True + True + 1 + + + + + Details + True + True + True + + + + True + True + 2 + + + + + False + True + 2 + + + + + page0 + page0 + + + + + True + False + vertical + + + True + False + Box Detais + + + + + + False + True + 0 + + + + + + True + False + + + True + False + Label + + + 0 + 0 + + + + + True + False + Placeholder + + + 1 + 0 + + + + + True + False + Remarks + + + 0 + 7 + + + + + True + False + + + 1 + 7 + + + + + True + False + Content + + + 0 + 1 + + + + + True + False + + + 1 + 1 + + + + + True + False + Type + + + 0 + 2 + + + + + True + False + Color + + + 0 + 3 + + + + + True + False + Weight + + + 0 + 4 + + + + + True + False + + + 1 + 2 + + + + + True + False + + + 1 + 3 + + + + + True + False + + + 1 + 4 + + + + + True + False + Weight net + + + 0 + 5 + + + + + True + False + + + 1 + 5 + + + + + True + False + Price + + + 0 + 6 + + + + + True + False + + + 1 + 6 + + + + + True + True + 1 + + + + + True + False + end + + + Delete + True + True + True + + + + True + True + 0 + True + + + + + Edit + True + True + True + + + + True + True + 1 + + + + + Close + True + True + True + + + + True + True + 2 + + + + + False + True + 2 + + + + + page1 + page1 + 1 + + + + + True + False + vertical + + + True + False + Edit Box + + + + + + False + True + 0 + + + + + + True + False + + + True + False + Label + + + 0 + 0 + + + + + True + False + Type + + + 0 + 1 + + + + + True + True + + + 1 + 0 + 6 + + + + + True + False + + + 1 + 1 + 6 + + + + + True + False + Dimensions (L x W x H) + + + 0 + 3 + + + + + True + False + 4 + 4 + x + + + 2 + 3 + + + + + True + False + 4 + 4 + x + + + 4 + 3 + + + + + True + True + 4 + 4 + 0 + adj_width_x + True + + + 1 + 3 + + + + + True + True + 4 + 4 + 0 + adj_width_y + True + + + 3 + 3 + + + + + True + True + 4 + 4 + 0 + adj_width_z + True + + + 5 + 3 + + + + + True + False + Price + + + 0 + 5 + + + + + True + False + Weight + + + 0 + 6 + + + + + True + False + Remarks + + + 0 + 8 + + + + + True + True + in + + + True + True + + + + + 1 + 8 + 6 + + + + + True + False + Weight net + + + 0 + 7 + + + + + True + False + 4 + 4 + mm + + + 6 + 3 + + + + + True + False + Content + + + 0 + 2 + + + + + True + True + + + 1 + 2 + 6 + + + + + True + True + 0 + adj_weight_net + + + 1 + 7 + 3 + + + + + True + True + 0 + adj_weight + + + 1 + 6 + 3 + + + + + True + True + 0 + adj_price + + + 1 + 5 + 3 + + + + + True + False + Colour + + + 0 + 4 + + + + + #FFFFFF + True + True + True + half + + + + 1 + 4 + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + 1 + + + + + True + False + end + + + Save + True + True + True + + + + True + True + 0 + + + + + Cancel + True + True + True + + + + True + True + 2 + + + + + False + True + 2 + + + + + page2 + page2 + 2 + + + + diff --git a/yms/ui/storage.ui b/yms/ui/storage.ui index 39c19a7..b1de980 100644 --- a/yms/ui/storage.ui +++ b/yms/ui/storage.ui @@ -205,7 +205,7 @@ - + True False @@ -224,7 +224,7 @@ True False - Placeholder + True 1 @@ -239,7 +239,7 @@ 0 - 1 + 6 @@ -249,23 +249,118 @@ 1 + 6 + + + + + True + False + Type + + + 0 1 - + + True + False + Position (x, y, z) + + + 0 + 2 + - + + True + False + Dimension (x, y, z) + + + 0 + 3 + - + + True + False + Angle + + + 0 + 4 + - + + True + False + Capacity + + + 0 + 5 + - + + True + False + True + + + 1 + 1 + + + + + True + False + True + + + 1 + 2 + + + + + True + False + True + + + 1 + 3 + + + + + True + False + True + + + 1 + 4 + + + + + True + False + True + + + 1 + 5 + diff --git a/yms/view/base.py b/yms/view/base.py index 52b1eef..d49430b 100644 --- a/yms/view/base.py +++ b/yms/view/base.py @@ -14,6 +14,7 @@ from gi.repository import GLib, Gtk, Gdk, Rsvg from .view import View from yms import gtkutils, combobox, treeview +from yms.lookup import Dropdown class BaseView(View): @@ -63,7 +64,15 @@ class BaseView(View): self.db.cur.execute("INSERT INTO settings (userid, sno, valint) VALUES (0, 1, 0)") self.db.conn.commit() else: - self.context.vessel.vid = row[0] + # check if vessel from settings exist + self.db.cur.execute("SELECT count(*) FROM vessel WHERE vid=%s", (row[0],)) + res = self.db.cur.fetchone()[0] + if res > 0: + self.context.vessel.vid = row[0] + else: + # TODO set vessel to first in list + self.context.vessel.vid = 0 + self.context.vessel.vesselname = "Ghost" # default storage for current vessel sql = "SELECT sid, sname FROM storage WHERE stype=0 AND vid=%s ORDER BY sname" @@ -75,7 +84,7 @@ class BaseView(View): vessellist = {} self.db.dcur.execute("SELECT vid, vesselname, model FROM vessel ORDER BY vid") for row in self.db.dcur.fetchall(): - vessellist[row['vid']] = ( + vessellist[row['vid']] = ( row['vesselname'] if row['model'] is None else row['vesselname'] + " - " + row['model'] ) @@ -154,6 +163,15 @@ class BaseView(View): if answer == False: return self.context.vessel.create() + model = self.combo['vessel'].get_model() + model.append((self.context.vessel.vid, self.context.vessel.vesselname)) + combobox.activate_row(self.combo['vessel'], self.context.vessel.vid) + self.edit_vessel(self.context.vessel) + self.stack['vessel'].set_visible_child(self.box['vessel_edit']) + + def on_but_vessel_edit_clicked(self, widget): + self.edit_vessel(self.context.vessel) + self.stack['vessel'].set_visible_child(self.box['vessel_edit']) def on_but_vessel_save_clicked(self, widget): print("Save vessel") @@ -172,6 +190,13 @@ class BaseView(View): txtbuf = self.text['vessel_remarks'].get_buffer() vessel.remarks = txtbuf.get_text(txtbuf.get_start_iter(), txtbuf.get_end_iter(), True) vessel.save() + self.stack['vessel'].set_visible_child(self.box['vessel']) + + def on_but_vessel_cancel_clicked(self, widget): + self.stack['vessel'].set_visible_child(self.box['vessel']) + + def on_but_vessel_delete_clicked(self, widget): + pass def on_but_comp_new_clicked(self, widget): print("New company") @@ -189,7 +214,11 @@ class BaseView(View): pass def display_vessel(self, vessel): - print("Display vessel") + self.label['vesselname'].set_text(vessel.vesselname) + self.label['model'].set_text(vessel.model or '') + self.label['year_built'].set_text(self.format_int(vessel.year_built, default='n/a')) + + def edit_vessel(self, vessel): self.entry['vesselname'].set_text(vessel.vesselname) self.entry['model'].set_text(vessel.model or '') self.spin['year_built'].set_value(vessel.year_built or 0) diff --git a/yms/view/boxes.py b/yms/view/boxes.py new file mode 100644 index 0000000..b002a9f --- /dev/null +++ b/yms/view/boxes.py @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: WTFPL + +import os +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import GLib, Gtk, Gdk + +from .view import View +from yms import gtkutils, combobox, treeview +from yms.model import box + +class BoxesView(View): + + def __init__(self, context): + super().__init__(context, _("Box"), "boxes.ui") + + # box already used for controls so other name here + self.sbox = box.Box(context.db) # current record + self.newrecord = False + + self.lst_box = Gtk.ListStore(int, str, str, str) + sql = ( + "SELECT b.boxid, b.label, b.content, b.remarks " + "FROM box AS b JOIN storage AS s USING (sid) " + "WHERE s.vid=%s " + "ORDER BY b.label" + ) + self.db.cur.execute(sql, (context.vessel.vid,)) + for row in self.db.cur.fetchall(): + self.lst_box.append(row) + + treeview.add_column(self.tree['box'], _('ID'), 0) + treeview.add_column(self.tree['box'], _('Label'), 1) + treeview.add_column(self.tree['box'], _('Content'), 2) + treeview.add_column(self.tree['box'], _('Remarks'), 3) + self.tree['box'].set_model(self.lst_box) + + + def load_list(self): + pass + + def display_record(self): + self.label['label'].set_text(self.sbox.label) + self.label['remarks'].set_text(self.sbox.remarks or '') + + def edit_record(self): + self.entry['label'].set_text(self.sbox.label) + self.entry['content'].set_text(self.sbox.content or '') + self.spin['wx'].set_value(self.sbox.wx or 0) + self.spin['wy'].set_value(self.sbox.wy or 0) + self.spin['wz'].set_value(self.sbox.wz or 0) + self.text['remarks'].get_buffer().set_text(self.sbox.remarks or '') + + # buttons on list page + def on_but_new_clicked(self, widget): + self.newrecord = True + self.label['title_edit'].set_text("Add new Box") + self.topwidget.set_visible_child(self.box['box_edit']) + + def on_but_edit_clicked(self, widget): + self.newrecord = False + self.label['title_edit'].set_text("Edit Box") + sid = treeview.get_value(self.tree['box'], 0) + self.sbox.load(sid) + self.edit_record() + self.topwidget.set_visible_child(self.box['box_edit']) + + def on_but_detail_clicked(self, widget): + sid = treeview.get_value(self.tree['box'], 0) + self.sbox.load(sid) + self.display_record() + self.topwidget.set_visible_child(self.box['box_detail']) + + # buttons on detail page + # edit2 uses edit callback + def on_but_close_clicked(self, widget): + self.topwidget.set_visible_child(self.box['box']) + + def on_but_delete_clicked(self, widget): + self.topwidget.set_visible_child(self.box['box']) + self.newrecord = False + + # buttons on edit page + def on_but_save_clicked(self, widget): + print("save not implemented yet") + self.newrecord = False + + def on_but_cancel_clicked(self, widget): + self.topwidget.set_visible_child(self.box['box']) + self.newrecord = False + + def on_but_color_clicked(self, widget): + pass + diff --git a/yms/view/cables.py b/yms/view/cables.py index 9a20a80..14f7dfd 100644 --- a/yms/view/cables.py +++ b/yms/view/cables.py @@ -62,3 +62,9 @@ class CablesView(View): def on_but_cancel_clicked(self, widget): self.topwidget.set_visible_child(self.box['cables']) + + def display_record(self): + pass + + def edit_record(self): + pass diff --git a/yms/view/documents.py b/yms/view/documents.py index 55f7199..3239658 100644 --- a/yms/view/documents.py +++ b/yms/view/documents.py @@ -59,3 +59,9 @@ class DocumentsView(View): def on_but_cancel_clicked(self, widget): self.topwidget.set_visible_child(self.box['documents']) + + def display_record(self): + pass + + def edit_record(self): + pass diff --git a/yms/view/fuses.py b/yms/view/fuses.py index b20ee2c..a08348d 100644 --- a/yms/view/fuses.py +++ b/yms/view/fuses.py @@ -64,3 +64,9 @@ class FusesView(View): def on_but_cancel_clicked(self, widget): self.topwidget.set_visible_child(self.box['fuses']) + + def display_record(self): + pass + + def edit_record(self): + pass diff --git a/yms/view/maintenance.py b/yms/view/maintenance.py index 5a0248f..efe8e36 100644 --- a/yms/view/maintenance.py +++ b/yms/view/maintenance.py @@ -58,3 +58,9 @@ class MaintenanceView(View): def on_but_cancel_clicked(self, widget): self.topwidget.set_visible_child(self.box['maintenance']) + + def display_record(self): + pass + + def edit_record(self): + pass diff --git a/yms/view/projects.py b/yms/view/projects.py index 7a74798..9987ecd 100644 --- a/yms/view/projects.py +++ b/yms/view/projects.py @@ -68,3 +68,9 @@ class ProjectsView(View): def on_but_cancel_clicked(self, widget): self.topwidget.set_visible_child(self.box['projects']) + + def display_record(self): + pass + + def edit_record(self): + pass diff --git a/yms/view/status.py b/yms/view/status.py index 9e9dacd..09c006e 100644 --- a/yms/view/status.py +++ b/yms/view/status.py @@ -21,7 +21,7 @@ class StatusView: gtkutils.init_named_controls(self, builder) # Generic information - self.label['dbinfo'].set_text("Database: {}".format(context.cfg['db'])) + self.label['dbinfo'].set_text("Database: {} on {}".format(context.cfg['db'], context.cfg['host'])) # Load table status info self.lst_tables = Gtk.ListStore(str, int) diff --git a/yms/view/storage.py b/yms/view/storage.py index a9639fa..b3313c4 100644 --- a/yms/view/storage.py +++ b/yms/view/storage.py @@ -8,6 +8,7 @@ from gi.repository import GLib, Gtk, Gdk from .view import View from yms import gtkutils, combobox, treeview from yms.model import storage +from yms.lookup import Dropdown class StorageView(View): @@ -34,6 +35,14 @@ class StorageView(View): treeview.add_column(self.tree['storage'], _('Remarks'), 3) self.tree['storage'].set_model(self.lst_storage) + combodict = self.context.lookup.dropdown(Dropdown.STORAGE_TYPE) + combobox.load_from_dict(self.combo['type'], combodict) + + combodict = self.context.lookup.dropdown(Dropdown.STORAGE_TYPE) + combobox.load_from_dict(self.combo['type'], combodict) + + combodict = self.context.lookup.enum('storage.capaunit') + combobox.load_from_dict(self.combo['unit'], combodict) def load_list(self): pass @@ -56,9 +65,11 @@ class StorageView(View): self.topwidget.set_visible_child(self.box['stor_edit']) def on_but_edit_clicked(self, widget): + sid = treeview.get_value(self.tree['storage'], 0) + if sid is None: + return self.newrecord = False self.label['title_edit'].set_text("Edit Storage") - sid = treeview.get_value(self.tree['storage'], 0) self.storage.load(sid) self.edit_record() self.topwidget.set_visible_child(self.box['stor_edit']) @@ -80,8 +91,12 @@ class StorageView(View): # buttons on edit page def on_but_save_clicked(self, widget): - print("save not implemented yet") self.newrecord = False + # check mandatory fields + sname = self.entry['sname'].get_text().strip() + if not sname: + return + # here be dragons def on_but_cancel_clicked(self, widget): self.topwidget.set_visible_child(self.box['storage']) diff --git a/yms/view/view.py b/yms/view/view.py index 5161323..b2a1ac0 100644 --- a/yms/view/view.py +++ b/yms/view/view.py @@ -27,25 +27,25 @@ class View: self.topwidget = builder.get_object("topobject") gtkutils.init_named_controls(self, builder) - def format_int(self, ival, separator=True, suffix=None): + def format_int(self, ival, separator=True, suffix=None, default=''): if ival is None: - return '' + return default formatted = locale.format_string("%d", ival, separator) if suffix: formatted += f"\u00A0{suffix}" return formatted - def format_float(self, fval, digits=2, grouping=True, suffix=None): + def format_float(self, fval, digits=2, grouping=True, suffix=None, default=''): if fval is None: - return '' + return default formatted = locale.format_string(f"%.{digits}f", fval, grouping) if suffix: formatted += f"\u00A0{suffix}" return formatted - def format_date(self, dval): + def format_date(self, dval, default=''): if dval is None: - return '' + return default return dval.strftime(self.context.datefmt) def on_textview_keypress(self, widget, event): diff --git a/ymsgui.py b/ymsgui.py index 02dddd0..b1b9aaa 100755 --- a/ymsgui.py +++ b/ymsgui.py @@ -37,9 +37,11 @@ from yms import database from yms import gtkutils, combobox, treeview from yms.model import user from yms.model import vessel +from yms.lookup import Lookup from yms.view.base import BaseView from yms.view.storage import StorageView +from yms.view.boxes import BoxesView from yms.view.equipment import EquipmentView from yms.view.inventory import InventoryView from yms.view.provisions import ProvisionsView @@ -57,17 +59,20 @@ class AppContext: 'host': 'localhost', 'db': 'yms', 'user': 'yms', - 'docpath': '/var/local/yms', + 'docpath': '/var/local/lib/yms', 'shapedir': '/usr/local/share/yms/shapes' } # mapping between PHP and Python datefmt_map = { - 'Y-m-d': '%Y.%m.%d', - 'Y/m/d': '%Y/%m/%d', - 'd.m.Y': '%d.%m.%Y', - 'd/m/Y': '%d/%m/%Y', - 'm-d-Y': '%m.%d.%Y' + 'Y-m-d': '%Y-%m-%d', # ISO / international / technical + 'd.m.Y': '%d.%m.%Y', # Germany / Central & Northern Europe + 'd/m/Y': '%d/%m/%Y', # UK / France / Spain / Italy / Australia / NZ + 'd-m-Y': '%d-%m-%Y', # Netherlands / Denmark / Belgium + 'd M Y': '%d %b %Y', # nautical documents / logbooks / unambiguous + 'm.d.Y': '%m.%d.%Y', # USA + 'm/d/Y': '%m/%d/%Y', # USA + 'Y/m/d': '%Y/%m/%d' # Japan / China } def __init__(self): @@ -80,6 +85,7 @@ class AppContext: def set_db(self, db): self.db = db + self.lookup = Lookup(db) def load(self): self.user = user.User(db, 0) @@ -97,6 +103,7 @@ class AppContext: else: # perhaps create missing record here? pass + self.lookup.load() def get_sharepath(self): path = os.path.realpath(__file__) @@ -113,6 +120,7 @@ class Frontend: self.modules = { "base": BaseView(context), "storage": StorageView(context), + "box": BoxesView(context), "equipment": EquipmentView(context), "inventory": InventoryView(context), "provisions": ProvisionsView(context), @@ -195,6 +203,7 @@ if __name__ == "__main__": context.cfg['host'] = config.get('DB', 'host') context.cfg['db'] = config.get('DB', 'db') db = database.Database(context.cfg['host'], context.cfg['db']) + context.set_db(db) connected = False if context.cfg['host'] == 'socket': connected = db.connect_local() @@ -203,7 +212,6 @@ if __name__ == "__main__": context.cfg['pass'] = config.get('DB', 'pass') connected = db.connect(context.cfg['user'], context.cfg['pass']) if connected: - context.set_db(db) context.load() # check if shapedir exists context.cfg['shapedir'] = config.get('Data', 'shapedir')