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""" {bno} STANDARD IMOSADMIN """ 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