import oracledb import os import sys import json import csv import re import subprocess import argparse from datetime import datetime from jit_oracle_connection import JitOracleConnection args = None oracle_connection: JitOracleConnection = None def striprtf(text): pattern = re.compile(r"\\([a-z]{1,32})(-?\d{1,10})?[ ]?|\\'([0-9a-f]{2})|\\([^a-z])|([{}])|[\r\n]+|(.)", re.I) destinations = frozenset(( 'aftncn','aftnsep','aftnsepc','annotation','atnauthor','atndate','atnicn','atnid', 'atnparent','atnref','atntime','atrfend','atrfstart','author','background', 'bkmkend','bkmkstart','blipuid','buptim','category','colorschememapping', 'colortbl','comment','company','creatim','datafield','datastore','defchp','defpap', 'do','doccomm','docvar','dptxbxtext','ebcend','ebcstart','factoidname','falt', 'fchars','ffdeftext','ffentrymcr','ffexitmcr','ffformat','ffhelptext','ffl', 'ffname','ffstattext','field','file','filetbl','fldinst','fldrslt','fldtype', 'fname','fontemb','fontfile','fonttbl','footer','footerf','footerl','footerr', 'footnote','formfield','ftncn','ftnsep','ftnsepc','g','generator','gridtbl', 'header','headerf','headerl','headerr','hl','hlfr','hlinkbase','hlloc','hlsrc', 'hsv','htmltag','info','keycode','keywords','latentstyles','lchars','levelnumbers', 'leveltext','lfolevel','linkval','list','listlevel','listname','listoverride', 'listoverridetable','listpicture','liststylename','listtable','listtext', 'lsdlockedexcept','macc','maccPr','mailmerge','maln','malnScr','manager','margPr', 'mbar','mbarPr','mbaseJc','mbegChr','mborderBox','mborderBoxPr','mbox','mboxPr', 'mchr','mcount','mctrlPr','md','mdeg','mdegHide','mden','mdiff','mdPr','me', 'mendChr','meqArr','meqArrPr','mf','mfName','mfPr','mfunc','mfuncPr','mgroupChr', 'mgroupChrPr','mgrow','mhideBot','mhideLeft','mhideRight','mhideTop','mhtmltag', 'mlim','mlimloc','mlimlow','mlimlowPr','mlimupp','mlimuppPr','mm','mmaddfieldname', 'mmath','mmathPict','mmathPr','mmaxdist','mmc','mmcJc','mmconnectstr', 'mmconnectstrdata','mmcPr','mmcs','mmdatasource','mmheadersource','mmmailsubject', 'mmodso','mmodsofilter','mmodsofldmpdata','mmodsomappedname','mmodsoname', 'mmodsorecipdata','mmodsosort','mmodsosrc','mmodsotable','mmodsoudl', 'mmodsoudldata','mmodsouniquetag','mmPr','mmquery','mmr','mnary','mnaryPr', 'mnoBreak','mnum','mobjDist','moMath','moMathPara','moMathParaPr','mopEmu', 'mphant','mphantPr','mplcHide','mpos','mr','mrad','mradPr','mrPr','msepChr', 'mshow','mshp','msPre','msPrePr','msSub','msSubPr','msSubSup','msSubSupPr','msSup', 'msSupPr','mstrikeBLTR','mstrikeH','mstrikeTLBR','mstrikeV','msub','msubHide', 'msup','msupHide','mtransp','mtype','mvertJc','mvfmf','mvfml','mvtof','mvtol', 'mzeroAsc','mzeroDesc','mzeroWid','nesttableprops','nextfile','nonesttables', 'objalias','objclass','objdata','object','objname','objsect','objtime','oldcprops', 'oldpprops','oldsprops','oldtprops','oleclsid','operator','panose','password', 'passwordhash','pgp','pgptbl','picprop','pict','pn','pnseclvl','pntext','pntxta', 'pntxtb','printim','private','propname','protend','protstart','protusertbl','pxe', 'result','revtbl','revtim','rsidtbl','rxe','shp','shpgrp','shpinst', 'shppict','shprslt','shptxt','sn','sp','staticval','stylesheet','subject','sv', 'svb','tc','template','themedata','title','txe','ud','upr','userprops', 'wgrffmtfilter','windowcaption','writereservation','writereservhash','xe','xform', 'xmlattrname','xmlattrvalue','xmlclose','xmlname','xmlnstbl', 'xmlopen', )) # Translation of some special characters. specialchars = { 'par': '\n', 'sect': '\n\n', 'page': '\n\n', 'line': '\n', 'tab': '\t', 'emdash': '\u2014', 'endash': '\u2013', 'emspace': '\u2003', 'enspace': '\u2002', 'qmspace': '\u2005', 'bullet': '\u2022', 'lquote': '\u2018', 'rquote': '\u2019', 'ldblquote': '\201C', 'rdblquote': '\u201D', } stack = [] ignorable = False # Whether this group (and all inside it) are "ignorable". ucskip = 1 # Number of ASCII characters to skip after a unicode character. curskip = 0 # Number of ASCII characters left to skip out = [] # Output buffer. for match in pattern.finditer(text): word,arg,hex,char,brace,tchar = match.groups() if brace: curskip = 0 if brace == '{': # Push state stack.append((ucskip,ignorable)) elif brace == '}': # Pop state ucskip,ignorable = stack.pop() elif char: # \x (not a letter) curskip = 0 if char == '~': if not ignorable: out.append('\xA0') elif char in '{}\\': if not ignorable: out.append(char) elif char == '*': ignorable = True elif word: # \foo curskip = 0 if word in destinations: ignorable = True elif ignorable: pass elif word in specialchars: out.append(specialchars[word]) elif word == 'uc': ucskip = int(arg) elif word == 'u': c = int(arg) if c < 0: c += 0x10000 if c > 127: out.append(chr(c)) #NOQA else: out.append(chr(c)) curskip = ucskip elif hex: # \'xx if curskip > 0: curskip -= 1 elif not ignorable: c = int(hex,16) if c > 127: out.append(chr(c)) #NOQA else: out.append(chr(c)) elif tchar: if curskip > 0: curskip -= 1 elif not ignorable: out.append(tchar) return ''.join(out) def log_print(text): global args if args.para_silent: print(text) def log_input(text): global args if args.para_silent: print(text) def escaped_csv_text(text): if not text: return '' return str(text).replace(';', '\\;') def artikel_daten_schreiben(uebteilenr: str, json_data: {}, csv_rows: [{}], level: int): global oracle_connection sql = f"""select lpad(c.posnr || '/' || c.varianten, length(c.posnr || '/' || c.varianten) + :ebene, '.') position, c.kompteilenr zeichnungsnummer, p.bez bezeichnung, (select sivas_aj.jit_tools.nclob_to_varchar2(g.text_nclob) from grnr_text g where g.grnr_teilenr = c.kompteilenr and g.art = 'T' and g.lfd_nr = 1 and g.sprache = 'D') konstruktionstext, decode(c.abw_anzahl, null, null, c.abw_anzahl || ' Stck á ' || c.laenge || ' mm') stueli_text, c.werkstoff, c.menge, c.abw_anzahl, c.laenge, c.werknorm, bez.name1_lieferant(artikel_lieferant.ermittle_hauptlieferant(c.kompteilenr)) hauptlieferant, (select min(uebteilenr) from comp where uebteilenr = c.kompteilenr) baugruppe, decode(nvl(c.dispo_aktiv_jn, 'N'), 'J', 'True', 'False') dispo_aktiv_jn, decode(c.ruestcode, null, null, c.ruestcode || ' - ' || bez.bez_ruestcode(c.ruestcode)) ruestcode, decode(nvl(c.kz_lieferanten_beistellung, 'N'), 'J', 'True', 'False') kz_lieferanten_beistellung from comp c, parts p where p.teilenr = c.kompteilenr and uebteilenr = :uebteilenr order by c.posnr, c.varianten""" cursor = oracle_connection.cursor() cursor.execute(sql, [level, uebteilenr]) columns = [col[0] for col in cursor.description] cursor.rowfactory = lambda *args: dict(zip(columns, args)) pos_data = cursor.fetchall() if not pos_data: log_print(f"Keine Datensätze für diese Parameter gefunden") return for pos in pos_data: artikel_data = {} artikel_data['Position'] = pos['POSITION'] artikel_data['Zeichnungsnummer'] = pos['ZEICHNUNGSNUMMER'] artikel_data['Parent'] = uebteilenr artikel_data['Bezeichnung'] = pos['BEZEICHNUNG'] if not pos['KONSTRUKTIONSTEXT']: artikel_data['Konstruktionstext'] = str() else: artikel_data['Konstruktionstext'] = striprtf(str(pos['KONSTRUKTIONSTEXT'])) artikel_data['Stüli-Text'] = pos['STUELI_TEXT'] artikel_data['Werkstoff'] = pos['WERKSTOFF'] artikel_data['Norm'] = pos['WERKNORM'] artikel_data['Menge'] = int(pos['MENGE']) if not pos['ABW_ANZAHL']: artikel_data['Abweichende Anzahl'] = str() else: artikel_data['Abweichende Anzahl'] = int(pos['ABW_ANZAHL']) if not pos['LAENGE']: artikel_data['Länge'] = str() else: artikel_data['Länge'] = int(pos['LAENGE']) artikel_data['Hauptlieferant'] = pos['HAUPTLIEFERANT'] artikel_data['Dispoaktiv'] = pos['DISPO_AKTIV_JN'] artikel_data['Rüstcode'] = pos['RUESTCODE'] artikel_data['Lieferantenbeistellung'] = pos['KZ_LIEFERANTEN_BEISTELLUNG'] header = ['Nummer', 'Position', 'Zeichnungsnummer', 'Bezeichnung', 'Parent', 'Konstruktionstext', 'Stüli-Text', 'Werkstoff', 'Norm', 'Menge', 'Abweichende Anzahl', 'Länge', 'Hauptlieferant', 'Dispoaktiv', 'Rüstcode', 'Lieferantenbeistellung'] csv_row = {'Nummer': escaped_csv_text(artikel_data['Zeichnungsnummer']), 'Position': escaped_csv_text(artikel_data['Position']), 'Zeichnungsnummer': escaped_csv_text(artikel_data['Zeichnungsnummer']), 'Bezeichnung': escaped_csv_text(artikel_data['Bezeichnung']), 'Parent': escaped_csv_text(artikel_data['Parent']), 'Konstruktionstext': escaped_csv_text(artikel_data['Konstruktionstext']), 'Stüli-Text': escaped_csv_text(artikel_data['Stüli-Text']), 'Werkstoff': escaped_csv_text(artikel_data['Werkstoff']), 'Norm': escaped_csv_text(artikel_data['Norm']), 'Menge': escaped_csv_text(artikel_data['Menge']), 'Abweichende Anzahl': escaped_csv_text(artikel_data['Abweichende Anzahl']), 'Länge': escaped_csv_text(artikel_data['Länge']), 'Hauptlieferant': escaped_csv_text(artikel_data['Hauptlieferant']), 'Dispoaktiv': escaped_csv_text(artikel_data['Dispoaktiv']), 'Rüstcode': escaped_csv_text(artikel_data['Rüstcode']), 'Lieferantenbeistellung': escaped_csv_text(artikel_data['Lieferantenbeistellung'])} csv_rows.append(csv_row) sql = f"""select o.agnr, o.kostenst kostenstelle, o.verricht_text, decode(o.codeausw, 'J', (select name1 from gepa where kdnr = o.kostenst), (select c.bez from centre c where c.kostenst = o.kostenst)) bezeichnung, nvl(o.ruestzeit * 60, 0) ruestzeit, nvl(o.stueckzeit * 60, 0) stueckzeit from opplan o where o.teilenr = :teilenr""" cursor.execute(sql, [pos['ZEICHNUNGSNUMMER']]) columns = [col[0] for col in cursor.description] cursor.rowfactory = lambda *args: dict(zip(columns, args)) ag_data = cursor.fetchall() first_time = True for ag in ag_data: if first_time: artikel_data['Arbeitsgänge'] = [] first_time = False ag_content = {} ag_content['Nummer'] = int(ag['AGNR']) ag_content['Kostenstelle'] = int(ag['KOSTENSTELLE']) ag_content['Bezeichnung'] = ag['BEZEICHNUNG'] ag_content['Verrichttext'] = ag['VERRICHT_TEXT'] ag_content['Rüstzeit'] = int(ag['RUESTZEIT']) ag_content['Stückzeit'] = int(ag['STUECKZEIT']) artikel_data['Arbeitsgänge'].append(ag_content) artikel_data_list_of_dict = [artikel_data] if artikel_data['Zeichnungsnummer'] in json_data: artikel_data_list_of_dict = json_data[artikel_data['Zeichnungsnummer']] artikel_data_list_of_dict.append(artikel_data) json_data[artikel_data['Zeichnungsnummer']] = artikel_data_list_of_dict if pos['BAUGRUPPE']: artikel_daten_schreiben(pos['BAUGRUPPE'], json_data, csv_rows, level+1) def main(): global args global oracle_connection instantclient_path = r"\\195.243.223.3\sivas_schreiben\jit\oracle\bin\x64" os.environ["PATH"] = instantclient_path + ";" + os.environ["PATH"] parser = argparse.ArgumentParser(description='Exportiert Stücklisten und deren Arbeitsgänge in eine JSON-, bzw. ' 'CSV-Datei.') parser.add_argument('-o', '--output', dest='para_output_dir', default=os.path.join(os.path.join(os.environ['USERPROFILE']), 'Desktop'), help='Spezifiziert das Zielverzeichnis, wo die Export-Datei abgelegt wird.') parser.add_argument('--format', dest='para_format', default='json', help='Spezifiziert den Typ der Export-Datei.') parser.add_argument('--silent', dest='para_silent', action='store_false', help='Deaktiviert die Ausgabe und Benutzereingaben.') parser.add_argument('--openresult', dest='para_openresult', action='store_true', help='Spezifiziert, ob nach dem Export die Export-Datei geöffnet werden soll.') parser.add_argument('-number', dest='para_number', help='Einzelne Artikelnummer') parser.add_argument('-numbers', dest='para_numbers', help='Kommaseparierte liste von Artikelnummern') parser.add_argument('-from', dest='para_from', help="Beginn des Artikelnummernbereichs, bspw.: 4700") parser.add_argument('-to', dest='para_to', help="Ende des Artikelnummernbereichs, bspw.: 4999") parser.add_argument('-export_pattern', dest='para_export_pattern', help="Angabe eines Artikelnummernbereichs mit " "Wildcards, bspw.: 87X123X45") args = parser.parse_args() try: oracledb.init_oracle_client(lib_dir=instantclient_path) except Exception as err: log_print("Error connecting: oracledb.init_oracle_client()") log_print(err) sys.exit(1) log_print('=== Start Sivas2json ===\n') log_print('Prüfe Parameter...') if not args.para_number and not args.para_numbers and not args.para_from and not args.para_to and not args.para_export_pattern: log_input("Spezifizieren Sie mindestens die Parameter '-number=4711', '-numbers=4711,4712' " "oder '-from=47xx' und '-to=49xx'.\nBeliebige Taste drücken um zu beenden...") return 1 log_print('Parameter OK\n') log_print('Oracle Verbindung...') try: # oracle_connection = JitOracleConnection('195.243.223.3', 'linux', instantclient_dir=instantclient_path, # init_oracle_client=False) oracle_connection = JitOracleConnection(db_host='195.243.223.3', db_sid='linux', db_user='sivas', db_pwd='forte', db_database='sivas', instantclient_dir=instantclient_path, tns_admin=None, app=None, init_oracle_client=True) except Exception as e: log_print(str(e)) log_print('Oracle Verbindung OK\n') sql = """select distinct uebteilenr from comp where (""" or_needed = False if args.para_number: sql += f" uebteilenr = '{args.para_number}'" or_needed = True if args.para_numbers: args.para_numbers = ','.join(["'" + x.strip() + "'" for x in args.para_numbers.split(',')]) if or_needed: sql += ' or ' sql += f" uebteilenr in ({args.para_numbers})" or_needed = True if args.para_from and args.para_to: if or_needed: sql += ' or ' sql += f" uebteilenr between '{args.para_from}' and '{args.para_to}'" if args.para_export_pattern: if or_needed: sql += ' or ' sql += f" uebteilenr like '{args.para_export_pattern.replace('X', '_').replace('x', '_')}'" sql += """) """ cursor = oracle_connection.cursor() cursor.execute(sql) columns = [col[0] for col in cursor.description] cursor.rowfactory = lambda *args: dict(zip(columns, args)) baugruppen = cursor.fetchall() if not baugruppen: if oracle_connection.connection(): oracle_connection.connection().close() log_input(f"Keine Datensätze für diese Parameter gefunden") return 1 file_count = 0 export_filename = str() header = ['Nummer', 'Position', 'Zeichnungsnummer', 'Bezeichnung', 'Parent', 'Konstruktionstext', 'Stüli-Text', 'Werkstoff', 'Norm', 'Menge', 'Abweichende Anzahl', 'Länge', 'Hauptlieferant', 'Dispoaktiv', 'Rüstcode', 'Lieferantenbeistellung'] for baugruppe in baugruppen: json_data = {} csv_rows = [{}] artikel_daten_schreiben(uebteilenr=baugruppe['UEBTEILENR'], json_data=json_data, csv_rows=csv_rows, level=0) export_filename = os.path.join(args.para_output_dir, baugruppe['UEBTEILENR'] + '.' + args.para_format.lower()) log_print(f"Schreibe Export-Datei {export_filename} ...") # Writing json with open(export_filename, "w+", encoding='utf8', newline='') as outfile: if args.para_format.lower() == 'csv': writer = csv.DictWriter(outfile, delimiter=";", fieldnames=header, escapechar='\\') # writer.writerow(header) writer.writeheader() for csv_row in csv_rows: writer.writerow(csv_row) else: outfile.write(json.dumps(json_data, indent=4, ensure_ascii=False)) if args.para_format.lower() == 'csv': lines = [] with open(export_filename, 'r') as f: lines = f.readlines() with open(export_filename, 'w') as f: f.writelines(lines[:1] + lines[2:]) log_print(f"Export-Datei geschrieben nach: {export_filename}.") file_count += 1 if oracle_connection.connection(): oracle_connection.connection().close() if args.para_openresult: if file_count > 1: log_input("Beliebige Taste drücken um Programm zu beenden und Ordner zu öffnen...") subprocess.Popen(['explorer', args.para_output_dir]) elif file_count == 1: log_input("Beliebige Taste drücken um Programm zu beenden und Datei zu öffnen...") subprocess.Popen([export_filename], shell=True) return 0 if __name__ == '__main__': main()