277 lines
9.1 KiB
Python
277 lines
9.1 KiB
Python
# 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()
|