# sumaexcel/excel.py import openpyxl from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.utils import get_column_letter import pandas as pd from typing import Optional, Dict, List, Union, Any import os import shutil from datetime import datetime from typing import Optional, Dict, Any, Union, Tuple from pathlib import Path from .styles import StyleManager from .merge import MergeManager from .image import ImageManager from .conditional import ConditionalFormatManager from .page import PageManager, PaperSizes class SumasenExcel: """Enhanced Excel handling class with extended functionality""" def __init__(self, debug: bool = False): self.debug = debug self.workbook = None self.template_filepath = None self.output_filepath = None self.current_sheet = None self._style_manager = None self._merge_manager = None self._image_manager = None self._conditional_manager = None self._page_manager = None def init(self, username: str, project_id: str, document: str, lang: str = "jp", docbase: str = "./docbase") -> Dict[str, str]: """Initialize Excel document with basic settings Args: username: User name for file operations project_id: Project identifier document: Document name lang: Language code (default: "jp") docbase: Base directory for documents (default: "./docbase") Returns: Dict with status ("ACK"/"NCK") and optional error message """ try: self.username = username self.project_id = project_id self.language = lang # Setup directory structure self.docpath = docbase self.docpath2 = f"{docbase}/{project_id}" # Create directories if they don't exist for path in [docbase, self.docpath2]: if not os.path.exists(path): os.makedirs(path, mode=0o755) # Load template inifile = f"{document}.ini" self.inifilepath = f"{self.docpath2}/{inifile}" if not os.path.exists(self.inifilepath): return {"status": "NCK", "message": f"INI file not found: {self.inifilepath}"} # Load template workbook template_file = self._get_ini_param("basic", f"templatefile_{lang}") self.template_filepath = f"{self.docpath2}/{template_file}" if not os.path.exists(self.template_filepath): # Copy from default if not exists default_template = f"{self.docpath}/{template_file}" shutil.copy2(default_template, self.template_filepath) self.workbook = openpyxl.load_workbook(self.template_filepath) return {"status": "ACK"} except Exception as e: return {"status": "NCK", "message": str(e)} def _get_ini_param(self, section: str, param: str) -> Optional[str]: """Get parameter from INI file Args: section: INI file section param: Parameter name Returns: Parameter value or None if not found """ try: # Use configparser to handle INI files import configparser config = configparser.ConfigParser() config.read(self.inifilepath) return config[section][param] except: return None def make_report(self, db, data_rec: Dict[str, Any], out_filename: Optional[str] = None, screen_index: int = 0) -> None: """Generate Excel report from template Args: db: Database connection data_rec: Data records to populate report out_filename: Optional output filename screen_index: Screen index for multi-screen reports """ # Get output filename if out_filename: outfile = f"{out_filename}_{self._get_ini_param('basic', 'doc_file')}" else: outfile = self._get_ini_param('basic', 'doc_file') self.output_filepath = f"{self.docpath2}/{outfile}" # Process sections sections = self._get_ini_param('basic', 'sections') if not sections: return for section in sections.split(','): self._process_section(section, db, data_rec, screen_index) # Save workbook self.workbook.save(self.output_filepath) def _process_section(self, section: str, db, data_rec: Dict[str, Any], screen_index: int) -> None: """Process individual section of report Args: section: Section name db: Database connection data_rec: Data records screen_index: Screen index """ # Get template sheet sheet_orig = self._get_ini_param(section, 'sheet') sheet_name = self._get_ini_param(section, f"sheetname_{self.language}") if not sheet_orig or not sheet_name: return # Copy template sheet template_sheet = self.workbook[sheet_orig] new_sheet = self.workbook.copy_worksheet(template_sheet) new_sheet.title = sheet_name # Process groups groups = self._get_ini_param(section, 'groups') if groups: for group in groups.split(','): self._process_group(new_sheet, section, group, db, data_rec, screen_index) def _process_group(self, sheet, section: str, group: str, db, data_rec: Dict[str, Any], screen_index: int) -> None: """Process group within section Args: sheet: Worksheet to process section: Section name group: Group name db: Database connection data_rec: Data records screen_index: Screen index """ pass # Implementation details will follow def init_sheet(self, sheet_name: str) -> None: """Initialize worksheet and managers""" self.current_sheet = self.workbook[sheet_name] 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) # Style operations def apply_style( self, cell_range: str, font: Dict[str, Any] = None, fill: Dict[str, Any] = None, border: Dict[str, Any] = None, alignment: Dict[str, Any] = None ) -> None: """Apply styles to cell range""" for row in self.current_sheet[cell_range]: for cell in row: if font: cell.font = self._style_manager.create_font(**font) if fill: cell.fill = self._style_manager.create_fill(**fill) if border: cell.border = self._style_manager.create_border(**border) if alignment: cell.alignment = self._style_manager.create_alignment(**alignment) # Merge operations def merge_range( self, start_row: int, start_col: int, end_row: int, end_col: int ) -> None: """Merge cell range""" self._merge_manager.merge_cells(start_row, start_col, end_row, end_col) # Image operations def add_image( self, image_path: Union[str, Path], position: Tuple[int, int], size: Optional[Tuple[int, int]] = None ) -> None: """Add image to worksheet""" self._image_manager.add_image(image_path, position, size) # Conditional formatting def add_conditional_format( self, cell_range: str, format_type: str, **kwargs ) -> None: """Add conditional formatting""" if format_type == 'color_scale': self._conditional_manager.add_color_scale(cell_range, **kwargs) elif format_type == 'data_bar': self._conditional_manager.add_data_bar(cell_range, **kwargs) elif format_type == 'icon_set': self._conditional_manager.add_icon_set(cell_range, **kwargs) elif format_type == 'custom': self._conditional_manager.add_custom_rule(cell_range, **kwargs) # Page setup def setup_page( self, orientation: str = 'portrait', paper_size: int = PaperSizes.A4, margins: Dict[str, float] = None, header_footer: Dict[str, Any] = None ) -> None: """Configure page setup""" self._page_manager.set_page_setup( orientation=orientation, paper_size=paper_size ) if margins: self._page_manager.set_margins(**margins) if header_footer: self._page_manager.set_header_footer(**header_footer) def cleanup(self) -> None: """Cleanup temporary files""" if self._image_manager: self._image_manager.cleanup() def __del__(self): """Destructor""" self.cleanup()