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
@@ -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')