439 lines
18 KiB
Python
439 lines
18 KiB
Python
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 subprocess
|
|
import threading
|
|
import time
|
|
import pathlib
|
|
from lxml import etree
|
|
|
|
class Order_List (_With_Table_Widget_) :
|
|
|
|
settings_group = "order-list"
|
|
Header = "Aufträge"
|
|
Columns = \
|
|
("Warenkorb", "Status", "Bearbeiter", "Kommission", "Kunde", "Linie")
|
|
|
|
State_Map = \
|
|
{ -1 : "wird gelöscht"
|
|
, 0 : "neu"
|
|
, 10 : "in Bearbeitung"
|
|
, 20 : "im CAD geöffnet"
|
|
, 25 : "CNC Erzeugung läuft"
|
|
, 90 : "Odoo"
|
|
, 95 : "Zurückgesetzt"
|
|
, 99 : "Storno"
|
|
}
|
|
|
|
def __init__ (self, parent = None) :
|
|
super ().__init__ (parent)
|
|
self._view.setContextMenuPolicy (QT.CustomContextMenu)
|
|
self._view.customContextMenuRequested.connect (self._show_context_menu)
|
|
ACT = QtGui.QAction
|
|
self._open_cad = ACT (self.tr ("Im CAD öffnen"))
|
|
self._recreate_cnc = ACT (self.tr ("CNC Programm neu erzeugen"))
|
|
self._reset_order = ACT (self.tr ("Auftrag zurücksetzen"))
|
|
self._cancel = ACT (self.tr ("Auftrag stronieren"))
|
|
self._send_to_odoo = ACT (self.tr ("Auftrag ans Odoo schicken"))
|
|
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._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 ()
|
|
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._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_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 :
|
|
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_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 ()
|
|
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) :
|
|
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]
|
|
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) :
|
|
row_data = self._view.selectedItems ()
|
|
if row_data :
|
|
bno = row_data [0].text ()
|
|
xml = f"""<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
|
<XML Type="JOBPROCESS">
|
|
<BatchJobs>
|
|
<BatchJob>
|
|
<OrderNo>{bno}</OrderNo>
|
|
<BatchName>STANDARD</BatchName>
|
|
<Source>IMOSADMIN</Source>
|
|
</BatchJob>
|
|
</BatchJobs>
|
|
</XML>
|
|
"""
|
|
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) :
|
|
txt_file = ( pathlib.Path (self.config.directories.factory_root)
|
|
/ App_State.imos_done_dir
|
|
/ f"{basket_no}.txt"
|
|
)
|
|
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)
|
|
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"
|
|
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 ()
|
|
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 ()
|
|
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 ()
|
|
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 _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
|
|
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
|
|
|
|
class Order_Detail (_With_Table_Widget_) :
|
|
|
|
settings_group = "order-detail"
|
|
Header = "Auftrags Detail"
|
|
Columns = \
|
|
("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, 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.baskets = {}
|
|
# end def __init__
|
|
|
|
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 ())
|
|
settings.setValue ("windowState", self.saveState ())
|
|
settings.endGroup ()
|
|
self._order_list .save_settings (settings)
|
|
self._order_detail.save_settings (settings)
|
|
# end def save_settings
|
|
|
|
def restore_settings (self, settings) :
|
|
settings.beginGroup ("order-display")
|
|
self.restoreGeometry (settings.value ("geometry" ))
|
|
self.restoreState (settings.value ("windowState"))
|
|
settings.endGroup ()
|
|
self._order_list .restore_settings (settings)
|
|
self._order_detail.restore_settings (settings)
|
|
# end def restore_settings
|
|
|
|
# end class Order_Display |