From 2c52631787c20842926b17ba51f7bb307f668402 Mon Sep 17 00:00:00 2001 From: Martin Glueck Date: Tue, 23 Apr 2024 15:29:21 +0200 Subject: [PATCH] Initial --- .gitignore | 3 + Action_List.py | 39 ++++++ App_State.py | 26 ++++ Login.py | 56 ++++++++ Order_Display.py | 241 +++++++++++++++++++++++++++++++++++ _With_Table_Widget_.py | 97 ++++++++++++++ create_new_database.py | 58 +++++++++ designbox_orders.py | 150 ++++++++++++++++++++++ designbox_orders.qrc | 5 + designbox_orders_rc.py | 130 +++++++++++++++++++ icons/person-from-portal.png | Bin 0 -> 1455 bytes schema.py | 122 ++++++++++++++++++ 12 files changed, 927 insertions(+) create mode 100644 .gitignore create mode 100644 Action_List.py create mode 100644 App_State.py create mode 100644 Login.py create mode 100644 Order_Display.py create mode 100644 _With_Table_Widget_.py create mode 100644 create_new_database.py create mode 100644 designbox_orders.py create mode 100644 designbox_orders.qrc create mode 100644 designbox_orders_rc.py create mode 100644 icons/person-from-portal.png create mode 100644 schema.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d53f70f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.py +/__pycache__ +/test diff --git a/Action_List.py b/Action_List.py new file mode 100644 index 0000000..70d142c --- /dev/null +++ b/Action_List.py @@ -0,0 +1,39 @@ +from QT import QtWidgets, QtGui, QT +from _With_Table_Widget_ import _With_Table_Widget_ +from App_State import App_State +import schema + +class Action_List (_With_Table_Widget_) : + + settings_group = "order-list" + Columns = \ + ("Datum", "Benutzer", "Kind", "Text") + + def __init__ (self, actions, parent = None) : + super ().__init__ (parent = parent) + self._view.setRowCount (len (actions)) + for row, a in enumerate (actions) : + self._add_read_only_string (row, 0, a.date.strftime ("%H:%M:%S %d-%d-%Y")) + self._add_read_only_string (row, 1, a.user.username) + self._add_read_only_string (row, 2, + App_State.Action_Kind [a.action_kind]) + self._add_read_only_string (row, 3, a.action) + header =self._view.horizontalHeader () + header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) + # end def __init__ + +# end class Action_List + +class Action_Dialog (QtWidgets.QDialog) : + + def __init__ (self, actions) : + super ().__init__ () + self.setMinimumSize (640, 400) + l = QtWidgets.QHBoxLayout (self) + self.action_list = Action_List (actions) + l.addWidget (self.action_list) + self.setModal (False) + # end def __init__ + + +# end class Action_Dialog \ No newline at end of file diff --git a/App_State.py b/App_State.py new file mode 100644 index 0000000..b34b83c --- /dev/null +++ b/App_State.py @@ -0,0 +1,26 @@ +import schema + +class App_State : + + user_id = None + + Action_Kind = \ + { 1 : "Create Order" + , 10 : "Change owner" + , 20 : "IMOS CAD started" + , 25 : "IMOS CAD closed" + } + @classmethod + def Create_Action (cls, kind, text, order = None) : + if kind not in cls. Action_Kind : + raise ValueError (f"Unknow action kind {kind}") + schema.Action \ + ( user = cls.user_id + , date = schema.datetime.now () + , action_kind = kind + , action = text + , order = order + ) + # end def create_action + +# end class App_State diff --git a/Login.py b/Login.py new file mode 100644 index 0000000..2bfaab3 --- /dev/null +++ b/Login.py @@ -0,0 +1,56 @@ +from QT import QtWidgets, QT +import schema + + +class Login_Dialog (QtWidgets.QDialog) : + + def __init__ (self, username = "") : + super ().__init__ () + self.setWindowTitle ("Login") + self.layout = L = QtWidgets.QFormLayout (self) + self.user_name = QtWidgets.QLineEdit () + self.password = QtWidgets.QLineEdit () + self.password.setEchoMode (QtWidgets.QLineEdit.Password) + L.addRow ("Benutzername", self.user_name) + L.addRow ("Passwort", self.password) + layout = QtWidgets.QHBoxLayout () + login = QtWidgets.QPushButton ("Login") + login.setEnabled (False) + self.error = QtWidgets.QLabel ("") + layout.addWidget (self.error) + login.clicked.connect (self._login) + layout.addStretch (10) + layout.addWidget (login) + login.setDefault (True) + L.addRow (layout) + self.login = login + self.password.editingFinished.connect (self._login) + self.user_name.editingFinished.connect(self._check_login_possible) + self.user_id = None + self.user_name.setText (username) + if username : + self.password.setFocus () + # end def __init__ + + def _check_login_possible (self) : + un = self.user_name.text () + pw = self.password.text () + self.login.setEnabled ((len (un) >= 3) and (len (pw) >= 3)) + # end def _check_login_possible + + def _login (self) : + un = self.user_name.text () + pw = self.password.text () + with schema.orm.db_session : + user = schema.User.get (username = un) + if not user : + self.error.setText ("User nicht gefunden") + else : + if user.check_password (pw) : + self.user_id = user.id + self.accept () + else : + self.error.setText ("Passwort falsch") + # end def _login + +# end class Login_Dialog \ No newline at end of file diff --git a/Order_Display.py b/Order_Display.py new file mode 100644 index 0000000..4932d0b --- /dev/null +++ b/Order_Display.py @@ -0,0 +1,241 @@ +from QT import QtWidgets, QtGui, QT +from _With_Table_Widget_ import _With_Table_Widget_ +from App_State import App_State +from Action_List import Action_Dialog +import schema +import subprocess +import threading +import time + +class Order_List (_With_Table_Widget_) : + + settings_group = "order-list" + Header = "Aufträge" + Columns = \ + ("Warenkorb", "Status", "Bearbeiter", "Kommission", "Kunde", "Linie") + + State_Map = \ + { 0 : "neu" + , 10 : "in Bearbeitung" + , 20 : "im CAD geöffnet" + , 90 : "Odoo" + , 95 : "Zurückgesetzt" + , 99 : "Storno" + } + def __init__ (self, parent = None) : + super ().__init__ (parent) + self._view.setContextMenuPolicy (QT.CustomContextMenu) + self._view.customContextMenuRequested.connect (self._show_context_menu) + ACT = QtGui.QAction + self._open_cad = ACT (self.tr ("Im CAD öffnen")) + self._recreate_cnc = ACT (self.tr ("CNC Programm neu erzeugen")) + self._reset_order = ACT (self.tr ("Auftrag zurücksetzen")) + self._cancel = ACT (self.tr ("Auftrag stronieren")) + self._send_to_odoo = ACT (self.tr ("Auftrag ins Odoo schicken")) + self._actions = ACT (self.tr ("Aktionsliste")) + self._open_cad.triggered.connect (self._open_order_in_cad) + self._recreate_cnc.triggered.connect (self._start_recreate_cnc) + self._reset_order.triggered.connect (self._reset_order_in_webshop) + self._cancel.triggered.connect (self._cancel_order) + self._send_to_odoo.triggered.connect (self._send_oder_to_odoo) + self._actions.triggered.connect (self._show_actions) + self._cad_processes = {} + # end def __init__ + + def _show_context_menu (self, pos) : + row_data = self._view.selectedItems () + if row_data : + bno = row_data [0].text () + with schema.orm.db_session () : + order = schema.Order.get (basket_no = bno) + if order.user and order.user.id != App_State.user_id : + r = QtWidgets.QMessageBox.question \ + ( self, self.tr ("Fehler") + , self.tr + ( "Dieser Auftrag wird bereits von jemand anderes " + "bearbeitet.\n" + "Wollen Sie den Auftrag als Bearbeiter übernehmen?" + ) + ) + if r == QtWidgets.QMessageBox.No : + return + App_State.Create_Action \ + ( 10 + , f"User changed from {order.user.id} to {App_State.user_id}" + , order + ) + order.user = schema.User [App_State.user_id] + self.update_order (order, False, row_data [0].row ()) + state = order.state + if state < 20 : + cm = QtWidgets.QMenu () + cm.addAction (self._open_cad) + cm.addAction (self._recreate_cnc) + cm.addSeparator () + cm.addAction (self._send_to_odoo) + cm.addSeparator () + cm.addAction (self._reset_order) + cm.addAction (self._cancel) + cm.addSeparator () + cm.addAction (self._actions) + cm.exec (self._view.mapToGlobal (pos)) + # end def _show_context_menu + + def update_order (self, order, new, row = None) : + if new : + row = self._view.rowCount () + self._view.setRowCount (row + 1) + self._add_read_only_string (row, 0, str (order.basket_no)) + self._add_read_only_string (row, 1, self.State_Map [order.state]) + if order.user : + self._add_read_only_string (row, 2, order.user.username) + self._add_read_only_string (row, 3, order.commission) + self._add_read_only_string (row, 4, order.imos_user) + self._add_read_only_string (row, 5, str (order.production_line)) + else : + if row is None : + for row in range (self._view.rowCount ()) : + if self._view.item (row, 0).text () == order.basket_no : + break + else : + print ("Could not find order") + return + self._update_read_only_string (row, 1, self.State_Map [order.state]) + self._update_read_only_string (row, 2, order.user.username) + # end def update_order + + def _open_order_in_cad (self) : + row_data = self._view.selectedItems () + if row_data : + bno = row_data [0].text () + with schema.orm.db_session () : + state = schema.State.get () + order = schema.Order.get (basket_no = bno) + if order.state < 20 : + order.state = 20 + self.update_order (order, False, row_data [0].row ()) + self._cad_processes [bno] = subprocess.Popen \ + ( (r"%s\%s\%s.dwg" % (state.imos_order_dir, bno, bno), ) + , shell = True + ) + threading.Thread \ + ( target = self._wait_for_cad_close + , args = (bno, ) + , daemon = True + ).start () + App_State.Create_Action \ + ( 20 + , f"IMOS CAD started {order.basket_no}" + , order + ) + # end def _open_order_in_cad + + def _wait_for_cad_close (self, basket_no) : + p = self._cad_processes [basket_no] + while p.poll () is None : + time.sleep (.5) + del self._cad_processes [basket_no] + with schema.orm.db_session () : + order = schema.Order.get (basket_no = basket_no) + order.state = 10 + self.update_order (order, False) + App_State.Create_Action \ + ( 25 + , f"IMOS CAD closed {order.basket_no}" + , order + ) + # end def _wait_for_cad_close + + def _start_recreate_cnc (self) : + row_data = self._view.selectedItems () + if row_data : + bno = row_data [0].text () + print (bno) + # end def _start_recreate_cnc + + def _reset_order_in_webshop (self) : + row_data = self._view.selectedItems () + if row_data : + bno = row_data [0].text () + print (bno) + # end def _reset_order_in_webshop + + def _cancel_order (self) : + row_data = self._view.selectedItems () + if row_data : + bno = row_data [0].text () + print (bno) + # end def _cancel_order + + def _send_oder_to_odoo (self) : + row_data = self._view.selectedItems () + if row_data : + bno = row_data [0].text () + print (bno) + # end def _send_oder_to_odoo + + def _show_actions (self) : + row_data = self._view.selectedItems () + if row_data : + bno = row_data [0].text () + with schema.orm.db_session () : + order = schema.Order.get (basket_no = bno) + Action_Dialog \ + (order.actions.order_by (schema.orm.desc (schema.Action.date))).exec () + # end def _show_actions + +# end class Order_List + +class Order_Detail (_With_Table_Widget_) : + + settings_group = "order-detail" + Header = "Auftrags Detail" + Columns = \ + ("Position", "Artikel", "Preis") + +# end class Order_Detail + +class Order_Display (QtWidgets.QSplitter) : + + def __init__ (self, parent) : + super ().__init__ (parent) + self._order_list = Order_List () + self._order_detail = Order_Detail () + self.addWidget (self._order_list) + self.addWidget (self._order_detail) + self.orders = {} + # end def __init__ + + def update_orders (self) : + with schema.orm.db_session : + for o in schema.Order.select () : + db_lc = o.actions.order_by (schema.Action.date).first ().date + if o.basket_no not in self.orders : + self._order_list.update_order (o, True) + self.orders [o.basket_no] = o + o.last_changed = db_lc + else : + if db_lc > self.orders [o.basket_no].last_changed : + self._order_list.update_order (o, False) + self.orders [o.basket_no].last_changed = db_lc + # end def update_orders + + def save_settings (self, settings) : + settings.beginGroup ("order-display") + settings.setValue ("geometry", self.saveGeometry ()) + settings.setValue ("windowState", self.saveState ()) + settings.endGroup () + self._order_list .save_settings (settings) + self._order_detail.save_settings (settings) + # end def save_settings + + def restore_settings (self, settings) : + settings.beginGroup ("order-display") + self.restoreGeometry (settings.value ("geometry" )) + self.restoreState (settings.value ("windowState")) + settings.endGroup () + self._order_list .restore_settings (settings) + self._order_detail.restore_settings (settings) + # end def restore_settings + +# end class Order_Display \ No newline at end of file diff --git a/_With_Table_Widget_.py b/_With_Table_Widget_.py new file mode 100644 index 0000000..c6cc859 --- /dev/null +++ b/_With_Table_Widget_.py @@ -0,0 +1,97 @@ +from QT import QtWidgets, QT + +class _With_Table_Widget_ (QtWidgets.QWidget) : + + Header = None + + def __init__ (self, parent = None) : + super ().__init__ (parent) + self._box = QtWidgets.QVBoxLayout (self) + if self.Header : + header = QtWidgets.QLabel (self.Header) + self._box.addWidget (header) + self._view = QtWidgets.QTableWidget (0, len (self.Columns)) + self._view.setHorizontalHeaderLabels (self.Columns) + self._box.addWidget (self._view) + self._view.setAlternatingRowColors (True) + self._view.setSelectionBehavior (QtWidgets.QAbstractItemView.SelectRows) + self._view.verticalHeader ().hide () + self.selection_model = self._view.selectionModel () + self.selection_model.currentChanged.connect (self._current_changed) + self._current_row = None + # end def __init__ + + def _add_read_only_string (self, row, col, text) : + item = QtWidgets.QTableWidgetItem (text) + flags = item.flags() & ~QT.ItemIsEditable + item.setFlags (flags) + self._view.setItem (row, col, item) + # end def _add_read_only_string + + def _update_read_only_string (self, row, col, text) : + item = self._view.item (row, col) + if not item : + self._add_read_only_string (row, col, text) + else : + model = self._view.model () + index = model.index (row, col) + model.setData (index, text) + # end def _update_read_only_string + + def _current_changed (self, new_index, old_index) : + row = new_index.row () + if row != self._current_row : + self._current_row = row + # end def _current_changed + + def save_settings (self, settings) : + settings.beginGroup (self.settings_group) + settings.setValue ("column-width", self.column_width ()) + if False and self.selection_model : + selection = [ self.model.item_path (i) + for i in self.selectionModel ().selectedRows () + ] + settings.setValue ("selection", selection) + settings.endGroup () + # end def save_settings + + def restore_settings (self, settings) : + settings.beginGroup (self.settings_group) + self.restore_column_width (settings.value ("column-width" )) + if False and self.selection_model : + self.restore_selection (settings.value ("selection" )) + settings.endGroup () + # end def restore_settings + + + def column_width (self) : + result = [] + for i in range (self._view.columnCount ()) : + result.append (self._view.columnWidth (i)) + return result + # end def column_width + + def restore_column_width (self, width) : + for i, w in enumerate (width or ()) : + self._view.setColumnWidth (i, int (w)) + # end def restore_column_width + + def restore_selection (self, selected) : + sm = self.view.selectionModel () + sm.reset () + model = self.view.model () + if model and selected : + iv_index = model.invalid_index + for path_to_selected in selected : + parent_index = iv_index + try : + for pp in path_to_selected : + index = model.index (pp, 0, parent_index) + parent_index = index + sm.select (index, sm.__class__.Select | sm.__class__.Rows) + except : + pass + # end def restore_selection + +# end class _With_Table_Widget_ + diff --git a/create_new_database.py b/create_new_database.py new file mode 100644 index 0000000..b0c86b3 --- /dev/null +++ b/create_new_database.py @@ -0,0 +1,58 @@ +### create new initial database +from schema import User, State, Production_Line, Order,Action, db, orm +import datetime + +def get_state () : + state = State.get () + if not state : + state = State \ + ( imos_export_dir = "?" + , pgiq_import_dir = "?" + , last_directory_scan = datetime.datetime.fromtimestamp (0) + ) + return state +# end def get_state + +if __name__ == "__main__" : + import argparse + parser = argparse.ArgumentParser () + parser.add_argument ("database", type = str) + parser.add_argument ("-u", "--user", type = str) + parser.add_argument ("-p", "--password", type = str) + parser.add_argument ("--check", action = "store_true") + parser.add_argument ("-i", "--imos_dir", type = str) + parser.add_argument ("-P", "--pgiq_dir", type = str) + parser.add_argument ("-l", "--line", type = str) + cmd = parser.parse_args () + + db.bind (provider = "sqlite", filename = cmd.database, create_db = True) + db.generate_mapping (create_tables = True) + with orm.db_session : + breakpoint () + if cmd.user : + u = User.get (username = cmd.user) + if not u : + u = User (username = cmd.user, password = cmd.password) + print (f"Add new user {cmd.user} to the database") + else : + if cmd.check : + print (f"Check password for user {cmd.user}: " + f"{u.check_password (cmd.password)}" + ) + else: + print (f"Set new password for user {cmd.user}") + u.change_password (cmd.password) + state = get_state () + if cmd.imos_dir : + state.imos_export_dir = cmd.imos_dir + if cmd.pgiq_dir : + state.pgiq_import_dir = cmd.pgiq_dir + print (state) + if cmd.line : + short, name = cmd.line.split (":") + line = Production_Line.get (short = short) + if not line : + line = Production_Line (short = short, name = name) + else : + line.name = name + print (line) \ No newline at end of file diff --git a/designbox_orders.py b/designbox_orders.py new file mode 100644 index 0000000..2fae52e --- /dev/null +++ b/designbox_orders.py @@ -0,0 +1,150 @@ +import os +from QT.Main_Window import Main_Window, QtWidgets +from Order_Display import Order_Display +from Login import Login_Dialog +from App_State import App_State +import designbox_orders_rc +import schema +import pathlib +from lxml import etree + +class Order_Processing (Main_Window) : + + APP_NAME = "Designbox Orders" + VERSION = "0.5" + APP_ORGANISATION = ("designbox-orders", "glaser-co.at") + APP_DIR = os.path.dirname (__file__) + + @classmethod + def _load_app_settings (cls, settings) : + settings.beginGroup ("TempSettings") + result = {"database" : settings.value ("database")} + result = {"username" : settings.value ("username")} + settings.endGroup () + return result + # end def _load_app_settings + + def _setupActions (self, app) : + super ()._setupActions (app) + self._add_action ("test", "Scan Dir", self.scan_orders_directory) + # end def _setupActions + + def _setupGui (self) : + self.main_menu = QtWidgets.QMenuBar (self) + self.setMenuBar (self.main_menu) + file = QtWidgets.QMenu ("& File") + self.main_menu.addMenu (file) + self.tool_bar_file = self.addToolBar ("File") + self.tool_bar_file.setObjectName ("tb_file") + for a, l in ( ( self.actions.exit + , (file, self.tool_bar_file) + ) + , ( (self.actions.test), (self.tool_bar_file, )) + ) : + for x in l : + x.addAction (a) + self.setCentralWidget (Order_Display (self)) + settings = self.settings + settings.beginGroup ("TempSettings") + user_id = settings.value ("user_id") + settings.endGroup () + with schema.orm.db_session : + App_State.user_id = schema.User [user_id].id + self.scan_orders_directory () + # end def _setupGui + + def _find_orders (self, root_path : pathlib.Path) : + result = set () + for path, dirs, files in root_path.walk () : + for f in files : + if f.endswith (".txt") : + xml_name = (path / f).with_suffix (".xml") + if xml_name.exists () : + result.add (xml_name) + for d in dirs : + result.intersection_update (self._find_orders (root_path / d)) + return result + # end def _find_orders + + def scan_orders_directory (self) : + with schema.orm.db_session : + state = schema.State.get () + orders = self._find_orders (pathlib.Path (state.imos_export_dir)) + with schema.orm.db_session : + for o in orders : + self._update_order_in_database (o) + self.centralWidget ().update_orders () + # end def scan_orders_directory + + def _update_order_in_database (self, xml_file) : + basket_no = xml_file.stem + if True : + order = schema.Order.get (basket_no = basket_no) + if not order : + xml = etree.parse (xml_file).getroot () + com = xml.xpath ("//TEXT_SHORT") [0].text + user,line = xml.xpath ("//RETAILER") [0].text.split ("/") + order = schema.Order \ + ( basket_no = basket_no + , state = 0 + , commission = com + , imos_user = user + , production_line = schema.Production_Line.get (short = line) + , active = True + ) + schema.Action \ + ( user = schema.User [App_State.user_id] + , date = schema.datetime.now () + , action_kind = 1 + , action = "Auftrag importiert" + , order = order + ) + # end def _update_order_in_database + + def _restore_settings (self) : + super ()._restore_settings () + self.centralWidget ().restore_settings (self.settings) + # end def _restore_settings + + def save_settings (self) : + settings = self.settings + super ().save_settings () + self.centralWidget ().save_settings (settings) + settings.beginGroup ("TempSettings") + settings.setValue ("user_id", App_State.user_id) + settings.endGroup () + # end def save_settings + + @classmethod + def pre_main_window_action (cls, settings) : + settings.beginGroup ("TempSettings") + db_file = settings.value ("database") + user_id = settings.value ("user_id") + settings.endGroup () + schema.db.bind (provider = "sqlite", filename = db_file) + schema.db.generate_mapping (create_tables = False) + username = "" + if user_id : + with schema.orm.db_session : + username = schema.User [user_id].username + if False : + login = Login_Dialog (username) + if login.exec () == 1 : + settings.beginGroup ("TempSettings") + settings.setValue ("user_id", login.user_id) + settings.endGroup () + return True + else : + print ("### Login skipped") + return True + # end def pre_main_window_action + +# end class Order_Processing + +if __name__ == "__main__" : + import argparse + parser = argparse.ArgumentParser () + parser.add_argument ("-s","--settings-file", type = str) + parser.add_argument ("-d", "--database", type = str) + cmd = parser.parse_args () + Order_Processing.run (Order_Processing.load_settings (cmd)) diff --git a/designbox_orders.qrc b/designbox_orders.qrc new file mode 100644 index 0000000..46b881d --- /dev/null +++ b/designbox_orders.qrc @@ -0,0 +1,5 @@ + + + icons/person-from-portal.png + + diff --git a/designbox_orders_rc.py b/designbox_orders_rc.py new file mode 100644 index 0000000..f60268c --- /dev/null +++ b/designbox_orders_rc.py @@ -0,0 +1,130 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.6.2 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x05\xaf\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ +\x00\x00\x00\x09pHYs\x00\x00\x01b\x00\x00\x01b\ +\x01_'\xd0S\x00\x00\x00\x19tEXtSof\ +tware\x00www.inksca\ +pe.org\x9b\xee<\x1a\x00\x00\x05\ +(\xd3|\x88\xa8\x88\xec\x9a\xa4\xf6\xa0i\x8a\x96f\x8a\ +\xe6\xeaa\xf6\xbe\x9c\xb3\xcf\xde{f\xf6\xb9\xfd\xe1p\ +\xce\x9dY\x9fw\xd6^\xb3f\xcd\x96\x99\x91\x87\xa4\xa1\ +\xc0$`\x18\xb0\x1f\xd8bf\x7f\xb6\x11v\x08IC\ +\x80\x85\xc0\x8d\xc0\xa5\xc0\xaf\xc0\xc7\xc0b3;\x12$\ +#\xef@*\xf4V`@\xd3\xf0I`\x9d\x99\xfd\xd5\ +\x07vgz\x86\x01[\x80+\x0a\xa6{\x80\x89fv\ +\xc8'')\x18\x9bL\xab\xf1\x00\x03\x81)\x92\x14k\ +h\x05\x9e\xa1\xd8x\xd2\xf1\x85!BZ\x1c\x904\x1a\ +\x18QB{\x010:\xd4\xba\x00\xdc\xd6\xe1<\xd0\xbe\ +\x02\xd7x\xe8\xaf\x0e\x11\x1a\x88\xb2\x7fT\x86\xae\x10!\ +\xbd\x0eH\xea\x02\xce\xf3\xd0\x0f\x9142Dp\x00\xf6\ +{\xe6\x7f\x0f\x11\xd2\xbc\x02\x97\x07*\xee\xab0Z\xd7\ +\xe1<\x90:\x90>\x9c\x97\x04*\x1e\xd5G\x0f\xf3s\ +\xb8lS\x84\x1e`q\x88\x90l\x05\x86\x02g\x05*\ +>\x1b\xb80\x90\xb6\x14i\x8a\x1c\x07,\x05~\x04\x8e\ +\xa7\xdfK\x81q!)\x14\xa0\x91~_\x14\xa9\xbf\x0b\ +8\x90\x1f\x94\xd4\x0f\xb7)\xcd\x06&\xa6t\xe7\x03\x87\ +\x80\x9f\x81/\x81\x0f\x81o\xcc\xecL\xbaY=\x1e\xa9\ +\xbbU\xa7\x99!i\x1a0*\x82o\x97\x99u\xe7\x8c\ +\x9f\x05\xbc\x08\x8c\x09\xe0\xdf\x07|\x00\xbc\x0f|jf\ +'#t\xb7 \x0b\xa1a\x91|\xc3\xb3\x1f\x92\x1a\x92\ +^\x01\xd6\x13f<\xc0H`>\xb0\x01\xd8-\xe9Y\ +I\xbe\xb4Z\x88DR\x7f\xe0\x9cH\xbe\xc1\x92\x06J\ +\x1a\x08\xac\x05\x1e\xad\xa3\xc0\x12I\xe7\xfa\x88\x12`P\x0d\xe1\x7f\x94\x8cw\ +\x03\xb7\x98\xd91\x00I\x83\x80\x195\xe4\x83[\xbd'\ +|Du\x1d8\x88\xcb\xe1\xcdX\x8f3\xfe\x9f\xa6\xb1\ +\x99\xc0\xe0\x1a\xf23<,\xa92B\x12\xc2K\x88<\ +\xde\x05n\x02\x16\x00\xd3\xcd,o<\xd4\x0f\x9f\x0cC\ +\x81\x87\xaa\x08\x1a@\xbf\x9a\xc2\xfb\x99\xd9&`S\xd1\ +dd\xf8\x18\xf0=\xc5\xe7\x8d\x97$%\xc0J3k\ +\x0b\xdd\x84\xfa\x0e4<\xf31\xe1\xf3\x15\xf0T\xc9\x5c\ +\x7f\xe0e\xe0\x80$K?{%-\x914\xa0\x11`\ +H\x19|\x8e\xdf\x1e!k\xa5\x99m\x94\xb4\x15\x98\x10\ +@?\x12x\x12\xb0NV\xa0\x94/\x0d\x9f\x99\x81r\ +\x8e\x03k\xd2\xdf\xf3\x80\xa3\x116\xcc-\xeaJ\xf4\x05\ +&\x10\x1e>k\xb3v\x8d\x99\xed\xc09\x1e\xdc\x83J\ +\x80\x7f\xa3\xcds\xa8\xe2\x8b)\xcdW4\xffaf\x9b\ +\x81kq\xcf\x85\x0f\xab\x12\xe0t\x84\xb2fT9p\ +,P\xc6\x1e\xe0\xf3\xfc`\xba\x12\xd7\x03Sq\x0e\xee\ +\xc4\x85Z\x86\xbd\xb8ziQ\xc3cH\x15\xaa\xf8\xb6\ +\xe1R\xa3\xaf\xaa\x5cefg\x8a&\xcc\xb5\x0c?I\ +?\xa5\xf8_B\xc8\xcc\xf6\xe0N\x5c>\xcc\x914\xb1\ +\xa6~\xc09p\xa2&o\xcb\xae\x9b\x9e\xcc\xeej\x1a\ +z\x04\x7fog\x0c\xb0Y\xd2\x1b\x92j\x95\xf5\x09\xad\ +\xb1\x15\x83|\xd90\x07xU\xd2p\x003\xfb\x0d\xd7\ +g\xfd\xc1#G\xb8\xe3\xe5vI\xf7\xc4\x1a\xd1\x89\x03\ +y\xbe\xc7pe\xf6\xaa\xf4\x98\x9a=\x8cc\x81\xa7i\ +w8\x8f\x11\xc0;\x926H\xf2u\x08{\xd1'+\ + i\x02\xae\xc7\x030\x0dX\x93\xd5\xf2fv\xca\xcc\ +^\x00\xae\xc2\xf5\xfe}\x98\x01t\xa7-\x1a/\x84k\ +eO\x0e\xa0\x1b\x9f\x1a\x17\xd4t\xc5\xa5\xbeyf\xd6\ +\x92\xcf\xd30Y\x8a\xbf\xb9\xfb\x80\x99-\xf7)I\x80\ +\xc3\x01\xc6\x8c\x07\xee#\xdcx\x80\xcb\x80\xeb\xf2\x83f\ +\xb6\x1a\xb8\x12x\x13\x97j\xcb05DI\xa8\x03\xd3\ +B\x84\xe5p\x14x\xabh\xc2\xcc\x0e\x9b\xd9|\xdcf\ +\xf5K\x09\x7f\xd09%1\xb3S\x84\xef\x9c1x\xcd\ +w\xaf\x96\x86\xd7\xf6\x92\xe92\xc7Z\x90\x15s\xbeF\ +\xea\xc6\x10aM\xd8\x0a<\xef#J\x1bc7\x94L\ +w\x97\x8c\xb7 s`\x9f\x87n\x1b\xae&\xa9\xa2;\ +\x0d\xec\xc0\xdd}M)8^\x16a\x12\xc5m\x9dc\ +\xc0\xe6\x00\xfe\xde\xc3\xcc^\x0f\x9d\x01_\xa7\x1fp%\ +\xf0\xc1\x10\x05\x1e\x94\x1d9?\x0bm\xf86\x87Ph\ +I\xf1w\x1f\x19\x0f0\xbdd\xfc\xa3P\x01\x09\xf4V\ +~\xbb\x03yv\x85\x0a\xaf\x82\xa4\x8bq\xe9\xb4\x08\xc1\ +\xcf\x5c\xf3\x89\xac\xec\xba'\x8f\xdd\xa1\xc2=(;r\ +\xfedf\xc1:z\x1dH\xdb\x80\xbe\xeb\xfd#f\xe6\ +{^B\xd1q\xf8@\xfb=\xf1\xb7\x1e\xfa\xefb\x84\ +{\xd0\xb6K\xa7\x88J\xd9-\x0e\x98\xd9N\xca\xefo\ +\x0f\x12\xb8\xb9\x04\xa2\xe8\xb4v\x94\xc0\xf4\x99\xa1\xa8+\ +\xf1\x05\xee\xe5\x8ef\x9c\xc0\xddeU\xd5.\xb1XQ\ +0\xb6,\xf6\xbe\xac\xedm\x15\x80\xf4t4\x09W\xdf\ +g\xaf\xdb\x04\xbd\xfe\x12\xacXj\xe0\xee\x82\xef\xc7\xad\ +\xc6r`\xa1\x99E5\x19\xfe\x036\xda\x8b\xa9m\xbd\ +\x82T\x00\x00\x00\x00IEND\xaeB`\x82\ +" + +qt_resource_name = b"\ +\x00\x05\ +\x00o\xa6S\ +\x00i\ +\x00c\x00o\x00n\x00s\ +\x00\x08\ +\x0f\x07Z\xc7\ +\x00e\ +\x00x\x00i\x00t\x00.\x00p\x00n\x00g\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x8e\xf0\xcd\xec\xfe\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/icons/person-from-portal.png b/icons/person-from-portal.png new file mode 100644 index 0000000000000000000000000000000000000000..2d0ce19581129d788f86df145e821dc21dd0445f GIT binary patch literal 1455 zcmV;g1yK5lP)Dx&7gA&YfAGch+9B zX3dtFk%y$Az$9Q8upiiBW`DL3b_hvBfQ7(~z@@;iz{kL1GdmI_Bkw>e^j2U%)9^`P zotgdB2X<$Ah5=iED+;E2fQe>y$R{T$7@JJ1@c;vXDUuXxXa%03*m%bms$yfS-Xcfe(Ri%kk$l6mACel9 zUIEq}@mK58P2hKEM)5=lX~eU{DR=f0T@O1GkvjF#t(}fEhL9 zgIm2Pd_;8}IEc&P%~+2!{-36?nZV#0@-sXvsWL(m!b^xe-UV&~768-DEN?v251$M} zfrqLH8bH6A!uy%oCSX(1WMudl@IA%njh7@Pz)CY~3*ChJ4m3RTG2!zS@KniMf8b@{ zfFw&ldnGNAG@ub+NEv*N-yR`rrI~G%v=x{TKtB?A60lBIpp-8SpP7Mjjst7b-}8W@ z5jM;%>PqwlCJ-JzYqNHZneD(i9^8XUfPbUSbEx8rDa}_j|6yjEfopNkg%7I|;Bh(FEKtTC;Pd=ox)Rp|Q*jRN#D!Xhy|{X5QNv?M6{`5Q;Zmcos$6AeXNo4wwG2N= zKc)CzLdeYa;7(jV%#k#)rhdRVaH1w}%dVTA%cQ}s`z&nyQ zNb+?Ed(kH=AZY@w#{)18Sd-QAW_HTVUI4Db_5OXB0d%J=8ZU%vWls*E8=o2%)O5wA zzIkTWlF!pHS&F~8`+%7(_bEw$!vV&O2ks->c!0}*>+*wUwi>t+coUau%P=)WNvH$U zLWJdT6nLv>!pshv*?in)^-BqV&^aYBvr~lcoH2OKcdwR)*Y=X>GRInw6?48u>c$XZ zP1p)N?;}bZV>gsccbAN}Aah`!hi)6LCaI!34eY?(eN8DiUKU{z#ciEqz~%t{-owmZ z51j#Dryp=7@WBuT9+Vw9KMQU6HiuAXN%40d84obMWc;I00SWY0e!ByCc7+PBf~1Ra z>9h#J&s_6~tn9ickKLidJt#YJ{$yssI(i*Q+kn37{Uc`PdtO4(ari*{J@L05`tM?d zd(gH^r-(|~Ee@F3PRj3Z4-&Y!1fuA)c9g*MnW3mF@dfVXj%J*|-DOqPF0oiqFt#lA zzO3zS6@c_~GznMVXRq4?z5WucSZd(zg73$z#&TexnME}j{sT7Ji>YnBf>Zzi002ov JPDHLkV1hwVk{SR2 literal 0 HcmV?d00001 diff --git a/schema.py b/schema.py new file mode 100644 index 0000000..c209b6c --- /dev/null +++ b/schema.py @@ -0,0 +1,122 @@ +from pony import orm +import datetime +from hashlib import sha256 +from datetime import datetime + +db = orm.Database () +key = "b9281aea-4a9e-4721-b932-5b5ffbc66cfe" + +class User (db.Entity) : + + username = orm.Required (str, unique = True) + password = orm.Required (str) + active = orm.Optional (bool) + last_logedin = orm.Optional (datetime) + created = orm.Optional (datetime) + actions = orm.Set ("Action") + orders = orm.Set ("Order") + + def __init__ (self, username, password, active = True) : + pwd = f"{key}:{password}" + password = sha256 (pwd.encode ("utf-8")).hexdigest () + super ().__init__ \ + ( username = username + , password = password + , active = active + , created = datetime.now () + ) + # end def __init__ + + def check_password (self, password) : + pwd = f"{key}:{password}" + password = sha256 (pwd.encode ("utf-8")).hexdigest () + return self.password == password + # end def check_password + + def change_password (self, password) : + pwd = f"{key}:{password}" + self.password = sha256 (pwd.encode ("utf-8")).hexdigest () + # end def change_password + +# end class User + +class Production_Line (db.Entity) : + + short = orm.Required (str) + name = orm.Required (str) + orders = orm.Set ("Order") + + def __str__ (self) : + return f"{self.name} ({self.short})" + # end def __str__ + +# end class Production_Line + +class Order (db.Entity) : + basket_no = orm.Required (str) + state = orm.Required (int) + commission = orm.Optional (str) + imos_user = orm.Required (str) + production_line = orm.Required (Production_Line) + user = orm.Optional (User) + active = orm.Optional (bool) + actions = orm.Set ("Action") + order_lines = orm.Set ("Order_Line") +# end class Order + +class Action (db.Entity) : + user = orm.Required (User) + date = orm.Required (datetime) + action_kind = orm.Required (int) + action = orm.Optional (str) + order = orm.Optional (Order) +# end class Action + +class Odoo_Article (db.Entity) : + name = orm.Required (str) + default_text = orm.Optional (str) + default_price = orm.Optional (float) + order_lines = orm.Set ("Order_Line") +# end class Odoo_Article + +class Order_Line (db.Entity) : + order = orm.Required (Order) + text = orm.Required (str) + price = orm.Required (float) + odoo_article = orm.Required (Odoo_Article) +# end class Order_Line + +class State (db.Entity) : + + imos_export_dir = orm.Required (str) + imos_order_dir = orm.Required (str) + sim_archive = orm.Required (str) + pgiq_import_dir = orm.Required (str) + last_directory_scan = orm.Required (datetime) + + def __str__ (self) : + return f""" +IMOS dir {self.imos_export_dir} +PGIQ dir {self.pgiq_import_dir} +Last scan {self.last_directory_scan} +""".strip () + # end def __str__ + +# end class State + +if __name__ == "__main__" : + import argparse + parser = argparse.ArgumentParser () + parser.add_argument ("database", type = str) + parser.add_argument ("-d", "--sql-debug", action = "store_true") + cmd = parser.parse_args () + orm.set_sql_debug (cmd.sql_debug) + db.bind (provider = "sqlite", filename = cmd.database, create_db = True) + db.generate_mapping (create_tables = True) + if False : + with orm.db_session : + u = User (username = "glueckm", password = "123") + if True : + with orm.db_session : + u = User.get (username = "glueckm") + print (u, u.check_password ("123")) \ No newline at end of file