Update
parent
5009cf8ae4
commit
0cd18ab9f8
361
App_State.py
361
App_State.py
|
|
@ -1,65 +1,332 @@
|
|||
from Log_View import Log_View, Log_Handler
|
||||
import schema
|
||||
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}}"
|
||||
)
|
||||
# end def create_action
|
||||
, baskets = baskets
|
||||
)
|
||||
|
||||
# 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
|
||||
# ('<span class="log-time">%(asctime)s</span> '
|
||||
# '<span class="level-%(levelname)s">%(levelname)s</span> - %(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"
|
||||
)
|
||||
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
|
||||
)
|
||||
cls.L.addHandler (lh)
|
||||
return cls._log_view
|
||||
# end def Log_View
|
||||
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))
|
||||
|
|
|
|||
23
Login.py
23
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 ()
|
||||
|
|
@ -41,16 +42,12 @@ class Login_Dialog (QtWidgets.QDialog) :
|
|||
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 :
|
||||
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
|
||||
345
Order_Display.py
345
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,25 +37,53 @@ 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 :
|
||||
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
|
||||
|
|
@ -63,14 +94,9 @@ class Order_List (_With_Table_Widget_) :
|
|||
)
|
||||
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 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)
|
||||
|
|
@ -81,63 +107,138 @@ class Order_List (_With_Table_Widget_) :
|
|||
cm.addAction (self._reset_order)
|
||||
cm.addAction (self._cancel)
|
||||
cm.addSeparator ()
|
||||
cm.addAction (self._actions)
|
||||
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]
|
||||
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)
|
||||
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 :
|
||||
if self._view.item (row, 0).text () == basket_no :
|
||||
break
|
||||
else :
|
||||
App_State.L.error \
|
||||
("Could not find row for order %s", order.basket_no)
|
||||
self.root.L.error \
|
||||
("Could not find row for order %s", basket_no)
|
||||
return
|
||||
self._update_read_only_string (row, 1, state)
|
||||
self._update_read_only_string (row, 2, order.user.username)
|
||||
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
|
||||
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 :
|
||||
def remove_basket (self, basket_no) :
|
||||
for row in range (self._view.rowCount ()) :
|
||||
if self._view.item (row, 0).text () == bno :
|
||||
if self._view.item (row, 0).text () == basket_no :
|
||||
self._view.removeRow (row)
|
||||
break
|
||||
# end def remove_orders
|
||||
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 ())
|
||||
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" \
|
||||
% (state.imos_factory_root, bno, bno)
|
||||
% (self.config.directories.factory_root, bno, bno)
|
||||
self._cad_processes [bno] = p = subprocess.Popen \
|
||||
( ( order_dwg, ), shell = True )
|
||||
threading.Thread \
|
||||
|
|
@ -145,29 +246,18 @@ class Order_List (_With_Table_Widget_) :
|
|||
, 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) :
|
||||
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_) :
|
|||
</BatchJobs>
|
||||
</XML>
|
||||
"""
|
||||
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)
|
||||
job_file_name = ( pathlib.Path (self.config.directories.sim_root)
|
||||
/ App_State.sim_batches
|
||||
/ f"{bno}.xml"
|
||||
)
|
||||
txt_file = ( pathlib.Path (state.imos_factory_root)
|
||||
txt_file = ( pathlib.Path (self.config.directories.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 ())
|
||||
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 ()
|
||||
App_State.Create_Action \
|
||||
( 30, f"Start CNC generation for {bno}", order)
|
||||
# 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)
|
||||
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 / App_State.imos_done_dir / f"{bno}{ext}"
|
||||
df = factory / App_State.pgiq_dir / f"{bno}{ext}"
|
||||
sf = factory / D.order_ready / f"{bno}{ext}"
|
||||
df = factory / D.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)
|
||||
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,39 +374,49 @@ 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
|
||||
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 :
|
||||
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
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
, hpos = hpos
|
||||
, text = text
|
||||
, tax = tax
|
||||
, price = price
|
||||
)
|
||||
)
|
||||
|
|
@ -103,7 +102,7 @@ class Order_Watch (App_State) :
|
|||
|
||||
def __init__ (self, config_file) :
|
||||
self.load_config (config_file)
|
||||
self.client = self.connect_to_mqtt ()
|
||||
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 ()
|
||||
|
||||
|
|
@ -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,156 +51,52 @@ 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 :
|
||||
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
|
||||
data = None
|
||||
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 ()
|
||||
|
|
@ -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
|
||||
# ('<span class="log-time">%(asctime)s</span> '
|
||||
# '<span class="level-%(levelname)s">%(levelname)s</span> - %(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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
glueck:
|
||||
active: true
|
||||
password: 8625ff9e7b2f0916bf6e9ef6c163d3d6f873a7796146453bf236d52f57cb90a6
|
||||
user1:
|
||||
active: true
|
||||
password: 8625ff9e7b2f0916bf6e9ef6c163d3d6f873a7796146453bf236d52f57cb90a6
|
||||
user2:
|
||||
active: true
|
||||
password: 8625ff9e7b2f0916bf6e9ef6c163d3d6f873a7796146453bf236d52f57cb90a6
|
||||
Loading…
Reference in New Issue