diff --git a/SumasenLibs/excel_lib/docker/docker-compose.yml b/SumasenLibs/excel_lib/docker/docker-compose.yml index e0b1267..bd37861 100644 --- a/SumasenLibs/excel_lib/docker/docker-compose.yml +++ b/SumasenLibs/excel_lib/docker/docker-compose.yml @@ -9,6 +9,12 @@ services: - ..:/app environment: - PYTHONPATH=/app - command: python ./testdata/sample.py + - POSTGRES_DB=rogdb + - POSTGRES_USER=admin + - POSTGRES_PASSWORD=admin123456 + - POSTGRES_HOST=localhost + - POSTGRES_PORT=5432 + network_mode: "host" tty: true + container_name: python_container # コンテナ名を明示的に指定 diff --git a/SumasenLibs/excel_lib/docker/python/Dockerfile b/SumasenLibs/excel_lib/docker/python/Dockerfile index f7a1e5d..2a7e958 100644 --- a/SumasenLibs/excel_lib/docker/python/Dockerfile +++ b/SumasenLibs/excel_lib/docker/python/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app # GPGキーの更新とパッケージのインストール RUN apt-get update --allow-insecure-repositories && \ - apt-get install -y --allow-unauthenticated python3-dev libpq-dev && \ + apt-get install -y --allow-unauthenticated python3-dev libpq-dev postgresql-client && \ rm -rf /var/lib/apt/lists/* # Pythonパッケージのインストール diff --git a/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py b/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py index 26841e3..f019ef7 100644 --- a/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py +++ b/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py @@ -13,16 +13,24 @@ from pathlib import Path # psycopg2のインポートを追加 import psycopg2 import psycopg2.extras +from copy import copy +from openpyxl.utils import range_boundaries from .config_handler import ConfigHandler # ini file のロード -from .styles import StyleManager -from .merge import MergeManager -from .image import ImageManager -from .conditional import ConditionalFormatManager +#from .styles import StyleManager +#from .merge import MergeManager +#from .image import ImageManager +#from .conditional import ConditionalFormatManager from .page import PageManager, PaperSizes + import logging +logging.basicConfig( + level=logging.INFO, # INFOレベル以上のログを表示 + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) + class SumasenExcel: """Enhanced Excel handling class with extended functionality""" @@ -142,7 +150,7 @@ class SumasenExcel: return {"status": False, "message": f"Exception in make_report: Error generating report: {str(e)}"} def proceed_section(self, section: str, variables: Dict[str, Any]): - logging.info(f"make_report.proceed_section step-1:section={section}") + print(f"make_report.proceed_section step-1:section={section}") try: # セクションの設定を取得 @@ -160,28 +168,29 @@ class SumasenExcel: new_sheet = self.workbook.create_sheet(title=section_config.get("sheet_name", section)) self.worksheet = new_sheet - self.dbname=variables.get('db'), - self.user=variables.get('username'), - self.password=variables.get('password'), - self.host=variables.get('host'), - self.port=variables.get('port') + self.dbname=variables.get('db') + self.user=variables.get('username') + self.password=variables.get('password') + self.host=variables.get('host','postgres') + self.port=variables.get('port','5432') if not self.dbname or not self.user or not self.password or not self.host or not self.port: return {"status": False, "message": f"Error no database connection information"} + print(f"db={self.dbname},user={self.user},pass={self.password},host={self.host},port={self.port}") # PostgreSQLに接続 self.conn = psycopg2.connect( - self.dbname, - self.user, - self.password, - self.host, - self.port + dbname=self.dbname, + user=self.user, + password=self.password, + host=self.host, + port=self.port ) - self._style_manager = StyleManager() - self._merge_manager = MergeManager(self.current_sheet) - self._image_manager = ImageManager(self.current_sheet) - self._conditional_manager = ConditionalFormatManager(self.current_sheet) - self._page_manager = PageManager(self.current_sheet) + #self._style_manager = StyleManager() + #self._merge_manager = MergeManager(self.current_sheet) + #self._image_manager = ImageManager(self.current_sheet) + #self._conditional_manager = ConditionalFormatManager(self.current_sheet) + #self._page_manager = PageManager(self.current_sheet) # シートの幅を設定 fit_to_width = section_config.get("fit_to_width") @@ -205,7 +214,8 @@ class SumasenExcel: # 各グループの設定を取得 for group in group_list: - ret = self.proceed_group(group,variables) + section_group = f"{section}.{group}" + ret = self.proceed_group(section_group,variables) if ret["status"] == False: return ret @@ -224,7 +234,7 @@ class SumasenExcel: # グループの処理パラメータを取得 group_range = group_config.get("group_range") - table = group_config.get("table") + table = group_config.get("table_name") where = group_config.get("where") if not where or not table or not group_range: return {"status": False, "message": f"Error invalid group parameters: {group_config}"} @@ -232,11 +242,11 @@ class SumasenExcel: sort = group_config.get("sort") if not sort: ret = self.proceed_one_record(table,where,group_range,variables) - if ret.status == True: + if ret.get("status") == True: return {"status": True, "message": f"Success generating group: {group}"} else: ret = self.proceed_all_records(table,where,sort,group_range,variables) - if ret.status == True: + if ret.get("status") == True: return {"status": True, "message": f"Success generating group: {group}"} except Exception as e: @@ -252,19 +262,22 @@ class SumasenExcel: group_range: 処理対象範囲 variables: DB接続情報を含む変数辞書 """ - logging.info(f"make_report.proceed_one_record step-1:table={table},where={where},group_range={group_range}") try: + print(f"make_report.proceed_one_record step-1:table={table},where={where},group_range={group_range}") # まずself.template_sheetの指定範囲のセルをself.current_sheetにコピーする。 - self.copy_template_to_current(self.range,self.range) + self.copy_template_to_current(group_range,group_range) + print(f"step-1") cursor = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) # SQLクエリを実行 query = f"SELECT * FROM {table} WHERE {where} LIMIT 1" cursor.execute(query) record = cursor.fetchone() - + + print(f"query={query}") + print(f"record={record}") if record: # group_rangeの範囲内のセルを走査 for row in self.current_worksheet: @@ -286,14 +299,36 @@ class SumasenExcel: cell.value = new_value cursor.close() - self.conn.close() return {"status": True, "message": f"Success generating group: "} except Exception as e: logging.error(f"Error in proceed_one_record: {str(e)}") return {"status": False, "message": f"Exception in proceed_one_record:Error generating report: {str(e)}"} - def proceed_all_record(self, table: str, where: str, sort: str, group_range: str, variables: Dict[str, Any]): + def get_column_letter(self, cell_reference): + """ + セル参照(例:'A12')から列文字を取得 + """ + if isinstance(cell_reference, str): + # アルファベット部分を抽出 + column = ''.join(c for c in cell_reference if c.isalpha()) + if column: + return column + return 'A' # デフォルト値 + + def get_row_number(self,cell_reference): + """ + セル参照(例:'A12')から行番号を取得 + """ + if isinstance(cell_reference, str): + # 数字部分のみを抽出 + digits = ''.join(c for c in cell_reference if c.isdigit()) + if digits: + return int(digits) + return int(cell_reference) + + + def proceed_all_records(self, table: str, where: str, sort: str, group_range: str, variables: Dict[str, Any]): """複数レコードを取得してシートの値を置き換える Args: @@ -303,11 +338,27 @@ class SumasenExcel: group_range: 処理対象範囲 variables: DB接続情報を含む変数辞書 """ - logging.info(f"make_report.proceed_all_record step-1:table={table},where={where},group_range={group_range}") + print(f"make_report.proceed_all_record step-1:table={table},where={where},group_range={group_range}") try: + # グループ範囲の行数を取得(セル参照対応) + if not group_range or ':' not in group_range: + raise ValueError(f"Invalid group_range format: {group_range}") + # グループ範囲の行数を取得 - start_row, end_row = map(int, group_range.split(':')[0].split(',')) + range_parts = group_range.split(':') + + logging.info(f"Processing range_parts: {range_parts}") # デバッグ用ログ + + start_row = self.get_row_number(range_parts[0].strip()) + start_col = self.get_column_letter(range_parts[0].strip()) + + end_row = self.get_row_number(range_parts[1].strip()) + end_col = self.get_column_letter(range_parts[1].strip()) + + if start_row > end_row: + raise ValueError(f"Invalid row range: start_row ({start_row}) > end_row ({end_row})") + template_rows = end_row - start_row + 1 cursor = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) @@ -316,13 +367,13 @@ class SumasenExcel: query = f"SELECT * FROM {table} WHERE {where} ORDER BY {sort}" cursor.execute(query) records = cursor.fetchall() - + print(f"query={query}, records={len(records)}") + current_row = start_row for record in records: # テンプレート範囲をコピー - self.copy_template_to_current( - f"{start_row},{end_row}", - f"{current_row},{current_row + template_rows - 1}" + self.copy_template_to_current(f"{start_col}{start_row}:{end_col}{end_row}", + f"{start_col}{current_row}:{end_col}{current_row + template_rows - 1}" ) # コピーした範囲内のセルを走査して値を置換 @@ -354,15 +405,43 @@ class SumasenExcel: logging.error(f"Error in proceed_all_record: {str(e)}") return {"status": False, "message": f"Exception in proceed_all_record:Error processing records: {str(e)}"} - def copy_template_to_current(self,range,target_range): - # テンプレートシートから現在のシートにデータをコピー - source_range = self.template_sheet.range(range) # コピーする範囲を指定 - target_range = self.current_sheet.range(target_range) # 貼り付け先の範囲を指定 + def copy_template_to_current(self, orig_range, target_range): + try: + print(f"orig_rage={orig_range},target_range={target_range}") + + # 範囲をパースする + min_col, min_row, max_col, max_row = range_boundaries(orig_range) + print(f"min_col, min_row, max_col, max_row = {min_col}, {min_row}, {max_col}, {max_row}") + + # 新しいシートを作成(必要な場合) + if 'Sheet' not in self.workbook.sheetnames: + self.current_sheet = self.workbook.create_sheet('Sheet') + else: + self.current_sheet = self.workbook['Sheet'] - # 値、数式、フォーマットをコピー - source_range.copy(target_range) - - + # セルの内容とスタイルをコピー + for row in range(min_row, max_row + 1): + for col in range(min_col, max_col + 1): + source_cell = self.template_sheet.cell(row=row, column=col) + target_cell = self.current_sheet.cell(row=row, column=col) + + # 値のコピー + target_cell.value = source_cell.value + + # スタイルのコピー + if source_cell.has_style: + target_cell.font = copy(source_cell.font) + target_cell.border = copy(source_cell.border) + target_cell.fill = copy(source_cell.fill) + target_cell.number_format = source_cell.number_format + target_cell.protection = copy(source_cell.protection) + target_cell.alignment = copy(source_cell.alignment) + + return {"status": True, "message": "Successfully copied template range"} + + except Exception as e: + logging.error(f"Error in copy_template_to_current: {str(e)}") + return {"status": False, "message": f"Exception in copy_template_to_current: {str(e)}"} # Style operations def apply_style( diff --git a/SumasenLibs/excel_lib/testdata/certificate_5033.xlsx b/SumasenLibs/excel_lib/testdata/certificate_5033.xlsx new file mode 100644 index 0000000..e852aa6 Binary files /dev/null and b/SumasenLibs/excel_lib/testdata/certificate_5033.xlsx differ diff --git a/SumasenLibs/excel_lib/testdata/sample.py b/SumasenLibs/excel_lib/testdata/sample.py index 2ccbdad..d7054ea 100644 --- a/SumasenLibs/excel_lib/testdata/sample.py +++ b/SumasenLibs/excel_lib/testdata/sample.py @@ -2,7 +2,16 @@ from sumaexcel import SumasenExcel import logging # 初期化 -variables = {"zekken_number":"5033","event_code":"FC岐阜"} +# 初期化 +variables = { + "zekken_number":"5033", + "event_code":"FC岐阜", + "db":"rogdb", + "username":"admin", + "password":"admin123456", + "host":"localhost", + "port":"5432" +} excel = SumasenExcel(document="test", variables=variables, docbase="./testdata") logging.info("Excelファイル作成 step-1") diff --git a/SumasenLibs/excel_lib/testdata/test.ini b/SumasenLibs/excel_lib/testdata/test.ini index 69ced26..9392f46 100644 --- a/SumasenLibs/excel_lib/testdata/test.ini +++ b/SumasenLibs/excel_lib/testdata/test.ini @@ -1,7 +1,7 @@ [basic] template_file=certificate_template.xlsx doc_file=certificate_[zekken_number].xlsx -sections=section1,section2 +sections=section1 maxcol=8 [section1] @@ -11,14 +11,14 @@ fit_to_width=1 orientation=portrait [section1.group1] -table_name=rog_entry -where=zekken_number='[zekken_number]' and event_code='[event_code]' -group_range=0,0,8,11 +table_name=mv_entry_details +where=zekken_number='[zekken_number]' and event_name='[event_code]' +group_range=A1:H11 [section1.group2] table_name=gps_checkins where=zekken_number='[zekken_number]' and event_code='[event_code]' -sort=order -group_range=0,12,8,12 +sort=path_order +group_range=A12:H12