diff --git a/App_State.py b/App_State.py
index b34b83c..497949a 100644
--- a/App_State.py
+++ b/App_State.py
@@ -1,14 +1,27 @@
+from Log_View import Log_View, Log_Handler
import schema
+import logging
class App_State :
user_id = 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"
}
@classmethod
def Create_Action (cls, kind, text, order = None) :
@@ -23,4 +36,27 @@ class App_State :
)
# end def create_action
+ @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 - %(levelname)s - %(message)s"
+ , datefmt = "%Y-%m-%d %H:%M:%S"
+ )
+ )
+ cls.L.addHandler (lh)
+ return cls._log_view
+ # end def Log_View
+
# end class App_State
diff --git a/Log_View.py b/Log_View.py
new file mode 100644
index 0000000..e04c832
--- /dev/null
+++ b/Log_View.py
@@ -0,0 +1,40 @@
+from QT import QtWidgets, QtCore, QT
+import logging
+
+class Log_Handler (logging.Handler) :
+
+ def __init__(self, widget) :
+ super ().__init__ ()
+ self._widget = widget
+ # end __init__
+
+ def emit (self, record) :
+ msg = self.format (record)
+ self._widget.append_log.emit (msg)
+ # end def emit
+
+# end class Log_Handler
+
+class Log_View (QtWidgets.QTextEdit) :
+
+ append_log = QtCore.Signal (str)
+
+ def __init__ (self, * args, ** kw) :
+ super ().__init__ (* args, ** kw)
+ self.setReadOnly (True)
+ self.append_log.connect (self.append)
+ # end def __init__
+
+ def save_settings (self, settings) :
+ settings.beginGroup ("log-view")
+ settings.setValue ("geometry", self.saveGeometry ())
+ settings.endGroup ()
+ # end def save_settings
+
+ def restore_settings (self, settings) :
+ settings.beginGroup ("log-view")
+ self.restoreGeometry (settings.value ("geometry" ))
+ settings.endGroup ()
+ # end def restore_settings
+
+# end class Log_View
\ No newline at end of file
diff --git a/Order_Display.py b/Order_Display.py
index 4932d0b..ef319ce 100644
--- a/Order_Display.py
+++ b/Order_Display.py
@@ -6,6 +6,7 @@ import schema
import subprocess
import threading
import time
+import pathlib
class Order_List (_With_Table_Widget_) :
@@ -18,6 +19,7 @@ class Order_List (_With_Table_Widget_) :
{ 0 : "neu"
, 10 : "in Bearbeitung"
, 20 : "im CAD geöffnet"
+ , 25 : "CNC Erzeugung läuft"
, 90 : "Odoo"
, 95 : "Zurückgesetzt"
, 99 : "Storno"
@@ -31,14 +33,16 @@ class Order_List (_With_Table_Widget_) :
self._recreate_cnc = ACT (self.tr ("CNC Programm neu erzeugen"))
self._reset_order = ACT (self.tr ("Auftrag zurücksetzen"))
self._cancel = ACT (self.tr ("Auftrag stronieren"))
- self._send_to_odoo = ACT (self.tr ("Auftrag ins Odoo schicken"))
+ self._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 = {}
# end def __init__
@@ -47,7 +51,7 @@ class Order_List (_With_Table_Widget_) :
if row_data :
bno = row_data [0].text ()
with schema.orm.db_session () :
- order = schema.Order.get (basket_no = bno)
+ 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")
@@ -67,8 +71,8 @@ class Order_List (_With_Table_Widget_) :
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 = QtWidgets.QMenu ()
cm.addAction (self._open_cad)
cm.addAction (self._recreate_cnc)
cm.addSeparator ()
@@ -76,34 +80,52 @@ class Order_List (_With_Table_Widget_) :
cm.addSeparator ()
cm.addAction (self._reset_order)
cm.addAction (self._cancel)
- cm.addSeparator ()
- cm.addAction (self._actions)
- cm.exec (self._view.mapToGlobal (pos))
+ 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 new :
- row = self._view.rowCount ()
- self._view.setRowCount (row + 1)
- self._add_read_only_string (row, 0, str (order.basket_no))
- self._add_read_only_string (row, 1, self.State_Map [order.state])
- if order.user :
- self._add_read_only_string (row, 2, order.user.username)
- self._add_read_only_string (row, 3, order.commission)
- self._add_read_only_string (row, 4, order.imos_user)
- self._add_read_only_string (row, 5, str (order.production_line))
+ 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)
else :
- if row is None :
- for row in range (self._view.rowCount ()) :
- if self._view.item (row, 0).text () == order.basket_no :
- break
- else :
- print ("Could not find order")
- return
- self._update_read_only_string (row, 1, self.State_Map [order.state])
- self._update_read_only_string (row, 2, order.user.username)
+ 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
+ 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 _open_order_in_cad (self) :
row_data = self._view.selectedItems ()
if row_data :
@@ -114,10 +136,10 @@ class Order_List (_With_Table_Widget_) :
if order.state < 20 :
order.state = 20
self.update_order (order, False, row_data [0].row ())
- self._cad_processes [bno] = subprocess.Popen \
- ( (r"%s\%s\%s.dwg" % (state.imos_order_dir, bno, bno), )
- , shell = True
- )
+ 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, )
@@ -141,7 +163,7 @@ class Order_List (_With_Table_Widget_) :
self.update_order (order, False)
App_State.Create_Action \
( 25
- , f"IMOS CAD closed {order.basket_no}"
+ , f"IMOS CAD closed {order.basket_no}: Error code {p.poll ()}"
, order
)
# end def _wait_for_cad_close
@@ -150,28 +172,100 @@ class Order_List (_With_Table_Widget_) :
row_data = self._view.selectedItems ()
if row_data :
bno = row_data [0].text ()
- print (bno)
+ xml = f"""
+
+
+
+ {bno}
+ STANDARD
+ IMOSADMIN
+
+
+
+"""
+ with schema.orm.db_session () :
+ state = schema.State.get ()
+ order = schema.Order.get (basket_no = bno)
+ 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)
# 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)
+ / App_State.imos_done_dir
+ / f"{basket_no}.txt"
+ )
+ err_file_name = ( pathlib.Path (state.imos_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}")
+ 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)
+ order.state = 10
+ self.update_order (order, False)
+ App_State.Create_Action (35, text, order)
+ # 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 ()
- print (bno)
+ App_State.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 ()
- print (bno)
+ App_State.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 ()
- print (bno)
+ with schema.orm.db_session () :
+ state = schema.State.get ()
+ order = schema.Order.get (basket_no = bno)
+ 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)
# end def _send_oder_to_odoo
def _show_actions (self) :
@@ -184,6 +278,30 @@ class Order_List (_With_Table_Widget_) :
(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 :
+ bno = row_data [0].text ()
+ r = QtWidgets.QMessageBox.question \
+ ( self, self.tr ("Fehler")
+ , self.tr
+ ( "Soll der Status wirklich auf In Bearbeitung "
+ "gesetzt werden?"
+ )
+ )
+ if r == QtWidgets.QMessageBox.No :
+ return
+ with schema.orm.db_session () :
+ order = schema.Order.get (basket_no = bno)
+ order.state = 10
+ self.update_order (order, False)
+ App_State.Create_Action \
+ ( 15
+ , f"Status wird auf 10 gesetzt"
+ , order
+ )
+ # end def _reset_order_state
+
# end class Order_List
class Order_Detail (_With_Table_Widget_) :
@@ -207,8 +325,9 @@ class Order_Display (QtWidgets.QSplitter) :
# end def __init__
def update_orders (self) :
+ orders = set (self.orders.keys ())
with schema.orm.db_session :
- for o in schema.Order.select () :
+ 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)
@@ -218,6 +337,10 @@ class Order_Display (QtWidgets.QSplitter) :
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 save_settings (self, settings) :
diff --git a/designbox_orders.py b/designbox_orders.py
index 2fae52e..1717f43 100644
--- a/designbox_orders.py
+++ b/designbox_orders.py
@@ -1,12 +1,14 @@
import os
from QT.Main_Window import Main_Window, QtWidgets
+from QT import QT
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
+from lxml import etree
+import logging
class Order_Processing (Main_Window) :
@@ -42,8 +44,14 @@ class Order_Processing (Main_Window) :
, ( (self.actions.test), (self.tool_bar_file, ))
) :
for x in l :
- x.addAction (a)
- self.setCentralWidget (Order_Display (self))
+ x.addAction (a)
+ sp = QtWidgets.QSplitter (self)
+ self._order_display = Order_Display (self)
+ self._log = App_State.Log_View (parent = self)
+ sp.addWidget (self._order_display)
+ sp.addWidget (self._log)
+ sp.setOrientation (QT.Vertical)
+ self.setCentralWidget (sp)
settings = self.settings
settings.beginGroup ("TempSettings")
user_id = settings.value ("user_id")
@@ -68,22 +76,29 @@ class Order_Processing (Main_Window) :
def scan_orders_directory (self) :
with schema.orm.db_session :
- state = schema.State.get ()
- orders = self._find_orders (pathlib.Path (state.imos_export_dir))
+ 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 (o)
- self.centralWidget ().update_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, xml_file) :
+ 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)
+ 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 :
- xml = etree.parse (xml_file).getroot ()
- com = xml.xpath ("//TEXT_SHORT") [0].text
- user,line = xml.xpath ("//RETAILER") [0].text.split ("/")
order = schema.Order \
( basket_no = basket_no
, state = 0
@@ -96,20 +111,52 @@ class Order_Processing (Main_Window) :
( user = schema.User [App_State.user_id]
, date = schema.datetime.now ()
, action_kind = 1
- , action = "Auftrag importiert"
+ , action = f"Auftrag importiert {basket_no}"
, order = order
)
+ 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
+ )
+ order.active = True
# end def _update_order_in_database
def _restore_settings (self) :
super ()._restore_settings ()
- self.centralWidget ().restore_settings (self.settings)
+ sp = self.centralWidget ()
+ settings = self.settings
+ settings.beginGroup ("main-splitter")
+ settings.setValue ("geometry", sp.saveGeometry ())
+ settings.setValue ("windowState", sp.saveState ())
+ settings.endGroup ()
+ self._order_display.restore_settings (settings)
+ self._log.restore_settings (settings)
# end def _restore_settings
def save_settings (self) :
settings = self.settings
+ sp = self.centralWidget ()
super ().save_settings ()
- self.centralWidget ().save_settings (settings)
+ settings.beginGroup ("main-splitter")
+ sp.restoreGeometry (settings.value ("geometry" ))
+ sp.restoreState (settings.value ("windowState"))
+ settings.endGroup ()
+ self._order_display.save_settings (settings)
+ self._log.save_settings (settings)
settings.beginGroup ("TempSettings")
settings.setValue ("user_id", App_State.user_id)
settings.endGroup ()
@@ -135,7 +182,7 @@ class Order_Processing (Main_Window) :
settings.endGroup ()
return True
else :
- print ("### Login skipped")
+ App_State.L.error ("### Login skipped")
return True
# end def pre_main_window_action
@@ -147,4 +194,9 @@ if __name__ == "__main__" :
parser.add_argument ("-s","--settings-file", type = str)
parser.add_argument ("-d", "--database", 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"
+ )
Order_Processing.run (Order_Processing.load_settings (cmd))
diff --git a/schema.py b/schema.py
index c209b6c..21afffd 100644
--- a/schema.py
+++ b/schema.py
@@ -88,17 +88,15 @@ class Order_Line (db.Entity) :
class State (db.Entity) :
- imos_export_dir = orm.Required (str)
- imos_order_dir = orm.Required (str)
- sim_archive = orm.Required (str)
- pgiq_import_dir = orm.Required (str)
+ imos_factory_root = orm.Required (str)
+ imos_sim_root = orm.Required (str)
last_directory_scan = orm.Required (datetime)
def __str__ (self) :
return f"""
-IMOS dir {self.imos_export_dir}
-PGIQ dir {self.pgiq_import_dir}
-Last scan {self.last_directory_scan}
+IMOS Facory root {self.imos_factory_root}
+IMOS SIM toot {self.imos_sim_root}
+Last scan {self.last_directory_scan}
""".strip ()
# end def __str__