consolidator
/
ConsWindow.py
339 строк · 15.5 Кб
1import os2import sys3import time4from PySide6.QtGui import QIcon,QTextCursor5from PySide6.QtCore import (Slot,QObject,Signal,QRunnable,QThreadPool,QSettings)6from PySide6.QtWidgets import (7QLineEdit,8QWidget,9QMainWindow,10QPushButton,11QFileDialog,12QMessageBox,13QPlainTextEdit,14QDialog,15QVBoxLayout)16from PySide6.QtUiTools import QUiLoader17from MyConfig import MyConfig18from MyLogger import MyLogger,EmittingStream19import pandas as pd20import ntpath21import warnings22from MainConsolidator import MainConsolidator23from OraWorker import OraWorker24from TodoWindow import TodoWindow25from GuidsWindow import GuidsWindow26import pandas_xlwt # не убирать!!!27import urllib.request28from http import client29from GuidsSync import GuidsSync30import traceback31
32
33class Semaphore(QObject):34""" Класс-семафор для отслеживания состояния потока """35started = Signal()36finished = Signal()37
38class Worker(QRunnable):39""""40Worker thread
41
42Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
43
44:param callback: The function callback to run on this worker thread. Supplied args and
45kwargs will be passed through to the runner.
46:type callback: function
47:param args: Arguments to pass to the callback function
48:param kwargs: Keywords to pass to the callback function
49
50"""
51
52def __init__(self, fn, *args, **kwargs):53super(Worker, self).__init__()54# Store constructor arguments (re-used for processing)55self.fn = fn56self.args = args57self.kwargs = kwargs58self.semaphore = Semaphore()59
60@Slot() # QtCore.Slot61def run(self):62"""" Initialise the runner function with passed args, kwargs. """63self.semaphore.started.emit()64self.fn(*self.args, **self.kwargs)65self.semaphore.finished.emit()66
67class ConsWindow(object):68
69
70def __init__(self,metadata:dict=None,update_flag=False,update_url:str=None) -> None:71try:72self.version:int = 073self.busy = False74try:75self.cur_path = sys._MEIPASS76except:77self.cur_path = os.path.dirname(os.path.realpath(__file__))78
79windowIcon = QIcon(f"{self.cur_path}\\consolidation.ico")80self.output_dir = f"{self.cur_path}\\output\\"81if not os.path.exists(self.output_dir): os.mkdir(self.output_dir)82self.threadpool = QThreadPool()83print(f"Multithreading with maximum {self.threadpool.maxThreadCount()} threads")84loader = QUiLoader()85es = EmittingStream()86self.selected_files = []87self.settings = MyConfig(f"{self.cur_path}\\config.json")88logFile:str = self.settings.get("LogFile",defaultValue="default.log")89consConfig:str = self.settings.get("ConsolidationConfig",defaultValue=f"{self.cur_path}\\consolidation.json")90self.consCfg = MyConfig(consConfig)91self.todoList:list= self.consCfg.get("todo",None)92guidsDir:str = self.settings.get("GuidsDir_",defaultValue=f"{self.cur_path}\\guids") #bug fix GuidsDir -> GuidsDir_93
94if not os.path.isdir(guidsDir): guidsDir=f"{self.cur_path}\\guids"95self.guids_path = guidsDir96self.mlog= MyLogger(__name__, log_file=logFile, outStream=es)97
98# init components99self.MainWindow:QMainWindow = loader.load(f"{self.cur_path}\\ConsWindow.ui", None)100self.MainWindow.setWindowIcon(windowIcon)101if metadata is not None:102prodName = metadata.get("FileDescription","module")103prodVersion = metadata.get("Version","0.0.0.0")104self.MainWindow.setWindowTitle(f"{prodName} ver.{prodVersion}")105self.consoleBox:QPlainTextEdit = self.MainWindow.consoleBox106self.openFilesButton:QPushButton = self.MainWindow.openFilesButton107self.consButton:QPushButton = self.MainWindow.consButton108self.filesInput:QLineEdit = self.MainWindow.filesInput109self.todoButton:QPushButton = self.MainWindow.todoButton110self.guidsButton:QPushButton = self.MainWindow.guidsButton111
112# init slot connections113es.textSignal.connect(self.write_to_console)114self.openFilesButton.clicked.connect(self.select_input_files)115self.consButton.clicked.connect(self.start_consolidate)116self.todoButton.clicked.connect(self.show_todo_list)117self.guidsButton.clicked.connect(self.show_guids_window)118self.MainWindow.destroyed.connect(self.app_destroy)119
120if update_flag:121self.mlog.info("Start update procedure")122self.openFilesButton.setEnabled(False)123self.todoButton.setEnabled(False)124self.consButton.setEnabled(False)125self.guidsButton.setEnabled(False)126self.start_download_update(update_url)127else:128# init MainConsolidator129self.consolidator = MainConsolidator(logFile,logStream=es)130self.consolidator.load_guids_x(guidsDir)131self.consolidator.columns = self.consCfg.get("columns",[])132# init 2 level handlers133self.handlers={}134self.hdict={}135hlist = self.consCfg.get("handlers",None)136for item in hlist:137module_path = f"{self.cur_path}\\handlers2\\{item['module']}.py"138with open(module_path,encoding="utf-8",mode="r") as f:139module_src = f.read()140self.handlers[item["key"]]=compile(module_src,"","exec")141self.hdict[item["key"]]=item["module"]142self.mlog.info(f"{item['key']} : {item['module']}")143self.mlog.info("2 level handlers loaded")144self.checkFile = self.settings.get("CheckFile","")145
146# oracle connect147user = self.settings.get("OracleConfig",defaultValue=None)["user"]148passw = self.settings.get("OracleConfig",defaultValue=None)["pass"]149dsn = self.settings.get("OracleConfig",defaultValue=None)["dsn"]150cl = self.settings.get("OracleConfig",defaultValue=None)["client"]151self.ora = OraWorker(user,passw,dsn,cl)152ora_inf= self.ora.ora_version()153self.mlog.info(f"Соединение с oracle:\n {ora_inf}")154self.mlog.info(f"pid: {os.getpid()}")155except:156self.mlog.error("ConsWindow init error",exc_info=True)157
158def app_destroy(self):159try:160self.ora.close()161self.mlog.info("oracle disconnected")162except:163self.mlog.error("app destroy",exc_info=True)164
165def app_error(self,mess:str):166""" Сообщение об ошибке """167QMessageBox.critical(self.MainWindow,"Ошибка",mess,QMessageBox.StandardButton.Yes,QMessageBox.StandardButton.Yes)168self.mlog.error(mess)169
170def write_to_console(self,mess:str):171"""Вывод в консоль приложения172
173Args:
174mess (str): Текст сообщения\n
175"""
176try:177self.consoleBox.appendHtml(mess)178except Exception as exp:179self.app_error(f"write_to_console error: {exp}")180
181def write_char_to_console(self,char:str):182self.consoleBox.moveCursor(QTextCursor.MoveOperation.PreviousCharacter,QTextCursor.MoveMode.KeepAnchor)183self.consoleBox.insertPlainText(char)184# self.consoleBox.setTextCursor(prev_cursor)185
186
187def show_todo_list(self):188try:189df = pd.DataFrame(self.consCfg.get("handlers",None))190todoWindow = TodoWindow(df)191if todoWindow.show()==1:192self.consCfg.set("handlers",todoWindow.data.to_dict("records"))193self.consCfg.sync()194except Exception as exp:195self.app_error(f"show_todo_list error: {exp}")196
197def show_guids_window(self):198""" Показать окно справочников """199try:200# gs:GuidsSync=None201# try:202# remoteHost=self.settings.get("RemoteGuidsHost",None)203# remoteUser=self.settings.get("RemoteUser",None)204# remotePass=self.settings.get("RemotePassword",None)205# remoteDir=self.settings.get("RemoteGuidsDir",None)206# gs = GuidsSync(remoteHost,remoteUser,remotePass)207# gs.set_remote_dir(remoteDir)208# gs.set_local_dir(self.guids_path)209# except Exception as e1: self.mlog.warning("Не удалось инициализировать модуль синхронизации справочников",exc_info=True)210# guidsWindow = GuidsWindow(self.guids_path,syncModule=gs)211guidsWindow = GuidsWindow()212guidsWindow.show()213self.mlog.info(f"Справочники изменены: {guidsWindow.guids_changed}")214# issue fix http://vsys01775:8282/flea/pyexcelcons3/-/issues/5215if guidsWindow.guids_changed: self.consolidator.load_guids_x(self.guids_path)216except Exception as exp:217self.app_error(f"show_guids_window error: {exp}")218stacktrace = traceback.format_exc()219self.app_error(stacktrace)220
221def files_loaded(self):222self.mlog.info("Файлы загружены")223self.consButton.setDisabled(False)224
225def select_input_files(self):226""" Выбор файлов с шаблонами """227try:228fdialog=QFileDialog(parent=self.MainWindow,filter="MS Access 97 (*.mdb);; Excel (*.xlsx)")229fdialog.setFileMode(QFileDialog.FileMode.ExistingFiles)230if fdialog.exec()==1:231self.selected_files = fdialog.selectedFiles()232files_str = ", ".join([f"[{ntpath.basename(f)}]" for f in self.selected_files])233self.filesInput.setText(files_str)234self.mlog.info(f"Входные файлы: {files_str}")235self.consButton.setDisabled(True)236worker = Worker(self.start_load_input_files)237worker.semaphore.finished.connect(self.files_loaded)238self.threadpool.start(worker)239except Exception as exp:240self.app_error(f"select_input_files error: {exp}")241
242def start_load_input_files(self):243try:244self.consolidator.load_input_data(self.selected_files)245self.consolidator.dump_input_data(f"{self.output_dir}input_data.xlsx")246except Exception as exp:247self.mlog.error("start_load_input_files error",exc_info=True)248
249
250def start_consolidate(self):251try:252worker = Worker(self.consolidate_items)253worker.semaphore.finished.connect(lambda: self.mlog.info("Все готово"))254self.threadpool.start(worker)255except Exception as exp:256self.mlog.error("start_consolidate error",exc_info=True)257
258
259def handler_cc(self,rec:pd.Series)->pd.Series:260try:261result:pd.Series={"f1":0}262module_name=f"__{self.hdict[rec['Производство2']]}__"263namespace={"consolidator":self.consolidator , "record":rec, "result":result,"__name__":module_name}264# self.mlog.info(module_name)265exec(self.handlers[rec["Производство2"]],namespace)266result = namespace["result"]267self.mlog.info(f"{result['ID в SAP ERP VMZ']} -> {result['Продукт УП']}")268return result269except Exception as exp:270self.mlog.error("handler2 error",exc_info=True)271rec["error"]=f"handler2_cc error: {exp}"272return rec273
274def check_handler(self,rec:pd.Series)->pd.Series:275# rec["exists"] = self.ora.product_exists(str(rec["Продукт УП"]))276# rec["ok"]=rec["Продукт УП"]==rec["Название продукта УП"]277check = self.ora.product_check(str(rec["Продукт УП"]),str(rec["ID в SAP ERP VMZ"]))278# rec["exists"] = ora.product_exists(str(rec["Продукт УП"]))279# rec["ok"]=rec["Продукт УП"]==rec["Название продукта УП"]280rec["ID в SAP ERP VMZ"]=str(rec["ID в SAP ERP VMZ"])281rec["Есть в УП"] = check[1]282rec["Уже укрупнено"] = check[2]283rec["Уже укрупнено в"] = check[0]284self.mlog.info(f"{rec['ID в SAP ERP VMZ']} : {rec['Продукт УП']} -> {check}")285return rec286
287def consolidate_items(self):288try:289if self.consolidator.input_data.empty: raise Exception("Отсутствуют входные данные")290final_df = self.consolidator.input_data.apply(self.handler_cc,axis=1)291final_df[self.consolidator.columns].to_excel(f"{self.output_dir}final_result.xlsx",index=False)292self.mlog.info("Финальный результат сохранен на диск")293
294############# проверка #######################################295df3:pd.DataFrame = final_df[["ID в SAP ERP VMZ","Полное наименование материала","Вид материала в SAP ERP","Продукт УП","error"]]296
297df_checked:pd.DataFrame = df3.apply(self.check_handler,axis=1)298
299# outfile=f"{self.output_dir}\\final_checked.xls"300# df_checked.to_excel(outfile,index=False,engine="xlwt")301### issue http://vsys01775:8282/flea/pyexcelcons3/-/issues/7302
303outfile=f"{self.output_dir}\\final_checked.xlsx"304df_checked.to_excel(outfile,index=False,engine="openpyxl")305
306self.mlog.info("checked")307os.system(f"start EXCEL.exe {outfile}")308
309##############################################################310except Exception as exp:311self.mlog.error("consolidate_items error",exc_info=True)312
313def start_download_update(self,update_url:str):314try:315worker = Worker(self.download_update,update_url)316worker.semaphore.finished.connect(self.update_downloaded)317self.threadpool.start(worker)318except: raise319
320def download_update(self,update_url:str):321self.mlog.info(f"start download {update_url} \n")322download_dir = f"{self.cur_path}\\update\\" if "_internal" not in self.cur_path else f"{self.cur_path}\\..\\..\\update\\"323if not os.path.isdir(download_dir): os.makedirs(download_dir)324mess=urllib.request.urlretrieve(update_url,f"{download_dir}update.zip")[0]325self.mlog.info(mess)326if "_internal" in self.cur_path:327with open(f"{download_dir}update.bat",mode="w",encoding="windows-1251") as f:328f.write(f'chcp 1251\ntaskkill /PID {os.getpid()}\npowershell -command "Expand-Archive -Force '+f"'{download_dir}update.zip' '{download_dir}..'"+'"\nexit')329f.flush()330f.close()331os.system(f"start {download_dir}update.bat")332
333def update_downloaded(self):334self.openFilesButton.setEnabled(True)335self.todoButton.setEnabled(True)336self.consButton.setEnabled(True)337self.guidsButton.setEnabled(True)338self.mlog.info("update downloaded")339self.busy=False