diff --git a/App_State.py b/App_State.py
index 5eda9ce..a86c3e3 100644
--- a/App_State.py
+++ b/App_State.py
@@ -1,65 +1,332 @@
-from Log_View import Log_View, Log_Handler
-import schema
-import logging
+import logging
+import logging.handlers
+from rich.logging import RichHandler
+import yaml
+import time
+import argparse
+import datetime
+import threading
+try :
+ import paho.mqtt.client as mqtt
+except :
+ mqtt = None
+try :
+ import redis
+except :
+ redis = None
+from Attr_Dict import Attr_Dict
+try :
+ from cryptography.fernet import Fernet
+except :
+ print ("Decryption supported disabled")
+
+logging.DEBUG5 = logging.DEBUG - 5
+logging.addLevelName (logging.DEBUG5, "DEBUG5")
+
+def debug5 (self, msg, *args, **kwargs):
+ """
+ Log 'msg % args' with severity 'DEBUG4'.
+
+ To pass exception information, use the keyword argument exc_info with
+ a true value, e.g.
+
+ logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
+ """
+ if self.isEnabledFor(logging.DEBUG5):
+ self._log(logging.DEBUG5, msg, args, **kwargs)
+# end def debug5
+logging.Logger.debug5 = debug5
class App_State :
- user_id = None
+ user = None
imos_done_dir = "IMOS-Done"
imos_order_dir = "ImOrder"
sim_batches = "Batches"
pgiq_dir = "OrderXML"
- L = logging.getLogger ()
- Action_Kind = \
- { 1 : "Create Order"
- , 2 : "Update Order"
- , 10 : "Change owner"
- , 15 : "Force order state"
- , 20 : "IMOS CAD started"
- , 25 : "IMOS CAD closed"
- , 30 : "CNC generation started"
- , 35 : "CNC generation finished"
- , 40 : "Odoo Freigabe"
- , 100 : "Article created"
- , 110 : "Order Line created"
- , 111 : "Order Line updated"
- }
- @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
+ def _setup_topics (self, config = None) :
+ config = config or getattr (self, "config", {})
+ mqr = config.mqtt ["topic-root"]
+ baskets = f"{mqr}/baskets"
+ self.mqtt = Attr_Dict \
+ ( basket = Attr_Dict
+ ( state = f"{baskets}/{{basket_no}}/state"
+ , user = f"{baskets}/{{basket_no}}/user"
+ , changed = f"{baskets}/{{basket_no}}/changed"
+ , data = f"{baskets}/{{basket_no}}"
+ )
+ , baskets = baskets
)
- # end def create_action
+
+ # end def _setup_topics
@classmethod
- def Log_View (cls, level = logging.INFO, parent = None) :
- cls.L.info ("Log window created")
- cls._log_view = Log_View (parent)
- lh = Log_Handler (cls._log_view)
- #lh.setFormatter \
- # ( logging.Formatter
- # ('%(asctime)s '
- # '%(levelname)s - %(message)s'
- # , datefmt = "%Y-%m-%d %H:%M:%S"
- # )
- # )
- lh.setLevel (level)
- lh.setFormatter \
- ( logging.Formatter
- ( "%(asctime)s - %(message)s"
+ def Add_Logging_Attributes (cls, parser = None) :
+ if parser is None :
+ parser = argparse.ArgumentParser ()
+ parser.add_argument ("--log-level", type = str, default = "INFO")
+ parser.add_argument ("--log-file", type = str)
+ parser.add_argument ("--log-file-level", type = str, default = "INFO")
+ return parser
+ # end def Add_Logging_Attributes
+
+ @classmethod
+ def Setup_Logging (cls, cmd, name = None) :
+ logging.basicConfig \
+ ( level = "DEBUG5"
+ , format = "%(message)s"
+ , handlers = []
+ )
+ cls.L = logging.getLogger (name)
+ handler = RichHandler ()
+ handler.setLevel (cmd.log_level)
+ cls.L.addHandler (handler)
+ if cmd.log_file :
+ handler = logging.handlers.TimedRotatingFileHandler \
+ (cmd.log_file, when = "midnight", encoding = "utf-8")
+ formatter = logging.Formatter \
+ ( "%(asctime)s - %(levelname)-7s - %(message)s"
, datefmt = "%Y-%m-%d %H:%M:%S"
)
- )
- cls.L.addHandler (lh)
- return cls._log_view
- # end def Log_View
+ handler.setFormatter (formatter)
+ if cmd.log_file_level :
+ handler.setLevel (cmd.log_file_level)
+ cls.L.addHandler (handler)
+ return cls.L
+ # end def Setup_Logging
+
+ @staticmethod
+ def snow (user = None) :
+ date = datetime.datetime.now ().strftime ("%Y-%m-%d %H-%M-%S")
+ if user :
+ return f"{date} -- {user}"
+ return date
+ # end def snow
+
+ @classmethod
+ def Load_Yaml (cls, file_name) :
+ with open (file_name, "r") as f :
+ return Attr_Dict (yaml.safe_load (f))
+ # end def Load_Yaml
+
+ def load_config (self, file_name) :
+ with open (file_name, "r") as f :
+ self.config = Attr_Dict (yaml.safe_load (f))
+ self.L.info ("Configuration loaded from %s", file_name)
+ return self.config
+ # end def load_config
+
+ def connect_to_db (self) :
+ if self.config.get ("database", "redis") == "mqtt" :
+ return MQTT \
+ (self.config, self._db_connected, self._db_changes)
+ return Redis (self.config, self._db_connected, self._db_changes)
+ # end def connect_to_db
+
+ def _db_connected (self, client) : pass
+
+ def _db_changes (self, key, value) : pass
+
+ def change_basket_user (self, basket_no, user, data) :
+ if user != data.get ("user") :
+ self.L.debug \
+ ( "Change basket user for %s from %s to %s"
+ , basket_no, data.get ("user", "<>"), user
+ )
+ data ["user"] = user
+ bt = self.mqtt.basket
+ data ["changed"] = self.snow (self.User)
+ self.client.publish \
+ ( bt.user.format (basket_no = basket_no)
+ , data ["user"]
+ )
+ self.client.publish \
+ ( bt.changed.format (basket_no = basket_no)
+ , data ["changed"]
+ )
+ return True
+ return False
+ # end def change_basket_user
+
+ def change_basket_state (self, basket_no, state, data) :
+ old_state = int (data.get ("state", "0"))
+ if state != old_state :
+ self.L.debug \
+ ( "Change basket status for %s from %s to %s"
+ , basket_no, old_state, state
+ )
+ data ["state"] = str (state)
+ bt = self.mqtt.basket
+ data ["changed"] = self.snow (self.User)
+ self.client.publish \
+ ( bt.state.format (basket_no = basket_no)
+ , data ["state"]
+ )
+ self.client.publish \
+ ( bt.changed.format (basket_no = basket_no)
+ , data ["changed"]
+ )
+ return True
+ return False
+ # end def change_basket_state
+
+ _key = b'Mgl1Wae_JILP0rbLazTL5gOpfrp4tqOFOiEsD_IUH8c='
+
+ @classmethod
+ def Decrypt (cls, data: bytes) -> bytes:
+ """Decrypt the passed data"""
+ return Fernet (cls._key).decrypt (data)
+ # end def Decrypt
+
+ @classmethod
+ def Encrypt (cls, data: bytes) -> bytes:
+ """Encrypt the passed data"""
+ return Fernet (cls._key).encrypt (data.encode ("ascii"))
+ # end def Encrypt
# end class App_State
+
+
+class _Database_Interface_ (App_State) :
+
+ def __init__ (self, config, connected, changes) :
+ self.connected = connected
+ self.changes = changes
+ # end def __init__
+
+ def subscribe (self, key) : pass
+ def publish (self, key, value) : pass
+ def delete (self, key): pass
+ def get_all_values (self, key) : pass
+
+# end class _Database_Interface_
+
+class MQTT (_Database_Interface_) :
+
+ def __init__ (self, config, connected, changes) :
+ super ().__init__ (config, connected, changes)
+ self.client = client = mqtt.Client ()
+ client.on_connect = self._on_connect
+ client.on_message = self._on_message
+ mqc = config ["mqtt"]
+ pw = self.Decrypt (mqc.password)
+ client.username_pw_set (mqc.user, pw)
+ self.L.info ("Try to connect to MQTT %s:%s", mqc.broker, mqc.port)
+ client.connect (mqc.broker, port = mqc.port)
+ self._mqtt_thread = threading.Thread \
+ (target = self.client.loop_forever, daemon = True)
+ self._mqtt_thread.start ()
+ # end def __init__
+
+ def _on_connect (self, client, user_data, flags, rc) :
+ self.L.info ("Connected to MQTT Broker")
+ if self.connected :
+ self.connected (self)
+ # end def _on_connect
+
+ def _on_message (self, client, user_data, msg) :
+ self.L.debug5 ("MQTT Message: %s: %s", msg.topic, msg.payload)
+ if self.changes :
+ self.changes (msg.topic, msg.payload)
+ # end def _on_message
+
+ def subscribe (self, key, recursie) :
+ if recursie :
+ key = f"{key}/#"
+ self.client.subscribe (key)
+ # end def subscribe
+
+ def publish (self, key, value) :
+ self.client.publish (key, payload = value, retain = True)
+ self.L.debug5 ("Publish to topic %s: %s", key, value)
+ # end def publish
+
+ def delete (self, key) :
+ self.client.publish (key, payload = None, retain = False)
+ self.L.debug5 ("Delete topic %s", key)
+ # end def delete
+
+# end class MQTT
+
+class Redis (_Database_Interface_) :
+
+ def __init__ (self, config, connected, changes) :
+ super ().__init__ (config, connected, changes)
+ RC = config.redis
+ host = RC.host
+ port = RC.get ("port", 6379)
+ self.L.info ("Try to connect to redis %s:%s", host, port)
+ self.client = client = redis.StrictRedis \
+ ( host = host
+ , port = port
+ , decode_responses = True
+ )
+ self.subscribtions = {}
+ if connected :
+ connected (self)
+ threading.Thread (target = self._listen, daemon = True).start ()
+ # end def __init__
+
+ def _listen (self) :
+ self.L.debug ("Start redis listener",)
+ pubsub = self.client.pubsub (ignore_subscribe_messages = True)
+ pubsub.execute_command (b"CLIENT", b"ID")
+ client_id = pubsub.connection.read_response ()
+ pubsub.execute_command \
+ (f"CLIENT TRACKING on REDIRECT {client_id} BCAST")
+ res = pubsub.connection.read_response ()
+ self.L.debug ("Redis tracking enabled: %s", res)
+ pubsub.subscribe ('__redis__:invalidate')
+ while True :
+ m = pubsub.get_message ()
+ if m and m ["channel"] == "__redis__:invalidate" :
+ if self.changes :
+ for key in m ["data"] :
+ for k, r in self.subscribtions.items () :
+ if ( ( r and key.startswith (k))
+ or (not r and (key == k))
+ ) :
+ val = self.client.get (key)
+ self.changes (key.replace (":", "/"), val)
+ else :
+ time.sleep (0.01)
+ # end def _listen
+
+ def get_all_values (self, key) :
+ key = key.replace ("/", ":")
+ for k in self.client.keys (f"{key}:*") :
+ v = self.client.get (k)
+ yield k.replace (":", "/"), v
+ # end def get_all_values
+
+ def subscribe (self, key, recursive) :
+ self.subscribtions [key.replace ("/", ":")] = recursive
+ # end def subscribe
+
+ def publish (self, key, value) :
+ key = key.replace ("/", ":")
+ self.client.set (key, value)
+ self.L.debug5 ("Set %s: %s", key, value)
+ # end def publish
+
+ def delete (self, key) :
+ key = key.replace ("/", ":")
+ self.client.delete (key)
+ self.L.debug5 ("Delete key %s", key)
+ # end def delete
+
+
+# end class Redis
+if __name__ == "__main__" :
+ import argparse
+ parser = argparse.ArgumentParser ()
+ parser.add_argument ("-e", "--encrypt", type = str)
+ parser.add_argument ("-d", "--decrypt", type = str)
+ cmd = parser.parse_args ()
+
+ if cmd.encrypt :
+ print (App_State.Encrypt (cmd.encrypt))
+ if cmd.decrypt :
+ print (App_State.Decrypt (cmd.decrypt))
diff --git a/Login.py b/Login.py
index 2bfaab3..986a26e 100644
--- a/Login.py
+++ b/Login.py
@@ -1,11 +1,13 @@
from QT import QtWidgets, QT
-import schema
-
+from User import User
+import yaml
class Login_Dialog (QtWidgets.QDialog) :
- def __init__ (self, username = "") :
+ def __init__ (self, config_file, username = "") :
super ().__init__ ()
+ cfg = User.Load_Yaml (config_file)
+ self.users = User (cfg.users)
self.setWindowTitle ("Login")
self.layout = L = QtWidgets.QFormLayout (self)
self.user_name = QtWidgets.QLineEdit ()
@@ -26,7 +28,6 @@ class Login_Dialog (QtWidgets.QDialog) :
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 ()
@@ -39,18 +40,14 @@ class Login_Dialog (QtWidgets.QDialog) :
# 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")
+ un = self.user_name.text ()
+ pw = self.password.text ()
+ pw_ok = self.users.check_password (un, pw, True)
+ self.login.setEnabled (pw_ok)
+ if not pw_ok :
+ self.error.setText ("Passwort falsch")
+ else :
+ self.accept ()
# end def _login
# end class Login_Dialog
\ No newline at end of file
diff --git a/Order_Display.py b/Order_Display.py
index 49ef304..e8bb180 100644
--- a/Order_Display.py
+++ b/Order_Display.py
@@ -1,12 +1,13 @@
from QT import QtWidgets, QtGui, QT
+from QT.Input_Dialog import Input_Dialog
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
import pathlib
+from lxml import etree
class Order_List (_With_Table_Widget_) :
@@ -16,7 +17,8 @@ class Order_List (_With_Table_Widget_) :
("Warenkorb", "Status", "Bearbeiter", "Kommission", "Kunde", "Linie")
State_Map = \
- { 0 : "neu"
+ { -1 : "wird gelöscht"
+ , 0 : "neu"
, 10 : "in Bearbeitung"
, 20 : "im CAD geöffnet"
, 25 : "CNC Erzeugung läuft"
@@ -24,6 +26,7 @@ class Order_List (_With_Table_Widget_) :
, 95 : "Zurückgesetzt"
, 99 : "Storno"
}
+
def __init__ (self, parent = None) :
super ().__init__ (parent)
self._view.setContextMenuPolicy (QT.CustomContextMenu)
@@ -34,140 +37,227 @@ class Order_List (_With_Table_Widget_) :
self._reset_order = ACT (self.tr ("Auftrag zurücksetzen"))
self._cancel = ACT (self.tr ("Auftrag stronieren"))
self._send_to_odoo = ACT (self.tr ("Auftrag ans Odoo schicken"))
- self._actions = ACT (self.tr ("Aktionsliste"))
self._reset_state = ACT (self.tr ("Status zurücksetzen"))
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._reset_state.triggered.connect (self._reset_order_state)
self._cad_processes = {}
+ self.selection_model.selectionChanged.connect (self._show_details)
+ self._view.setSortingEnabled (True)
# end def __init__
+ @property
+ def baskets (self) :
+ return self.parent ().baskets
+ # end def baskets
+
+ @property
+ def config (self) :
+ return self.parent ().config
+ # end def config
+
+ @property
+ def L (self) :
+ return self.root.L
+ # end def config
+
+ @property
+ def root (self) :
+ return self.parent ().parent ().parent ()
+ # end def root
+
+ def _show_details (self, selected, deselected) :
+ row_data = self._view.selectedItems ()
+ if row_data :
+ bno = row_data [0].text ()
+ basket = self.baskets [bno]
+ self.parent ()._order_detail.update_view (bno, basket)
+ # end def _current_changed
+
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, active = True)
- 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
- cm = QtWidgets.QMenu ()
- if state < 20 :
- 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)
+ basket = self.baskets [bno]
+ user = basket.get ("user")
+ if user and user != App_State.User :
+ 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
+ if self.root.change_basket_user (bno, App_State.User, basket) :
+ self.update_basket (bno, basket, False, row_data [0].row ())
+ state = int (basket ["state"])
+ cm = QtWidgets.QMenu ()
+ if state < 20 :
+ cm.addAction (self._open_cad)
+ cm.addAction (self._recreate_cnc)
cm.addSeparator ()
- cm.addAction (self._reset_state)
- cm.exec (self._view.mapToGlobal (pos))
+ cm.addAction (self._send_to_odoo)
+ cm.addSeparator ()
+ cm.addAction (self._reset_order)
+ cm.addAction (self._cancel)
+ cm.addSeparator ()
+ cm.addSeparator ()
+ cm.addAction (self._reset_state)
+ cm.exec (self._view.mapToGlobal (pos))
# end def _show_context_menu
- def update_order (self, order, new, row = None) :
- if order.active :
- state = self.State_Map [order.state]
- 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, 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 :
- App_State.L.error \
- ("Could not find row for order %s", order.basket_no)
- return
- self._update_read_only_string (row, 1, state)
- self._update_read_only_string (row, 2, order.user.username)
+ def update_basket (self, basket_no, data, new, row = None) :
+ state = int (data.get ("state", "0") or "-1")
+ if new :
+ row = self._view.rowCount ()
+ self._view.setRowCount (row + 1)
else :
- for row in range (self._view.rowCount ()) :
- if self._view.item (row, 0).text () == order.basket_no :
- self._view.removeRow (row)
- return order.basket_no
- # end def update_order
+ if row is None :
+ for row in range (self._view.rowCount ()) :
+ if self._view.item (row, 0).text () == basket_no :
+ break
+ else :
+ self.root.L.error \
+ ("Could not find row for order %s", basket_no)
+ return
+ self._add_read_only_string (row, 0, basket_no)
+ self._add_read_only_string (row, 1, self.State_Map [state])
+ self._add_read_only_string (row, 2, data.get ("user", ""))
+ self._add_read_only_string (row, 3, data.get ("commission", ""))
+ self._add_read_only_string (row, 4, data.get ("shop_user", ""))
+ self._add_read_only_string (row, 5, data.get ("line", ""))
+ # end def update_basket
- def remove_orders (self, orders) :
- for bno in orders :
- for row in range (self._view.rowCount ()) :
- if self._view.item (row, 0).text () == bno :
- self._view.removeRow (row)
- break
- # end def remove_orders
+ def remove_basket (self, basket_no) :
+ for row in range (self._view.rowCount ()) :
+ if self._view.item (row, 0).text () == basket_no :
+ self._view.removeRow (row)
+ return
+ # end def remove_basket
+
+ def add_xml_element (self, root, tag, text = None, ** kw) :
+ result = etree.SubElement (root, tag, **kw)
+ if text is not None :
+ result.text = str (text)
+ return text
+ # end def add_xml_element
+
+ def _add_order_line_to_xml_file (self, xml_file : pathlib.Path, xml_element) :
+ xml = etree.parse (xml_file).getroot ()
+ bl = xml.xpath ("//BuilderList") [0]
+ bl.append (xml_element)
+ ori_name = xml_file.parent / "original" / xml_file.name
+ if not ori_name.exists () :
+ ori_name.parent.mkdir (exist_ok = True)
+ self.L.debug ("Rename %s to %s", xml_file, ori_name)
+ xml_file.rename (ori_name)
+ with xml_file.open ("wb") as f :
+ f.write (etree.tostring (xml, pretty_print = True, encoding = "utf-8"))
+ self.L.info ("Patched XML file written %s", xml_file)
+ # end def _add_order_line_to_xml_file
+
+ def add_cnc_orderline (self) :
+ row_data = self._view.selectedItems ()
+ if not row_data :
+ return
+ r = Input_Dialog \
+ ( "CNC Bearbeitung verrechnen"
+ , _MIN_WIDTH = 400
+ , article = {"default": "TZ_PROCESSING_FEE", "title" : "Artikel", "required": True}
+ , count = {"default": 1, "title" : "Anzahl", "required": True, "type" : "int"}
+ , text = {"default": "Sonderbearbeitung für Position", "title" : "Text", "required": True}
+ , price = {"default": 60, "title" : "Preis"
+ , "type" : "int"
+ }
+ ).run ()
+ if r :
+ bno = row_data [0].text ()
+ basket = self.baskets [bno]
+ positions = basket ["positions"]
+ pos = int (positions [-1] ["pos"].split (".") [0])
+ hpos = int (positions [-1] ["hpos"])
+ tax = positions [0] ["tax"]
+ price = r ["price"]
+ tax_amount= price / 100 * tax
+ pos += 1
+ hpos += 1
+ positions.append \
+ ( { "art_name" : r ["article"]
+ , "count" : r ["count"]
+ , "pos" : str (pos)
+ , "hpos" : str (hpos)
+ , "text" : r ["text"]
+ , "tax" : tax
+ , "price" : price
+ }
+ )
+ self.parent ()._order_detail.update_view (bno, basket)
+ xset = etree.Element ("Set", LineNo = str (pos))
+ AXE = self.add_xml_element
+ AXE (xset, "hierarchicalPos", hpos)
+ AXE (xset, "Pname", r ["article"])
+ AXE (xset, "Count", r ["count"])
+ AXE (xset, "PVarString", f"IDBEXTID:=1|ART_NAME:={r ['article']}")
+ AXE (xset, "ARTICLE_TEXT_INFO1", r ["text"])
+ AXE (xset, "ARTICLE_TEXT_INFO2", r ["text"])
+ AXE (xset, "ARTICLE_TEXT_INFO3")
+ AXE (xset, "ARTICLE_TEXT_INFO4", 0)
+ AXE (xset, "ARTICLE_TEXT_INFO5", r ["text"])
+ AXE (xset, "ARTICLE_TEXT_INFO6", r ["text"])
+ AXE (xset, "ARTICLE_TEXT_INFO7")
+ AXE (xset, "ARTICLE_PRICE_INFO1", price)
+ AXE (xset, "ARTICLE_PRICE_INFO2", price)
+ AXE (xset, "ARTICLE_PRICE_INFO3", price)
+ AXE (xset, "ARTICLE_PRICE_INFO4", tax)
+ AXE (xset, "ARTICLE_PRICE_INFO5", round (tax_amount, 2))
+ AXE (xset, "ARTICLE_PRICE_INFO6", round (price + tax_amount, 2))
+ breakpoint ()
+ OR = pathlib.Path (self.config.directories.order_ready)
+ AR = pathlib.Path (self.config.directories.sim_root) / "Archive"
+ ## first patch the XML in the archive directory
+ self._add_order_line_to_xml_file (AR / f"{bno}.xml", xset)
+ ## than patch the file in the order-ready directory
+ self._add_order_line_to_xml_file (OR / f"{bno}.xml", xset)
+ return True
+ return False
+ # end def add_cnc_orderline
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, active = True)
- if order.state < 20 :
- order.state = 20
- self.update_order (order, False, row_data [0].row ())
- order_dwg = r"%s\ImOrder\%s\%s.dwg" \
- % (state.imos_factory_root, bno, bno)
- self._cad_processes [bno] = p = subprocess.Popen \
- ( ( order_dwg, ), 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
- )
+ bno = row_data [0].text ()
+ basket = self.baskets [bno]
+ state = int (basket.get ("state", 0))
+ if state < 20 :
+ if self.root.change_basket_state (bno, 20, basket) :
+ self.update_basket (bno, basket, False, row_data [0].row ())
+ order_dwg = r"%s\ImOrder\%s\%s.dwg" \
+ % (self.config.directories.factory_root, bno, bno)
+ self._cad_processes [bno] = p = subprocess.Popen \
+ ( ( order_dwg, ), shell = True )
+ threading.Thread \
+ ( target = self._wait_for_cad_close
+ , args = (bno, )
+ , daemon = True
+ ).start ()
# end def _open_order_in_cad
def _wait_for_cad_close (self, basket_no) :
- App_State.L.info ("Auftrag %s im CAD geöffnet", basket_no)
+ self.L.info ("Auftrag %s im CAD geöffnet", 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, active = True)
- order.state = 10
- self.update_order (order, False)
- App_State.Create_Action \
- ( 25
- , f"IMOS CAD closed {order.basket_no}: Error code {p.poll ()}"
- , order
- )
- App_State.L.info ("CAD für Auftrag %s beendet", basket_no)
+ basket = self.baskets [basket_no]
+ if self.root.change_basket_state (basket_no, 10, basket) :
+ self.update_basket (basket_no, basket, False)
+ self.L.info ("Auftrag %s im CAD geschlossen", basket_no)
# end def _wait_for_cad_close
def _start_recreate_cnc (self) :
@@ -185,101 +275,80 @@ class Order_List (_With_Table_Widget_) :
"""
- with schema.orm.db_session () :
- state = schema.State.get ()
- order = schema.Order.get (basket_no = bno, active = True)
- job_file_name = ( pathlib.Path (state.imos_sim_root)
- / App_State.sim_batches
- / f"{bno}.xml"
- )
- txt_file = ( pathlib.Path (state.imos_factory_root)
- / App_State.imos_done_dir
- / f"{bno}.txt"
- )
- order.state = 25
- with job_file_name.open ("w") as f :
- f.write (xml)
- if txt_file.exists () :
- txt_file.unlink ()
- self.update_order (order, False, row_data [0].row ())
- threading.Thread \
- ( target = self._wait_for_cnc_done
- , args = (bno, )
- , daemon = True
- ).start ()
- App_State.Create_Action \
- ( 30, f"Start CNC generation for {bno}", order)
+ job_file_name = ( pathlib.Path (self.config.directories.sim_root)
+ / App_State.sim_batches
+ / f"{bno}.xml"
+ )
+ txt_file = ( pathlib.Path (self.config.directories.factory_root)
+ / App_State.imos_done_dir
+ / f"{bno}.txt"
+ )
+ with job_file_name.open ("w") as f :
+ f.write (xml)
+ if txt_file.exists () :
+ txt_file.unlink ()
+ basket = self.baskets [bno]
+ if self.root.change_basket_state (bno, 25, basket) :
+ self.update_basket (bno, basket, False, row_data [0].row ())
+ threading.Thread \
+ ( target = self._wait_for_cnc_done
+ , args = (bno, )
+ , daemon = True
+ ).start ()
# end def _start_recreate_cnc
def _wait_for_cnc_done (self, basket_no) :
- with schema.orm.db_session () :
- state = schema.State.get ()
- txt_file = ( pathlib.Path (state.imos_factory_root)
+ txt_file = ( pathlib.Path (self.config.directories.factory_root)
/ App_State.imos_done_dir
/ f"{basket_no}.txt"
)
- err_file_name = ( pathlib.Path (state.imos_sim_root)
+ err_file_name = ( pathlib.Path (self.config.directories.sim_root)
/ App_State.sim_batches
/ "Error"
/ f"{basket_no}.xml"
)
while not txt_file.exists () and not err_file_name.exists ():
time.sleep (0.5)
- App_State.L.debug (f"wait for {txt_file}/{err_file_name}")
+ self.L.debug (f"wait for {txt_file}/{err_file_name}")
if err_file_name.exists () :
text = f"Fehler bei der CNC Erzeugung for {basket_no}"
else :
text = f"CNC Erzeugung für {basket_no} abgeschlossen"
- with schema.orm.db_session () :
- order = schema.Order.get (basket_no = basket_no, active = True)
- order.state = 10
- self.update_order (order, False)
- App_State.Create_Action (35, text, order)
+ self.L.info (text.format (basket_no = basket_no))
+ basket = self.baskets [basket_no]
+ if self.root.change_basket_state (basket_no, 10, basket) :
+ self.update_basket (basket_no, basket, False)
# end def _wait_for_cnc_done
def _reset_order_in_webshop (self) :
row_data = self._view.selectedItems ()
if row_data :
bno = row_data [0].text ()
- App_State.L.error ("Reset order in webshop %s", bno)
+ self.L.error ("Reset order in webshop %s", bno)
# end def _reset_order_in_webshop
def _cancel_order (self) :
row_data = self._view.selectedItems ()
if row_data :
bno = row_data [0].text ()
- App_State.L.error ("Cancel Order %s", bno)
+ self.L.error ("Cancel Order %s", bno)
# end def _cancel_order
def _send_oder_to_odoo (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, active = True)
- factory = pathlib.Path (state.imos_factory_root)
- for ext in ".xml", ".txt" :
- sf = factory / App_State.imos_done_dir / f"{bno}{ext}"
- df = factory / App_State.pgiq_dir / f"{bno}{ext}"
- sf.rename (df)
- App_State.L.info ("Auftrag %s für Odoo freigegeben", bno)
- App_State.Create_Action \
- (40, f"Auftrag {bno} für Odoo freigegeben", order)
- order.active = False
- self.update_order (order, False)
+ bno = row_data [0].text ()
+ basket = self.baskets [bno]
+ state = int (basket ["state"])
+ D = self.config.directories
+ factory = pathlib.Path (D.factory_root)
+ for ext in ".xml", ".txt" :
+ sf = factory / D.order_ready / f"{bno}{ext}"
+ df = factory / D.pgiq_dir / f"{bno}{ext}"
+ sf.rename (df)
+ self.L.info ("Auftrag %s für Odoo freigegeben", 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, active = True)
- Action_Dialog \
- (order.actions.order_by (schema.orm.desc (schema.Action.date))).exec ()
- # end def _show_actions
-
def _reset_order_state (self) :
row_data = self._view.selectedItems ()
if row_data :
@@ -293,15 +362,9 @@ class Order_List (_With_Table_Widget_) :
)
if r == QtWidgets.QMessageBox.No :
return
- with schema.orm.db_session () :
- order = schema.Order.get (basket_no = bno, active = True)
- order.state = 10
- self.update_order (order, False)
- App_State.Create_Action \
- ( 15
- , f"Status wird auf 10 gesetzt"
- , order
- )
+ basket = self.baskets [bno]
+ if self.root.change_basket_state (bno, 10, basket) :
+ self.update_basket (bno, basket, False)
# end def _reset_order_state
# end class Order_List
@@ -311,40 +374,50 @@ class Order_Detail (_With_Table_Widget_) :
settings_group = "order-detail"
Header = "Auftrags Detail"
Columns = \
- ("Position", "Artikel", "Preis")
+ ("Position", "Anzahl", "Artikel", "Preis")
+
+ def update_view (self, basket_no, basket) :
+ if basket :
+ positions = basket.get ("positions", ())
+ self._view.setRowCount (len (positions))
+ for i, p in enumerate (positions) :
+ self._update_read_only_string (i, 0, str (p ["pos"]))
+ self._update_read_only_string (i, 1, str (p ["count"]))
+ self._update_read_only_string (i, 2, str (p ["text"]))
+ self._update_read_only_string (i, 3, "%7.2f" % p ["price"])
+ # end def update_view
# end class Order_Detail
class Order_Display (QtWidgets.QSplitter) :
- def __init__ (self, parent) :
+ def __init__ (self, config, parent) :
super ().__init__ (parent)
+ self.config = config
self._order_list = Order_List ()
self._order_detail = Order_Detail ()
self.addWidget (self._order_list)
self.addWidget (self._order_detail)
- self.orders = {}
+ self.baskets = {}
# end def __init__
- def update_orders (self) :
- orders = set (self.orders.keys ())
- with schema.orm.db_session :
- for o in schema.Order.select (active = True) :
- 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
- orders.discard (o.basket_no)
- self._order_list.remove_orders (orders)
- for o in orders :
- self.orders.pop (o, None)
- # end def update_orders
-
+ def update_basket (self, basket_no, data) :
+ new = basket_no not in self.baskets
+ if data is not None :
+ if basket_no not in self.baskets :
+ self.baskets [basket_no] = data
+ else :
+ self.baskets [basket_no].update (data)
+ self._order_list.update_basket (basket_no, self.baskets [basket_no], new)
+ else : ### remove order
+ self._order_list.remove_basket (basket_no)
+ self.baskets.pop (basket_no, None)
+ # end def update_basket
+
+ def add_cnc_orderline (self) :
+ self._order_list.add_cnc_orderline ()
+ # end def add_cnc_orderline
+
def save_settings (self, settings) :
settings.beginGroup ("order-display")
settings.setValue ("geometry", self.saveGeometry ())
diff --git a/Order_Watch.py b/Order_Watch.py
index 6ceb227..883c36e 100644
--- a/Order_Watch.py
+++ b/Order_Watch.py
@@ -6,22 +6,14 @@ from filesystem_watcher import \
WindowsApiEmitterExceptionHandling, Directory_Handler
import time
import json
-import datetime
-class Order_Handler (Directory_Handler) :
+class Order_Handler (Directory_Handler, App_State) :
def __init__ (self, path, client, config) :
super ().__init__ (path)
self.client = client
- mqr = config.mqtt ["topic-root"]
- baskets = f"{mqr}/baskets"
- self.mqtt = Attr_Dict \
- ( basket_topic = f"{baskets}/{{basket_no}}"
- , basket_state = f"{baskets}/{{basket_no}}/state"
- , basket_user = f"{baskets}/{{basket_no}}/user"
- , basket_changed = f"{baskets}/{{basket_no}}/changed"
- , baskets = baskets
- )
+ self.config = config
+ self._setup_topics (config)
# end def __init__
def on_any_event (self, event) :
@@ -39,11 +31,10 @@ class Order_Handler (Directory_Handler) :
file = Path (event.src_path)
if file.suffix == ".xml" :
basket_no = file.stem
- for t in self.mqtt.values () :
- self.client.publish \
- ( t.format (basket_no = basket_no)
- , payload = None, retain = False
- )
+ for t in self.mqtt.basket.values () :
+ key = t.format (basket_no = basket_no)
+ self.client.delete (key)
+ self.L.debug ("Delete key %s", key)
self.L.info ("Order removed %s", basket_no)
super ().on_any_event (event)
# end def on_any_event
@@ -51,24 +42,28 @@ class Order_Handler (Directory_Handler) :
def new_order (self, xml_file : Path) :
try :
self.L.info ("New basket %s", xml_file.name)
- xml = etree.parse (xml_file).getroot ()
+ sim_xml = ( Path ( self.config.directories.sim_root)
+ / "Archive" / xml_file.name
+ )
+ if not sim_xml.exists () :
+ self.L.error ("Archive order xml does not exist: %s", sim_xml)
+ xml = etree.parse (sim_xml).getroot ()
com = xml.xpath ("//TEXT_SHORT") [0].text
user, line = xml.xpath ("//INFO8") [0].text.split ("/")
basket_no = xml_file.stem
+ bt = self.mqtt.basket
self.client.publish \
- ( self.mqtt.basket_state.format (basket_no = basket_no)
- , payload = "new", retain = True
- )
+ ( bt.state.format (basket_no = basket_no), 0)
self.client.publish \
- ( self.mqtt.basket_changed.format (basket_no = basket_no)
- , payload = App_State.snow ("import"), retain = True
+ ( bt.changed.format (basket_no = basket_no)
+ , App_State.snow ("import")
)
basket = dict \
- (user = user, line = line, commission = com)
+ (shop_user = user, line = line, commission = com)
basket ["positions"] = self._scan_order_pos (xml)
self.client.publish \
- ( self.mqtt.basket_topic.format (basket_no = basket_no)
- , payload = json.dumps (basket), retain = True
+ ( bt.data.format (basket_no = basket_no)
+ , json.dumps (basket)
)
self.L.info ("Created new basket %s", basket_no)
except Exception as e :
@@ -82,13 +77,17 @@ class Order_Handler (Directory_Handler) :
art_name = a.xpath ("Pname") [0].text.strip ()
count = int (a.xpath ("Count") [0].text)
pos = a.get ("LineNo")
+ hpos = a.xpath ("hierarchicalPos") [0].text
text = a.xpath ("ARTICLE_TEXT_INFO1" ) [0].text.strip ()
price = float (a.xpath ("ARTICLE_PRICE_INFO1") [0].text)
+ tax = float (a.xpath ("ARTICLE_PRICE_INFO4") [0].text)
result.append \
( dict ( art_name = art_name
, count = count
- , pos = pos
+ , pos = pos
+ , hpos = hpos
, text = text
+ , tax = tax
, price = price
)
)
@@ -102,8 +101,8 @@ class Order_Watch (App_State) :
order_incomming_dir = r"n:\glueck\watch-me\inbox"
def __init__ (self, config_file) :
- self.load_config (config_file)
- self.client = self.connect_to_mqtt ()
+ self.load_config (config_file)
+ self.client = self.connect_to_db ()
# end def __init__
def _start_watchdog (self) :
@@ -139,7 +138,7 @@ if __name__ == "__main__" :
parser.add_argument ("config_file", type = str)
cmd = parser.parse_args ()
- _Logger_.L = Order_Watch.Setup_Logging (cmd)
+ _Logger_.L = App_State.Setup_Logging (cmd)
ow = Order_Watch (cmd.config_file)
ow.run ()
\ No newline at end of file
diff --git a/designbox_orders.py b/designbox_orders.py
index a1f485e..9ce8985 100644
--- a/designbox_orders.py
+++ b/designbox_orders.py
@@ -1,16 +1,17 @@
import os
from QT.Main_Window import Main_Window, QtWidgets
from QT import QT
+from Log_View import Log_View, Log_Handler
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
import logging
+import threading
+import json
-class Order_Processing (Main_Window) :
+class Order_Processing (Main_Window, App_State) :
APP_NAME = "Designbox Orders"
VERSION = "0.5"
@@ -20,18 +21,27 @@ class Order_Processing (Main_Window) :
@classmethod
def _load_app_settings (cls, settings) :
settings.beginGroup ("TempSettings")
- result = {"database" : settings.value ("database")}
- result = {"username" : settings.value ("username")}
+ result = { "config_file" : settings.value ("config_file")
+ , "user_name" : settings.value ("user_name")
+ }
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)
+ self._add_action \
+ ( "add_cnc_orderline", "CNC Bearbeitung verrechnen"
+ , self.add_cnc_orderline
+ )
# end def _setupActions
def _setupGui (self) :
+ settings = self.settings
+ settings.beginGroup ("TempSettings")
+ config_file = settings.value ("config_file")
+ settings.endGroup ()
+ self.load_config (config_file)
self.main_menu = QtWidgets.QMenuBar (self)
self.setMenuBar (self.main_menu)
file = QtWidgets.QMenu ("& File")
@@ -41,157 +51,53 @@ class Order_Processing (Main_Window) :
for a, l in ( ( self.actions.exit
, (file, self.tool_bar_file)
)
- , ( (self.actions.test), (self.tool_bar_file, ))
+ , ( (self.actions.add_cnc_orderline), (self.tool_bar_file, ))
) :
for x in l :
x.addAction (a)
sp = QtWidgets.QSplitter (self)
- self._order_display = Order_Display (self)
- self._log = App_State.Log_View (parent = self)
+ self._order_display = Order_Display (self.config, self)
+ self.create_log_view (parent = self)
sp.addWidget (self._order_display)
- sp.addWidget (self._log)
+ sp.addWidget (self._log_view)
sp.setOrientation (QT.Vertical)
self.setCentralWidget (sp)
- 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 ()
+ self._setup_topics ()
+ self.L.info ("User: %s", App_State.User)
+ self.client = self.connect_to_db ()
# 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 _db_connected (self, client) :
+ topic = self.mqtt.baskets
+ client.subscribe (topic, True)
+ self.L.debug ("Subscribe to %s", topic)
+ for k, v in client.get_all_values (topic) :
+ self._db_changes (k, v)
+ # end def _mqtt_connected
- def scan_orders_directory (self) :
- with schema.orm.db_session :
- state = schema.State.get ()
- sim_archive = pathlib.Path (state.imos_sim_root) / "Archive"
- iroot = pathlib.Path (state.imos_factory_root)
- orders = self._find_orders (iroot / App_State.imos_done_dir)
- with schema.orm.db_session :
- for o in orders :
- self._update_order_in_database (sim_archive, o)
- orders = set (o.stem for o in orders)
- for o in schema.Order.select (active = True) :
- if o.basket_no not in orders :
- o.active = False
- self._order_display.update_orders ()
- # end def scan_orders_directory
-
- def _update_order_in_database (self, sim_archive, xml_file) :
- basket_no = xml_file.stem
- if True :
- order = schema.Order.get (basket_no = basket_no, active = True)
- xml_file = sim_archive / xml_file.name
- xml = etree.parse (xml_file).getroot ()
- com = xml.xpath ("//TEXT_SHORT") [0].text
- user,line = xml.xpath ("//INFO8") [0].text.split ("/")
- if not order :
- 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 = f"Auftrag importiert {basket_no}"
- , order = order
- )
+ def _db_changes (self, key, value) :
+ bst = self.mqtt.baskets
+ if key.startswith (bst) :
+ key = key [len (bst) + 1:]
+ parts = key.split ("/")
+ basket_no = parts.pop (0)
+ if not parts :
+ if value :
+ data = json.loads (value)
+ else :
+ data = None
else :
- pl = schema.Production_Line.get (short = line)
- if ( (order.commission != com)
- or (order.imos_user != user)
- or (order.production_line != pl)
- ) :
- order.set ( commission = com
- , imos_user = user
- , production_line = pl
- )
- schema.Action \
- ( user = schema.User [App_State.user_id]
- , date = schema.datetime.now ()
- , action_kind = 2
- , action =
- f"Auftrag geändert fom XML {basket_no}"
- , order = order
- )
- self._scan_order_pos (order, xml)
- order.active = True
- # end def _update_order_in_database
-
- def _scan_order_pos (self, order, xml) :
- bl = xml.xpath ("//BuilderList") [0]
- changes = 0
- for a in bl.getchildren () :
- art_name = a.xpath ("Pname") [0].text.strip ()
- count = int (a.xpath ("Count") [0].text)
- pos = a.get ("LineNo")
- text = a.xpath ("ARTICLE_TEXT_INFO1" ) [0].text.strip ()
- price = float (a.xpath ("ARTICLE_PRICE_INFO1") [0].text)
- article = schema.Odoo_Article.get (name = art_name)
- if not article :
- article = schema.Odoo_Article \
- ( name = art_name
- , default_text = art_name
- , default_price = 0
- )
- App_State.Create_Action \
- ( 100
- , f"Artikel {art_name} wurde in der Datenbank angelegt"
- , order
- )
- order_line = schema.Order_Line.get (order = order, position = pos)
- if not order_line :
- order_line = schema.Order_Line \
- ( order = order
- , position = pos
- , count = count
- , text = text
- , price = price
- , odoo_article = article
- )
- App_State.Create_Action \
- ( 110
- , f"Auftragszeile {pos} für {order.basket_no} wurde in der "
- "Datenbank angelegt"
- , order
- )
- changes += 1
- else :
- if ( (order_line.text != text)
- or (order_line.count != count)
- or (order_line.price != price)
- ) :
- print ("Update")
- order_line.text = text
- order_line.price = price
- order_line.count = count
- changes += 1
- App_State.Create_Action \
- ( 111
- , f"Auftragszeile {pos} für {order.basket_no} wurde "
- "in der Datenbank geändert"
- , order
- )
- # end def _scan_order_pos
+ if isinstance (value, bytes) :
+ value = value.decode ("utf-8")
+ data = {parts [0] : value}
+ self.L.debug ("Change basket %s: %s", basket_no, data)
+ self._order_display.update_basket (basket_no, data)
+ # end def _mqtt_message
+ def add_cnc_orderline (self) :
+ self._order_display.add_cnc_orderline ()
+ # end def add_cnc_orderline
+
def _restore_settings (self) :
super ()._restore_settings ()
sp = self.centralWidget ()
@@ -201,7 +107,7 @@ class Order_Processing (Main_Window) :
settings.setValue ("windowState", sp.saveState ())
settings.endGroup ()
self._order_display.restore_settings (settings)
- self._log.restore_settings (settings)
+ self._log_view.restore_settings (settings)
# end def _restore_settings
def save_settings (self) :
@@ -213,47 +119,61 @@ class Order_Processing (Main_Window) :
sp.restoreState (settings.value ("windowState"))
settings.endGroup ()
self._order_display.save_settings (settings)
- self._log.save_settings (settings)
+ self._log_view.save_settings (settings)
settings.beginGroup ("TempSettings")
- settings.setValue ("user_id", App_State.user_id)
+
+ settings.setValue ("user_name", App_State.User)
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")
+ config_file = settings.value ("config_file")
+ user_name = settings.value ("user_name")
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)
+ login = Login_Dialog (config_file, user_name)
if login.exec () == 1 :
settings.beginGroup ("TempSettings")
- settings.setValue ("user_id", login.user_id)
+ settings.setValue ("user_name", App_State.User)
settings.endGroup ()
return True
else :
- App_State.L.error ("### Login skipped")
+ cls.L.error ("### Login skipped")
+ App_State.User = user_name
return True
# end def pre_main_window_action
+ def create_log_view (self, level = "INFO", parent = None) :
+ self.L.debug ("Log window created")
+ self._log_view = Log_View (parent)
+ lh = Log_Handler (self._log_view)
+ #lh.setFormatter \
+ # ( logging.Formatter
+ # ('%(asctime)s '
+ # '%(levelname)s - %(message)s'
+ # , datefmt = "%Y-%m-%d %H:%M:%S"
+ # )
+ # )
+ lh.setLevel (level)
+ lh.setFormatter \
+ ( logging.Formatter
+ ( "%(asctime)s - %(message)s"
+ , datefmt = "%Y-%m-%d %H:%M:%S"
+ )
+ )
+ self.L.addHandler (lh)
+ return self._log_view
+ # end def create_log_view
+
# 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)
+ parser = Order_Processing.Add_Logging_Attributes ()
+ parser.add_argument ("-s", "--settings-file", type = str)
+ parser.add_argument ("-c", "--config-file", type = str)
cmd = parser.parse_args ()
- logging.basicConfig \
- ( level = logging.DEBUG
- , format = "%(asctime)s - %(levelname)s - %(message)s"
- , datefmt = "%Y-%m-%d %H:%M:%S"
- )
+ App_State.Setup_Logging (cmd)
Order_Processing.run (Order_Processing.load_settings (cmd))
diff --git a/order_watch.yaml b/order_watch.yaml
index 2edd3c4..5f52685 100644
--- a/order_watch.yaml
+++ b/order_watch.yaml
@@ -4,3 +4,14 @@ mqtt:
user: imos
password: gAAAAABmTy4fipVAw10oKCE2aLWq79BF9Id5H8-lEEm8tOgcoaGgIJRWwBdR0nj-lrN-70hcLMHVqNudk7FTJjOvE0KOCkDr0A==
topic-root: imos-order
+redis:
+ host: odoo-test
+
+database: redis
+
+users: users.yaml
+directories:
+ order_ready: n:\glueck\watch-me\inbox
+ factory_root: n:\IMOS\Live-System\Factory
+ sim_root: n:\IMOS\Live-System\SIM
+ pgiq_dir: n:\glueck\watch-me\odoo
\ No newline at end of file
diff --git a/users.yaml b/users.yaml
new file mode 100644
index 0000000..0d65800
--- /dev/null
+++ b/users.yaml
@@ -0,0 +1,9 @@
+glueck:
+ active: true
+ password: 8625ff9e7b2f0916bf6e9ef6c163d3d6f873a7796146453bf236d52f57cb90a6
+user1:
+ active: true
+ password: 8625ff9e7b2f0916bf6e9ef6c163d3d6f873a7796146453bf236d52f57cb90a6
+user2:
+ active: true
+ password: 8625ff9e7b2f0916bf6e9ef6c163d3d6f873a7796146453bf236d52f57cb90a6