Compare commits
3 Commits
106ab0e94e
...
9eb45d7e97
| Author | SHA1 | Date | |
|---|---|---|---|
| 9eb45d7e97 | |||
| 2aaecb6b22 | |||
| 6e472cf634 |
@ -3,6 +3,7 @@ FROM osgeo/gdal:ubuntu-small-3.4.0
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
||||||
LABEL maintainer="nouffer@gmail.com"
|
LABEL maintainer="nouffer@gmail.com"
|
||||||
LABEL description="Development image for the Rogaining JP"
|
LABEL description="Development image for the Rogaining JP"
|
||||||
|
|
||||||
@ -38,8 +39,28 @@ RUN apt-get install -y python3
|
|||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
python3-pip
|
python3-pip
|
||||||
|
|
||||||
|
# ベースイメージの更新とパッケージのインストール
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y \
|
||||||
|
libreoffice \
|
||||||
|
libreoffice-calc \
|
||||||
|
libreoffice-writer \
|
||||||
|
python3-uno # LibreOffice Python バインディング
|
||||||
|
|
||||||
|
# 作業ディレクトリとパーミッションの設定
|
||||||
|
RUN mkdir -p /app/docbase /tmp/libreoffice && \
|
||||||
|
chmod -R 777 /app/docbase /tmp/libreoffice
|
||||||
|
|
||||||
|
|
||||||
RUN pip install --upgrade pip
|
RUN pip install --upgrade pip
|
||||||
RUN pip install -e ./SumasenLibs/excel_lib
|
|
||||||
|
# Copy the package directory first
|
||||||
|
COPY SumasenLibs/excel_lib /app/SumasenLibs/excel_lib
|
||||||
|
COPY ./docbase /app/docbase
|
||||||
|
|
||||||
|
# Install the package in editable mode
|
||||||
|
RUN pip install -e /app/SumasenLibs/excel_lib
|
||||||
|
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
|
|
||||||
|
|||||||
@ -3,3 +3,4 @@ pandas>=1.0.0
|
|||||||
pillow>=8.0.0
|
pillow>=8.0.0
|
||||||
configparser>=5.0.0
|
configparser>=5.0.0
|
||||||
psycopg2-binary==2.9.9
|
psycopg2-binary==2.9.9
|
||||||
|
requests
|
||||||
|
|||||||
Binary file not shown.
@ -24,7 +24,32 @@ from .config_handler import ConfigHandler # ini file のロード
|
|||||||
#from .conditional import ConditionalFormatManager
|
#from .conditional import ConditionalFormatManager
|
||||||
from .page import PageManager, PaperSizes
|
from .page import PageManager, PaperSizes
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import pytz
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
import requests
|
||||||
|
from io import BytesIO
|
||||||
|
from PIL import Image
|
||||||
|
import re
|
||||||
|
from django.http import HttpResponse
|
||||||
|
import uuid
|
||||||
|
import unicodedata
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
from openpyxl.utils import get_column_letter
|
||||||
|
from openpyxl.drawing.image import Image as XLImage
|
||||||
|
from openpyxl.drawing.spreadsheet_drawing import OneCellAnchor, AnchorMarker
|
||||||
|
from openpyxl.utils.units import pixels_to_EMU
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.getLogger('PIL').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('openpyxl').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('PIL.PngImagePlugin').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('PIL.TiffImagePlugin').setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO, # INFOレベル以上のログを表示
|
level=logging.INFO, # INFOレベル以上のログを表示
|
||||||
@ -68,6 +93,8 @@ class SumasenExcel:
|
|||||||
self._page_manager = None
|
self._page_manager = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
logging.info("step-1")
|
||||||
|
|
||||||
# document base を設定
|
# document base を設定
|
||||||
self.docpath = docbase
|
self.docpath = docbase
|
||||||
if not os.path.exists(docbase):
|
if not os.path.exists(docbase):
|
||||||
@ -86,6 +113,8 @@ class SumasenExcel:
|
|||||||
|
|
||||||
self.basic = basic
|
self.basic = basic
|
||||||
|
|
||||||
|
logging.info("step-2")
|
||||||
|
|
||||||
# basicセクションから必要なパラメータを取得
|
# basicセクションから必要なパラメータを取得
|
||||||
template_file = basic.get("template_file")
|
template_file = basic.get("template_file")
|
||||||
if not template_file:
|
if not template_file:
|
||||||
@ -106,6 +135,7 @@ class SumasenExcel:
|
|||||||
if not sections:
|
if not sections:
|
||||||
logging.error("sections not found in basic section")
|
logging.error("sections not found in basic section")
|
||||||
|
|
||||||
|
logging.info("step-3")
|
||||||
|
|
||||||
# セクションをリストに変換
|
# セクションをリストに変換
|
||||||
self.section_list = [s.strip() for s in sections.split(",")]
|
self.section_list = [s.strip() for s in sections.split(",")]
|
||||||
@ -119,11 +149,15 @@ class SumasenExcel:
|
|||||||
# デフォルトで作成されるシートを削除
|
# デフォルトで作成されるシートを削除
|
||||||
self.workbook.remove(self.workbook.active)
|
self.workbook.remove(self.workbook.active)
|
||||||
|
|
||||||
|
logging.info("step-4")
|
||||||
|
|
||||||
# テンプレートワークブックをロード
|
# テンプレートワークブックをロード
|
||||||
self.template_filepath = f"{self.docpath}/{template_file}"
|
self.template_filepath = f"{self.docpath}/{template_file}"
|
||||||
if not os.path.exists(self.template_filepath):
|
if not os.path.exists(self.template_filepath):
|
||||||
logging.error(f"Template file not found: {self.template_filepath}")
|
logging.error(f"Template file not found: {self.template_filepath}")
|
||||||
|
|
||||||
|
logging.info("step-5")
|
||||||
|
|
||||||
self.template_workbook = openpyxl.load_workbook(self.template_filepath)
|
self.template_workbook = openpyxl.load_workbook(self.template_filepath)
|
||||||
self.template_sheet = self.template_workbook.active
|
self.template_sheet = self.template_workbook.active
|
||||||
|
|
||||||
@ -165,8 +199,17 @@ class SumasenExcel:
|
|||||||
return {"status": False, "message": f"Error no template sheet found: {template_sheet_name}"}
|
return {"status": False, "message": f"Error no template sheet found: {template_sheet_name}"}
|
||||||
|
|
||||||
# シートの名前を設定
|
# シートの名前を設定
|
||||||
new_sheet = self.workbook.create_sheet(title=section_config.get("sheet_name", section))
|
new_sheet_name = section_config.get("sheet_name", section)
|
||||||
self.worksheet = new_sheet
|
# Create new sheet with template name if it doesn't exist
|
||||||
|
if new_sheet_name not in self.workbook.sheetnames:
|
||||||
|
self.current_sheet = self.workbook.create_sheet(new_sheet_name)
|
||||||
|
else:
|
||||||
|
self.current_sheet = self.workbook[new_sheet_name]
|
||||||
|
|
||||||
|
|
||||||
|
# Remove default sheet if it exists
|
||||||
|
if 'Sheet' in self.workbook.sheetnames:
|
||||||
|
del self.workbook['Sheet']
|
||||||
|
|
||||||
self.dbname=variables.get('db')
|
self.dbname=variables.get('db')
|
||||||
self.user=variables.get('username')
|
self.user=variables.get('username')
|
||||||
@ -195,12 +238,17 @@ class SumasenExcel:
|
|||||||
# シートの幅を設定
|
# シートの幅を設定
|
||||||
fit_to_width = section_config.get("fit_to_width")
|
fit_to_width = section_config.get("fit_to_width")
|
||||||
if fit_to_width:
|
if fit_to_width:
|
||||||
new_sheet.sheet_view.zoomScaleNormal = float(fit_to_width)
|
self.current_sheet.sheet_view.zoomScaleNormal = float(fit_to_width)
|
||||||
|
|
||||||
# シートの向きを設定
|
# シートの向きを設定
|
||||||
orientation = section_config.get("orientation")
|
orientation = section_config.get("orientation")
|
||||||
new_sheet.sheet_view.orientation = orientation if orientation else "portrait"
|
self.current_sheet.sheet_view.orientation = orientation if orientation else "portrait"
|
||||||
self.current_worksheet = new_sheet
|
|
||||||
|
max_col = self.basic.get("maxcol",20)
|
||||||
|
if not max_col:
|
||||||
|
return {"status": False, "message": f"Error no maxcol found: basic"}
|
||||||
|
#self.set_column_width(1,int(max_col))
|
||||||
|
self.set_column_width_from_config(self.current_sheet)
|
||||||
|
|
||||||
# グループ定義を取得
|
# グループ定義を取得
|
||||||
groups = section_config.get("groups")
|
groups = section_config.get("groups")
|
||||||
@ -253,6 +301,150 @@ class SumasenExcel:
|
|||||||
logging.error(f"Error in proceed_group: {str(e)}")
|
logging.error(f"Error in proceed_group: {str(e)}")
|
||||||
return {"status": False, "message": f"Exception in proceed_group : Error generating report: {str(e)}"}
|
return {"status": False, "message": f"Exception in proceed_group : Error generating report: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
|
def format_cell_value(self, field_value, cell):
|
||||||
|
"""セルの値を適切な形式に変換する
|
||||||
|
|
||||||
|
Args:
|
||||||
|
field_value: DBから取得した値
|
||||||
|
cell: 対象のExcelセル
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
変換後の値
|
||||||
|
"""
|
||||||
|
# Noneの場合は空文字を返す
|
||||||
|
if field_value is None:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# 真偽値の場合
|
||||||
|
if isinstance(field_value, bool):
|
||||||
|
return "OK" if field_value else "-"
|
||||||
|
|
||||||
|
# 日時型の場合
|
||||||
|
if isinstance(field_value, datetime):
|
||||||
|
jst = pytz.timezone('Asia/Tokyo')
|
||||||
|
# UTC -> JST変換
|
||||||
|
jst_time = field_value.astimezone(jst)
|
||||||
|
return jst_time.strftime('%H:%M:%S')
|
||||||
|
|
||||||
|
# 文字列の場合の処理
|
||||||
|
if isinstance(field_value, str):
|
||||||
|
try:
|
||||||
|
# 画像ファイルパスを取得する関数
|
||||||
|
def get_image_path(value):
|
||||||
|
# URLの場合
|
||||||
|
if value.startswith('https://'):
|
||||||
|
# URLからファイル名を抽出
|
||||||
|
filename = os.path.basename(urlparse(value).path)
|
||||||
|
return os.path.join("/app/media/compressed", filename)
|
||||||
|
# checkinパスの場合
|
||||||
|
elif value.startswith('checkin/'):
|
||||||
|
return os.path.join("/app/media", value)
|
||||||
|
# ファイル名のみの場合
|
||||||
|
elif re.search(r'\.(jpg|jpeg|png|gif|bmp|mpo)$', value, re.I):
|
||||||
|
return os.path.join("/app/media/compressed", value)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 画像パスを取得
|
||||||
|
image_path = get_image_path(field_value)
|
||||||
|
|
||||||
|
if image_path and os.path.exists(image_path):
|
||||||
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
import io
|
||||||
|
|
||||||
|
# 画像を開く
|
||||||
|
with Image.open(image_path) as img:
|
||||||
|
# RGBAの場合はRGBに変換
|
||||||
|
if img.mode in ('RGBA', 'LA'):
|
||||||
|
background = Image.new('RGB', img.size, (255, 255, 255))
|
||||||
|
background.paste(img, mask=img.split()[-1])
|
||||||
|
img = background
|
||||||
|
elif img.mode not in ('RGB', 'L'):
|
||||||
|
img = img.convert('RGB')
|
||||||
|
|
||||||
|
# ワークシートを取得
|
||||||
|
worksheet = cell.parent
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 列の幅を取得(文字単位からピクセルに変換)
|
||||||
|
column_letter = get_column_letter(cell.column)
|
||||||
|
column_width = worksheet.column_dimensions[column_letter].width
|
||||||
|
cell_width = int((column_width or 8.43) * 7.5) # 8.43は標準の文字幅
|
||||||
|
except Exception:
|
||||||
|
cell_width = 100 # デフォルト値
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 行の高さを取得(ポイント単位からピクセルに変換)
|
||||||
|
row_height = worksheet.row_dimensions[cell.row].height
|
||||||
|
cell_height = int((row_height or 15) * 1.33) # 15は標準の行の高さ
|
||||||
|
except Exception:
|
||||||
|
cell_height = 20 # デフォルト値
|
||||||
|
|
||||||
|
# 最小サイズを設定
|
||||||
|
cell_width = max(cell_width, 100)
|
||||||
|
cell_height = max(cell_height, 20)
|
||||||
|
|
||||||
|
# 最大サイズを設定
|
||||||
|
max_width = 800
|
||||||
|
max_height = 600
|
||||||
|
cell_width = min(cell_width, max_width)
|
||||||
|
cell_height = min(cell_height, max_height)
|
||||||
|
|
||||||
|
# アスペクト比を保持しながらリサイズ
|
||||||
|
img_width, img_height = img.size
|
||||||
|
img_aspect = img_width / img_height
|
||||||
|
cell_aspect = cell_width / cell_height
|
||||||
|
|
||||||
|
if img_aspect > cell_aspect:
|
||||||
|
width = cell_width
|
||||||
|
height = int(width / img_aspect)
|
||||||
|
else:
|
||||||
|
height = cell_height
|
||||||
|
width = int(height * img_aspect)
|
||||||
|
|
||||||
|
# 画像をリサイズ
|
||||||
|
img_resized = img.resize((width, height), Image.BICUBIC)
|
||||||
|
|
||||||
|
# BytesIOオブジェクトを作成して画像を保存
|
||||||
|
img_byte_arr = io.BytesIO()
|
||||||
|
img_resized.save(img_byte_arr, format='JPEG', quality=85, optimize=True)
|
||||||
|
img_byte_arr.seek(0)
|
||||||
|
|
||||||
|
# OpenPyXLのImageオブジェクトを作成
|
||||||
|
from openpyxl.drawing.image import Image as XLImage
|
||||||
|
excel_image = XLImage(img_byte_arr)
|
||||||
|
|
||||||
|
# 画像の配置
|
||||||
|
excel_image.anchor = f"{column_letter}{cell.row}"
|
||||||
|
|
||||||
|
# 画像をワークシートに追加
|
||||||
|
worksheet.add_image(excel_image)
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"画像の処理に失敗: {str(e)}, image_path={image_path}")
|
||||||
|
import traceback
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
return field_value
|
||||||
|
else:
|
||||||
|
if image_path:
|
||||||
|
logging.warning(f"画像ファイルが存在しません: {image_path}")
|
||||||
|
return field_value
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"形式変換の処理に失敗: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
return field_value
|
||||||
|
|
||||||
|
# その他の場合は文字列に変換
|
||||||
|
return str(field_value)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def proceed_one_record(self,table:str,where:str,group_range:str,variables: Dict[str, Any]):
|
def proceed_one_record(self,table:str,where:str,group_range:str,variables: Dict[str, Any]):
|
||||||
"""1レコードのデータを取得してシートの値を置き換える
|
"""1レコードのデータを取得してシートの値を置き換える
|
||||||
|
|
||||||
@ -268,6 +460,7 @@ class SumasenExcel:
|
|||||||
# まずself.template_sheetの指定範囲のセルをself.current_sheetにコピーする。
|
# まずself.template_sheetの指定範囲のセルをself.current_sheetにコピーする。
|
||||||
self.copy_template_to_current(group_range,group_range)
|
self.copy_template_to_current(group_range,group_range)
|
||||||
|
|
||||||
|
|
||||||
print(f"step-1")
|
print(f"step-1")
|
||||||
cursor = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
cursor = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||||
|
|
||||||
@ -280,7 +473,7 @@ class SumasenExcel:
|
|||||||
print(f"record={record}")
|
print(f"record={record}")
|
||||||
if record:
|
if record:
|
||||||
# group_rangeの範囲内のセルを走査
|
# group_rangeの範囲内のセルを走査
|
||||||
for row in self.current_worksheet:
|
for row in self.current_sheet:
|
||||||
for cell in row:
|
for cell in row:
|
||||||
if cell.value and isinstance(cell.value, str):
|
if cell.value and isinstance(cell.value, str):
|
||||||
# [field_name]形式の文字列を検索
|
# [field_name]形式の文字列を検索
|
||||||
@ -292,9 +485,11 @@ class SumasenExcel:
|
|||||||
new_value = cell.value
|
new_value = cell.value
|
||||||
for field_name in matches:
|
for field_name in matches:
|
||||||
if field_name in record:
|
if field_name in record:
|
||||||
|
# 新しい形式変換関数を使用
|
||||||
|
formatted_value = self.format_cell_value(record[field_name], cell)
|
||||||
new_value = new_value.replace(
|
new_value = new_value.replace(
|
||||||
f'[{field_name}]',
|
f'[{field_name}]',
|
||||||
str(record[field_name])
|
formatted_value
|
||||||
)
|
)
|
||||||
cell.value = new_value
|
cell.value = new_value
|
||||||
|
|
||||||
@ -378,7 +573,7 @@ class SumasenExcel:
|
|||||||
|
|
||||||
# コピーした範囲内のセルを走査して値を置換
|
# コピーした範囲内のセルを走査して値を置換
|
||||||
for row in range(current_row, current_row + template_rows):
|
for row in range(current_row, current_row + template_rows):
|
||||||
for cell in self.current_worksheet[row]:
|
for cell in self.current_sheet[row]:
|
||||||
if cell.value and isinstance(cell.value, str):
|
if cell.value and isinstance(cell.value, str):
|
||||||
# [field_name]形式の文字列を検索
|
# [field_name]形式の文字列を検索
|
||||||
import re
|
import re
|
||||||
@ -389,9 +584,11 @@ class SumasenExcel:
|
|||||||
new_value = cell.value
|
new_value = cell.value
|
||||||
for field_name in matches:
|
for field_name in matches:
|
||||||
if field_name in record:
|
if field_name in record:
|
||||||
|
# 新しい形式変換関数を使用
|
||||||
|
formatted_value = self.format_cell_value(record[field_name], cell)
|
||||||
new_value = new_value.replace(
|
new_value = new_value.replace(
|
||||||
f'[{field_name}]',
|
f'[{field_name}]',
|
||||||
str(record[field_name])
|
formatted_value
|
||||||
)
|
)
|
||||||
cell.value = new_value
|
cell.value = new_value
|
||||||
|
|
||||||
@ -405,40 +602,224 @@ class SumasenExcel:
|
|||||||
logging.error(f"Error in proceed_all_record: {str(e)}")
|
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)}"}
|
return {"status": False, "message": f"Exception in proceed_all_record:Error processing records: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
|
def set_column_width_from_config(self, worksheet):
|
||||||
|
"""
|
||||||
|
INIファイルの設定に基づいて列幅を設定する
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
worksheet: 設定対象のワークシート
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 処理結果を示す辞書
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
section_config = self.conf.get_section('basic')
|
||||||
|
|
||||||
|
# basic セクションから column_width を読み取る
|
||||||
|
if not section_config.get('column_width'):
|
||||||
|
raise ValueError("column_width setting not found in [basic] section")
|
||||||
|
|
||||||
|
# カンマ区切りの文字列を数値のリストに変換
|
||||||
|
width_str = section_config.get('column_width')
|
||||||
|
widths = [float(w.strip()) for w in width_str.split(',')]
|
||||||
|
|
||||||
|
# 各列に幅を設定
|
||||||
|
for col_index, width in enumerate(widths, start=1):
|
||||||
|
col_letter = get_column_letter(col_index)
|
||||||
|
worksheet.column_dimensions[col_letter].width = width
|
||||||
|
logging.info(f"Set column {col_letter} width to {width}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": True,
|
||||||
|
"message": "Column widths set successfully from config",
|
||||||
|
"details": {
|
||||||
|
"num_columns": len(widths),
|
||||||
|
"widths": widths
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
except ValueError as ve:
|
||||||
|
logging.error(f"Invalid column width value in config: {str(ve)}")
|
||||||
|
return {
|
||||||
|
"status": False,
|
||||||
|
"message": f"Invalid column width configuration: {str(ve)}"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error setting column widths: {str(e)}")
|
||||||
|
return {
|
||||||
|
"status": False,
|
||||||
|
"message": f"Error setting column widths: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def verify_column_widths(self, worksheet, expected_widths=None):
|
||||||
|
"""
|
||||||
|
列幅が正しく設定されているか検証する
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
worksheet: 検証対象のワークシート
|
||||||
|
expected_widths: 期待される幅のリスト(指定がない場合はINIファイルから読み取り)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: 検証結果のリスト
|
||||||
|
"""
|
||||||
|
if expected_widths is None:
|
||||||
|
width_str = self.config.get('basic', 'column_width')
|
||||||
|
expected_widths = [float(w.strip()) for w in width_str.split(',')]
|
||||||
|
|
||||||
|
verification_results = []
|
||||||
|
for col_index, expected_width in enumerate(expected_widths, start=1):
|
||||||
|
col_letter = get_column_letter(col_index)
|
||||||
|
actual_width = worksheet.column_dimensions[col_letter].width
|
||||||
|
|
||||||
|
matches = abs(float(actual_width) - float(expected_width)) < 0.01
|
||||||
|
verification_results.append({
|
||||||
|
'column': col_letter,
|
||||||
|
'expected_width': expected_width,
|
||||||
|
'actual_width': actual_width,
|
||||||
|
'matches': matches
|
||||||
|
})
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
logging.warning(
|
||||||
|
f"Column {col_letter} width mismatch: "
|
||||||
|
f"expected={expected_width}, actual={actual_width}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return verification_results
|
||||||
|
|
||||||
|
def copy_template_range(self, orig_min_row, orig_min_col, orig_max_row, orig_max_col, target_min_row):
|
||||||
|
"""
|
||||||
|
テンプレートの範囲をコピーし、マージセルの罫線を正しく設定する
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# マージセルの情報を保存
|
||||||
|
merged_ranges = []
|
||||||
|
|
||||||
|
# マージセルをコピー
|
||||||
|
for merged_range in self.template_sheet.merged_cells:
|
||||||
|
min_col, min_row, max_col, max_row = range_boundaries(str(merged_range))
|
||||||
|
|
||||||
|
# コピー範囲と重なるマージセルをチェック
|
||||||
|
if (min_col >= orig_min_col and max_col <= orig_max_col and
|
||||||
|
min_row >= orig_min_row and max_row <= orig_max_row):
|
||||||
|
# ターゲットのマージ範囲を計算
|
||||||
|
target_merge_min_row = target_min_row + (min_row - orig_min_row)
|
||||||
|
target_merge_max_row = target_min_row + (max_row - orig_min_row)
|
||||||
|
target_merge_range = f"{get_column_letter(min_col)}{target_merge_min_row}:" \
|
||||||
|
f"{get_column_letter(max_col)}{target_merge_max_row}"
|
||||||
|
|
||||||
|
# マージ情報を保存
|
||||||
|
merged_ranges.append({
|
||||||
|
'range': target_merge_range,
|
||||||
|
'min_col': min_col,
|
||||||
|
'max_col': max_col,
|
||||||
|
'min_row': target_merge_min_row,
|
||||||
|
'max_row': target_merge_max_row,
|
||||||
|
'source_cell': self.template_sheet.cell(row=min_row, column=min_col)
|
||||||
|
})
|
||||||
|
|
||||||
|
# セルをマージ
|
||||||
|
self.current_sheet.merge_cells(target_merge_range)
|
||||||
|
|
||||||
|
# セルの内容とスタイルをコピー
|
||||||
|
row_offset = target_min_row - orig_min_row
|
||||||
|
for row in range(orig_min_row, orig_max_row + 1):
|
||||||
|
for col in range(orig_min_col, orig_max_col + 1):
|
||||||
|
source_cell = self.template_sheet.cell(row=row, column=col)
|
||||||
|
target_cell = self.current_sheet.cell(row=row+row_offset, column=col)
|
||||||
|
|
||||||
|
# 値とスタイルをコピー
|
||||||
|
if source_cell.value is not None:
|
||||||
|
target_cell.value = source_cell.value
|
||||||
|
|
||||||
|
if source_cell.has_style:
|
||||||
|
target_cell.font = copy(source_cell.font)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# マージセルの罫線を設定
|
||||||
|
for merge_info in merged_ranges:
|
||||||
|
source_cell = merge_info['source_cell']
|
||||||
|
source_border = source_cell.border
|
||||||
|
|
||||||
|
# マージ範囲内の各セルに罫線を設定
|
||||||
|
for row in range(merge_info['min_row'], merge_info['max_row'] + 1):
|
||||||
|
for col in range(merge_info['min_col'], merge_info['max_col'] + 1):
|
||||||
|
target_cell = self.current_sheet.cell(row=row, column=col)
|
||||||
|
|
||||||
|
# 新しい罫線スタイルを作成
|
||||||
|
new_border = copy(source_border)
|
||||||
|
|
||||||
|
# 範囲の端のセルの場合のみ、対応する辺の罫線を設定
|
||||||
|
if col == merge_info['min_col']: # 左端
|
||||||
|
new_border.left = copy(source_border.left)
|
||||||
|
if col == merge_info['max_col']: # 右端
|
||||||
|
new_border.right = copy(source_border.right)
|
||||||
|
if row == merge_info['min_row']: # 上端
|
||||||
|
new_border.top = copy(source_border.top)
|
||||||
|
if row == merge_info['max_row']: # 下端
|
||||||
|
new_border.bottom = copy(source_border.bottom)
|
||||||
|
|
||||||
|
target_cell.border = new_border
|
||||||
|
|
||||||
|
return {"status": True, "message": "Range copied successfully with merged cell borders"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error copying template range: {str(e)}")
|
||||||
|
return {"status": False, "message": f"Error copying template range: {str(e)}"}
|
||||||
|
|
||||||
|
def verify_merged_cell_borders(self, worksheet, merge_range):
|
||||||
|
"""
|
||||||
|
マージセルの罫線が正しく設定されているか検証する
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
min_col, min_row, max_col, max_row = range_boundaries(merge_range)
|
||||||
|
|
||||||
|
border_status = {
|
||||||
|
'top': [],
|
||||||
|
'bottom': [],
|
||||||
|
'left': [],
|
||||||
|
'right': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 各辺の罫線をチェック
|
||||||
|
for row in range(min_row, max_row + 1):
|
||||||
|
for col in range(min_col, max_col + 1):
|
||||||
|
cell = worksheet.cell(row=row, column=col)
|
||||||
|
|
||||||
|
if col == min_col: # 左辺
|
||||||
|
border_status['left'].append(cell.border.left)
|
||||||
|
if col == max_col: # 右辺
|
||||||
|
border_status['right'].append(cell.border.right)
|
||||||
|
if row == min_row: # 上辺
|
||||||
|
border_status['top'].append(cell.border.top)
|
||||||
|
if row == max_row: # 下辺
|
||||||
|
border_status['bottom'].append(cell.border.bottom)
|
||||||
|
|
||||||
|
return border_status
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error verifying merged cell borders: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def copy_template_to_current(self, orig_range, target_range):
|
def copy_template_to_current(self, orig_range, target_range):
|
||||||
try:
|
try:
|
||||||
print(f"orig_rage={orig_range},target_range={target_range}")
|
#print(f"copy_template_to_current : orig_range={orig_range},target_range={target_range}")
|
||||||
|
|
||||||
# 範囲をパースする
|
# 範囲をパースする
|
||||||
orig_min_col, orig_min_row, orig_max_col, orig_max_row = range_boundaries(orig_range)
|
orig_min_col, orig_min_row, orig_max_col, orig_max_row = range_boundaries(orig_range)
|
||||||
target_min_col, target_min_row, target_max_col, target_max_row = range_boundaries(target_range)
|
target_min_col, target_min_row, target_max_col, target_max_row = range_boundaries(target_range)
|
||||||
print(f"min_col, min_row, max_col, max_row = {orig_min_col}, {orig_min_row}, {orig_max_col}, {orig_max_row}")
|
#print(f"min_col, min_row, max_col, max_row = {orig_min_col}, {orig_min_row}, {orig_max_col}, {orig_max_row}")
|
||||||
|
|
||||||
print(f"min_col, min_row, max_col, max_row = {target_min_col}, {target_min_row}, {target_max_col}, {target_max_row}")
|
|
||||||
|
|
||||||
# Get template sheet name from ini file
|
# Get template sheet name from ini file
|
||||||
section_config = self.conf.get_section(self.section_list[0]) # 現在の処理中のセクション
|
section_config = self.conf.get_section(self.section_list[0]) # 現在の処理中のセクション
|
||||||
template_sheet_name = section_config.get("template_sheet")
|
template_sheet_name = section_config.get("template_sheet")
|
||||||
|
new_sheet_name = section_config.get("sheet_name",template_sheet_name)
|
||||||
if not template_sheet_name:
|
|
||||||
raise ValueError("Template sheet name not found in configuration")
|
|
||||||
|
|
||||||
# Create new sheet with template name if it doesn't exist
|
|
||||||
if template_sheet_name not in self.workbook.sheetnames:
|
|
||||||
self.current_sheet = self.workbook.create_sheet(template_sheet_name)
|
|
||||||
else:
|
|
||||||
self.current_sheet = self.workbook[template_sheet_name]
|
|
||||||
|
|
||||||
# Remove default sheet if it exists
|
|
||||||
if 'Sheet' in self.workbook.sheetnames:
|
|
||||||
del self.workbook['Sheet']
|
|
||||||
|
|
||||||
# Copy column widths
|
|
||||||
for col in range(orig_min_col, orig_max_col + 1):
|
|
||||||
col_letter = get_column_letter(col)
|
|
||||||
if col_letter in self.template_sheet.column_dimensions:
|
|
||||||
self.current_sheet.column_dimensions[col_letter].width = \
|
|
||||||
self.template_sheet.column_dimensions[col_letter].width
|
|
||||||
|
|
||||||
# Copy row heights
|
# Copy row heights
|
||||||
for row in range(orig_min_row, orig_max_row + 1):
|
for row in range(orig_min_row, orig_max_row + 1):
|
||||||
@ -449,19 +830,24 @@ class SumasenExcel:
|
|||||||
if target_row not in self.current_sheet.row_dimensions:
|
if target_row not in self.current_sheet.row_dimensions:
|
||||||
self.current_sheet.row_dimensions[target_row] = openpyxl.worksheet.dimensions.RowDimension(target_row)
|
self.current_sheet.row_dimensions[target_row] = openpyxl.worksheet.dimensions.RowDimension(target_row)
|
||||||
self.current_sheet.row_dimensions[target_row].height = source_height
|
self.current_sheet.row_dimensions[target_row].height = source_height
|
||||||
|
#print(f"Row(target_row).height = {source_height}")
|
||||||
|
|
||||||
# Copy merged cells
|
# Copy merged cells
|
||||||
for merged_range in self.template_sheet.merged_cells:
|
self.copy_template_range(orig_min_row, orig_min_col, orig_max_row, orig_max_col, target_min_row)
|
||||||
min_col, min_row, max_col, max_row = range_boundaries(str(merged_range))
|
|
||||||
# Check if merge range intersects with our copy range
|
#for merged_range in self.template_sheet.merged_cells:
|
||||||
if (min_col >= orig_min_col and max_col <= orig_max_col and
|
# min_col, min_row, max_col, max_row = range_boundaries(str(merged_range))
|
||||||
min_row >= orig_min_row and max_row <= orig_max_row):
|
# #print(f"Merge cell: min_col, min_row, max_col, max_row = {min_col}, {min_row}, {max_col}, {max_row}")
|
||||||
# Calculate target merge range
|
# # Check if merge range intersects with our copy range
|
||||||
target_merge_min_row = target_min_row + (min_row - orig_min_row)
|
# if (min_col >= orig_min_col and max_col <= orig_max_col and
|
||||||
target_merge_max_row = target_min_row + (max_row - orig_min_row)
|
# min_row >= orig_min_row and max_row <= orig_max_row):
|
||||||
target_merge_range = f"{get_column_letter(min_col)}{target_merge_min_row}:" \
|
# # Calculate target merge range
|
||||||
f"{get_column_letter(max_col)}{target_merge_max_row}"
|
# target_merge_min_row = target_min_row + (min_row - orig_min_row)
|
||||||
self.current_sheet.merge_cells(target_merge_range)
|
# target_merge_max_row = target_min_row + (max_row - orig_min_row)
|
||||||
|
# target_merge_range = f"{get_column_letter(min_col)}{target_merge_min_row}:" \
|
||||||
|
# f"{get_column_letter(max_col)}{target_merge_max_row}"
|
||||||
|
# self.current_sheet.merge_cells(target_merge_range)
|
||||||
|
# #print(f"Merge : {target_merge_range}")
|
||||||
|
|
||||||
# Copy cell contents and styles
|
# Copy cell contents and styles
|
||||||
row_offset = target_min_row - orig_min_row
|
row_offset = target_min_row - orig_min_row
|
||||||
@ -470,17 +856,18 @@ class SumasenExcel:
|
|||||||
source_cell = self.template_sheet.cell(row=row, column=col)
|
source_cell = self.template_sheet.cell(row=row, column=col)
|
||||||
target_cell = self.current_sheet.cell(row=row+row_offset, column=col)
|
target_cell = self.current_sheet.cell(row=row+row_offset, column=col)
|
||||||
|
|
||||||
# Copy value
|
if source_cell.value:
|
||||||
target_cell.value = source_cell.value
|
# Copy value
|
||||||
|
target_cell.value = source_cell.value
|
||||||
# Copy styles
|
#print(f"({col},{row}) : {target_cell.value}")
|
||||||
if source_cell.has_style:
|
# Copy styles
|
||||||
target_cell.font = copy(source_cell.font)
|
if source_cell.has_style:
|
||||||
target_cell.border = copy(source_cell.border)
|
target_cell.font = copy(source_cell.font)
|
||||||
target_cell.fill = copy(source_cell.fill)
|
target_cell.border = copy(source_cell.border)
|
||||||
target_cell.number_format = source_cell.number_format
|
target_cell.fill = copy(source_cell.fill)
|
||||||
target_cell.protection = copy(source_cell.protection)
|
target_cell.number_format = source_cell.number_format
|
||||||
target_cell.alignment = copy(source_cell.alignment)
|
target_cell.protection = copy(source_cell.protection)
|
||||||
|
target_cell.alignment = copy(source_cell.alignment)
|
||||||
|
|
||||||
# Copy page setup
|
# Copy page setup
|
||||||
target_page_setup = self.current_sheet.page_setup
|
target_page_setup = self.current_sheet.page_setup
|
||||||
|
|||||||
BIN
SumasenLibs/excel_lib/testdata/certificate_5033.xlsx
vendored
BIN
SumasenLibs/excel_lib/testdata/certificate_5033.xlsx
vendored
Binary file not shown.
10
SumasenLibs/excel_lib/testdata/test.ini
vendored
10
SumasenLibs/excel_lib/testdata/test.ini
vendored
@ -2,10 +2,12 @@
|
|||||||
template_file=certificate_template.xlsx
|
template_file=certificate_template.xlsx
|
||||||
doc_file=certificate_[zekken_number].xlsx
|
doc_file=certificate_[zekken_number].xlsx
|
||||||
sections=section1
|
sections=section1
|
||||||
maxcol=8
|
maxcol=10
|
||||||
|
column_width=3,5,16,16,16,16,16,8,8,12,3
|
||||||
|
|
||||||
[section1]
|
[section1]
|
||||||
template_sheet=certificate
|
template_sheet=certificate
|
||||||
|
sheet_name=certificate
|
||||||
groups=group1,group2
|
groups=group1,group2
|
||||||
fit_to_width=1
|
fit_to_width=1
|
||||||
orientation=portrait
|
orientation=portrait
|
||||||
@ -13,12 +15,12 @@ orientation=portrait
|
|||||||
[section1.group1]
|
[section1.group1]
|
||||||
table_name=mv_entry_details
|
table_name=mv_entry_details
|
||||||
where=zekken_number='[zekken_number]' and event_name='[event_code]'
|
where=zekken_number='[zekken_number]' and event_name='[event_code]'
|
||||||
group_range=A1:H11
|
group_range=A1:J12
|
||||||
|
|
||||||
|
|
||||||
[section1.group2]
|
[section1.group2]
|
||||||
table_name=gps_checkins
|
table_name=v_checkins_locations
|
||||||
where=zekken_number='[zekken_number]' and event_code='[event_code]'
|
where=zekken_number='[zekken_number]' and event_code='[event_code]'
|
||||||
sort=path_order
|
sort=path_order
|
||||||
group_range=A12:H12
|
group_range=A13:J13
|
||||||
|
|
||||||
|
|||||||
26
docbase/certificate.ini
Normal file
26
docbase/certificate.ini
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[basic]
|
||||||
|
template_file=certificate_template.xlsx
|
||||||
|
doc_file=certificate_[zekken_number].xlsx
|
||||||
|
sections=section1
|
||||||
|
maxcol=10
|
||||||
|
column_width=3,5,16,16,16,16,16,8,8,12,3
|
||||||
|
|
||||||
|
[section1]
|
||||||
|
template_sheet=certificate
|
||||||
|
sheet_name=certificate
|
||||||
|
groups=group1,group2
|
||||||
|
fit_to_width=1
|
||||||
|
orientation=portrait
|
||||||
|
|
||||||
|
[section1.group1]
|
||||||
|
table_name=mv_entry_details
|
||||||
|
where=zekken_number='[zekken_number]' and event_name='[event_code]'
|
||||||
|
group_range=A1:J12
|
||||||
|
|
||||||
|
|
||||||
|
[section1.group2]
|
||||||
|
table_name=v_checkins_locations
|
||||||
|
where=zekken_number='[zekken_number]' and event_code='[event_code]'
|
||||||
|
sort=path_order
|
||||||
|
group_range=A13:J13
|
||||||
|
|
||||||
BIN
docbase/certificate_template.xlsx
Normal file
BIN
docbase/certificate_template.xlsx
Normal file
Binary file not shown.
@ -631,17 +631,12 @@ class GpsCheckin(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'gps_checkins'
|
db_table = 'gps_checkins'
|
||||||
constraints = [
|
|
||||||
models.UniqueConstraint(
|
|
||||||
fields=['zekken_number', 'event_code', 'path_order'],
|
|
||||||
name='unique_gps_checkin'
|
|
||||||
)
|
|
||||||
]
|
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['zekken_number', 'event_code','path_order'], name='idx_zekken_event'),
|
models.Index(fields=['zekken_number', 'event_code', 'path_order'], name='idx_zekken_event'),
|
||||||
models.Index(fields=['create_at'], name='idx_create_at'),
|
models.Index(fields=['create_at'], name='idx_create_at'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.event_code}-{self.zekken_number}-{self.path_order}-buy:{self.buy_flag}-valid:{self.validate_location}-point:{self.points}"
|
return f"{self.event_code}-{self.zekken_number}-{self.path_order}-buy:{self.buy_flag}-valid:{self.validate_location}-point:{self.points}"
|
||||||
|
|
||||||
|
|||||||
501
rog/views.py
501
rog/views.py
@ -5,6 +5,10 @@ User = get_user_model()
|
|||||||
import traceback
|
import traceback
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
|
|
||||||
|
import subprocess # subprocessモジュールを追加
|
||||||
|
import tempfile # tempfileモジュールを追加
|
||||||
|
import shutil # shutilモジュールを追加
|
||||||
|
|
||||||
from django.contrib.auth.tokens import default_token_generator
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
|
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
|
||||||
from django.utils.encoding import force_bytes, force_str
|
from django.utils.encoding import force_bytes, force_str
|
||||||
@ -90,7 +94,7 @@ from io import BytesIO
|
|||||||
from django.urls import get_resolver
|
from django.urls import get_resolver
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
from django.http import HttpResponse
|
||||||
from sumaexcel import SumasenExcel
|
from sumaexcel import SumasenExcel
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -2538,6 +2542,80 @@ def get_checkins(request, *args, **kwargs):
|
|||||||
|
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
def update_checkins(request):
|
def update_checkins(request):
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
update_base = request.data
|
||||||
|
logger.info(f"Processing update data: {update_base}")
|
||||||
|
zekken_number = update_base['zekken_number']
|
||||||
|
event_code = update_base['event_code']
|
||||||
|
|
||||||
|
# 既存レコードの更新
|
||||||
|
for update in update_base['checkins']:
|
||||||
|
if 'id' in update and int(update['id']) > 0:
|
||||||
|
try:
|
||||||
|
checkin = GpsCheckin.objects.get(id=update['id'])
|
||||||
|
logger.info(f"Updating existing checkin: {checkin}")
|
||||||
|
|
||||||
|
# 既存レコードの更新
|
||||||
|
checkin.path_order = update['order']
|
||||||
|
checkin.buy_flag = update.get('buy_flag', False)
|
||||||
|
checkin.validate_location = update.get('validation', False)
|
||||||
|
checkin.points = update.get('points', 0)
|
||||||
|
checkin.update_at = timezone.now()
|
||||||
|
checkin.update_user = request.user.email if request.user.is_authenticated else None
|
||||||
|
checkin.save()
|
||||||
|
logger.info(f"Updated existing checkin result: {checkin}")
|
||||||
|
|
||||||
|
except GpsCheckin.DoesNotExist:
|
||||||
|
logger.error(f"Checkin with id {update['id']} not found")
|
||||||
|
continue # エラーを無視して次のレコードの処理を継続
|
||||||
|
|
||||||
|
# 新規レコードの作成
|
||||||
|
for update in update_base['checkins']:
|
||||||
|
if 'id' in update and int(update['id']) == 0:
|
||||||
|
logger.info(f"Creating new checkin: {update}")
|
||||||
|
try:
|
||||||
|
checkin = GpsCheckin.objects.create(
|
||||||
|
zekken_number=zekken_number,
|
||||||
|
event_code=event_code,
|
||||||
|
path_order=update['order'],
|
||||||
|
cp_number=update['cp_number'],
|
||||||
|
validate_location=update.get('validation', False),
|
||||||
|
buy_flag=update.get('buy_flag', False),
|
||||||
|
points=update.get('points', 0),
|
||||||
|
create_at=timezone.now(),
|
||||||
|
update_at=timezone.now(),
|
||||||
|
create_user=request.user.email if request.user.is_authenticated else None,
|
||||||
|
update_user=request.user.email if request.user.is_authenticated else None
|
||||||
|
)
|
||||||
|
logger.info(f"Created new checkin: {checkin}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating new checkin: {str(e)}")
|
||||||
|
continue # エラーを無視して次のレコードの処理を継続
|
||||||
|
|
||||||
|
# 更新後のデータを順序付けて取得
|
||||||
|
updated_checkins = GpsCheckin.objects.filter(
|
||||||
|
zekken_number=zekken_number,
|
||||||
|
event_code=event_code
|
||||||
|
).order_by('path_order')
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
'status': 'success',
|
||||||
|
'message': 'Checkins updated successfully',
|
||||||
|
'data': [{'id': c.id, 'path_order': c.path_order} for c in updated_checkins]
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in update_checkins: {str(e)}", exc_info=True)
|
||||||
|
return Response(
|
||||||
|
{"error": "Failed to update checkins", "detail": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
def update_checkins_old(request):
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
update_base = request.data
|
update_base = request.data
|
||||||
@ -2605,43 +2683,402 @@ def update_checkins(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@api_view(['GET'])
|
@api_view(['GET'])
|
||||||
def export_excel(request, zekken_number):
|
def export_excel(request, zekken_number, event_code):
|
||||||
|
temp_dir = None
|
||||||
|
try:
|
||||||
|
# パラメータを文字列型に変換
|
||||||
|
zekken_number = str(zekken_number)
|
||||||
|
event_code = str(event_code)
|
||||||
|
|
||||||
# 初期化
|
logger.info(f"Exporting Excel/PDF for zekken_number: {zekken_number}, event_code: {event_code}")
|
||||||
variables = {
|
|
||||||
"zekken_number":sekken_number,
|
|
||||||
"event_code":request["FC岐阜"],
|
|
||||||
"db":"rogdb",
|
|
||||||
"username":"admin",
|
|
||||||
"password":"admin123456",
|
|
||||||
"host":"localhost",
|
|
||||||
"port":"5432"
|
|
||||||
}
|
|
||||||
excel = SumasenExcel(document="test", variables=variables, docbase="./docbase")
|
|
||||||
# ./docbase/certificate.ini の定義をベースに、
|
|
||||||
# ./docbase/certificate_template.xlsxを読み込み
|
|
||||||
# ./docbase/certificate_(zekken_number).xlsxを作成する
|
|
||||||
|
|
||||||
# シート初期化
|
# 入力値の検証
|
||||||
ret = excel.make_report(variables=variables)
|
if not zekken_number or not event_code:
|
||||||
if ret["status"]==True:
|
logger.error("Missing required parameters")
|
||||||
filepath=ret["filepath"]
|
return Response(
|
||||||
logging.info(f"Excelファイル作成 : ret.filepath={filepath}")
|
{"error": "Both zekken_number and event_code are required"},
|
||||||
else:
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
message = ret.get("message", "No message provided")
|
)
|
||||||
logging.error(f"Excelファイル作成失敗 : ret.message={message}")
|
|
||||||
|
# docbaseディレクトリのパスを絶対パスで設定
|
||||||
|
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
docbase_path = os.path.join(base_dir, 'docbase')
|
||||||
|
|
||||||
|
# ディレクトリ存在確認と作成
|
||||||
|
os.makedirs(docbase_path, exist_ok=True)
|
||||||
|
|
||||||
|
# 設定ファイルのパス
|
||||||
|
template_path = os.path.join(docbase_path, 'certificate_template.xlsx')
|
||||||
|
ini_path = os.path.join(docbase_path, 'certificate.ini')
|
||||||
|
|
||||||
|
# テンプレートと設定ファイルの存在確認
|
||||||
|
if not os.path.exists(template_path):
|
||||||
|
logger.error(f"Template file not found: {template_path}")
|
||||||
|
return Response(
|
||||||
|
{"error": "Excel template file missing"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
if not os.path.exists(ini_path):
|
||||||
|
logger.error(f"INI file not found: {ini_path}")
|
||||||
|
return Response(
|
||||||
|
{"error": "Configuration file missing"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
# Docker環境用のデータベース設定を使用
|
||||||
|
db_settings = settings.DATABASES['default']
|
||||||
|
|
||||||
|
# 初期化
|
||||||
|
variables = {
|
||||||
|
"zekken_number": str(zekken_number),
|
||||||
|
"event_code": str(event_code),
|
||||||
|
"db": str(db_settings['NAME']),
|
||||||
|
"username": str(db_settings['USER']),
|
||||||
|
"password": str(db_settings['PASSWORD']),
|
||||||
|
"host": str(db_settings['HOST']),
|
||||||
|
"port": str(db_settings['PORT']),
|
||||||
|
"template_path": template_path
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
excel = SumasenExcel(document="certificate", variables=variables, docbase=docbase_path)
|
||||||
|
ret = excel.make_report(variables=variables)
|
||||||
|
|
||||||
|
if ret["status"] != True:
|
||||||
|
message = ret.get("message", "No message provided")
|
||||||
|
logger.error(f"Excelファイル作成失敗 : ret.message={message}")
|
||||||
|
return Response(
|
||||||
|
{"error": f"Excel generation failed: {message}"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
excel_path = ret.get("filepath")
|
||||||
|
if not excel_path or not os.path.exists(excel_path):
|
||||||
|
logger.error(f"Output file not found: {excel_path}")
|
||||||
|
return Response(
|
||||||
|
{"error": "Generated file not found"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
# フォーマット指定(excel or pdf)
|
||||||
|
format_type = request.query_params.get('format', 'pdf')
|
||||||
|
|
||||||
|
if format_type.lower() == 'pdf':
|
||||||
|
try:
|
||||||
|
# 一時ディレクトリを作成
|
||||||
|
temp_dir = tempfile.mkdtemp()
|
||||||
|
temp_excel = os.path.join(temp_dir, f'certificate_{zekken_number}.xlsx')
|
||||||
|
temp_pdf = os.path.join(temp_dir, f'certificate_{zekken_number}.pdf')
|
||||||
|
|
||||||
|
# Excelファイルを一時ディレクトリにコピー
|
||||||
|
shutil.copy2(excel_path, temp_excel)
|
||||||
|
|
||||||
|
# 一時ディレクトリのパーミッションを設定
|
||||||
|
os.chmod(temp_dir, 0o777)
|
||||||
|
os.chmod(temp_excel, 0o666)
|
||||||
|
|
||||||
|
logger.info(f"Converting Excel to PDF in temp directory: {temp_dir}")
|
||||||
|
|
||||||
|
# LibreOfficeを使用してExcelをPDFに変換
|
||||||
|
conversion_command = [
|
||||||
|
'soffice', # LibreOfficeの代替コマンド
|
||||||
|
'--headless',
|
||||||
|
'--convert-to',
|
||||||
|
'pdf',
|
||||||
|
'--outdir',
|
||||||
|
temp_dir,
|
||||||
|
temp_excel
|
||||||
|
]
|
||||||
|
|
||||||
|
logger.debug(f"Running conversion command: {' '.join(conversion_command)}")
|
||||||
|
|
||||||
|
# 環境変数を設定
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['HOME'] = temp_dir # LibreOfficeの設定ディレクトリを一時ディレクトリに設定
|
||||||
|
|
||||||
|
# 変換プロセスを実行
|
||||||
|
process = subprocess.run(
|
||||||
|
conversion_command,
|
||||||
|
env=env,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(f"Conversion output: {process.stdout}")
|
||||||
|
|
||||||
|
# PDFファイルの存在確認
|
||||||
|
if not os.path.exists(temp_pdf):
|
||||||
|
logger.error("PDF conversion failed - output file not found")
|
||||||
|
return Response(
|
||||||
|
{"error": "PDF conversion failed - output file not found"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
# PDFファイルを読み込んでレスポンスを返す
|
||||||
|
with open(temp_pdf, 'rb') as pdf_file:
|
||||||
|
pdf_content = pdf_file.read()
|
||||||
|
|
||||||
|
response = HttpResponse(pdf_content, content_type='application/pdf')
|
||||||
|
response['Content-Disposition'] = f'attachment; filename="certificate_{zekken_number}_{event_code}.pdf"'
|
||||||
|
return response
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.error(f"Error converting to PDF: {str(e)}\nSTDOUT: {e.stdout}\nSTDERR: {e.stderr}")
|
||||||
|
return Response(
|
||||||
|
{"error": f"PDF conversion failed: {str(e)}"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
# 一時ディレクトリの削除
|
||||||
|
if temp_dir and os.path.exists(temp_dir):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
logger.debug(f"Temporary directory removed: {temp_dir}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to remove temporary directory: {str(e)}")
|
||||||
|
|
||||||
|
else: # Excel形式の場合
|
||||||
|
with open(excel_path, 'rb') as excel_file:
|
||||||
|
response = HttpResponse(
|
||||||
|
excel_file.read(),
|
||||||
|
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
|
)
|
||||||
|
response['Content-Disposition'] = f'attachment; filename="certificate_{zekken_number}_{event_code}.xlsx"'
|
||||||
|
return response
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in Excel/PDF generation: {str(e)}", exc_info=True)
|
||||||
|
return Response(
|
||||||
|
{"error": f"File generation failed: {str(e)}"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in export_excel: {str(e)}", exc_info=True)
|
||||||
|
return Response(
|
||||||
|
{"error": "Failed to export file", "detail": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
# 確実に一時ディレクトリを削除
|
||||||
|
if temp_dir and os.path.exists(temp_dir):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to remove temporary directory in finally block: {str(e)}")
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
def export_exceli_old2(request,zekken_number, event_code):
|
||||||
|
|
||||||
|
try:
|
||||||
|
# パラメータを文字列型に変換
|
||||||
|
zekken_number = str(zekken_number)
|
||||||
|
event_code = str(event_code)
|
||||||
|
|
||||||
|
logger.info(f"Exporting Excel for zekken_number: {zekken_number}, event_code: {event_code}")
|
||||||
|
|
||||||
|
# 入力値の検証
|
||||||
|
if not zekken_number or not event_code:
|
||||||
|
logger.error("Missing required parameters")
|
||||||
|
return Response(
|
||||||
|
{"error": "Both zekken_number and event_code are required"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# docbaseディレクトリのパスを絶対パスで設定
|
||||||
|
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
docbase_path = os.path.join(base_dir, 'docbase')
|
||||||
|
|
||||||
|
# ディレクトリ存在確認と作成
|
||||||
|
os.makedirs(docbase_path, exist_ok=True)
|
||||||
|
|
||||||
|
# 設定ファイルのパス
|
||||||
|
template_path = os.path.join(docbase_path, 'certificate_template.xlsx')
|
||||||
|
ini_path = os.path.join(docbase_path, 'certificate.ini')
|
||||||
|
|
||||||
|
# テンプレートと設定ファイルの存在確認
|
||||||
|
if not os.path.exists(template_path):
|
||||||
|
logger.error(f"Template file not found: {template_path}")
|
||||||
|
return Response(
|
||||||
|
{"error": "Excel template file missing"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
if not os.path.exists(ini_path):
|
||||||
|
logger.error(f"INI file not found: {ini_path}")
|
||||||
|
return Response(
|
||||||
|
{"error": "Configuration file missing"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
# Docker環境用のデータベース設定を使用
|
||||||
|
db_settings = settings.DATABASES['default']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# レスポンスの生成
|
# 初期化
|
||||||
output.seek(0)
|
variables = {
|
||||||
response = HttpResponse(
|
"zekken_number":str(zekken_number),
|
||||||
output.read(),
|
"event_code":str(event_code),
|
||||||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
"db":str(db_settings['NAME']), #"rogdb",
|
||||||
)
|
"username":str(db_settings['USER']), #"admin",
|
||||||
response['Content-Disposition'] = f'attachment; filename=./docbase/certificate_{zekken_number}.xlsx'
|
"password":str(db_settings['PASSWORD']), #"admin123456",
|
||||||
return response
|
"host":str(db_settings['HOST']), # Docker Composeのサービス名を使用 # "localhost",
|
||||||
|
"port":str(db_settings['PORT']), #"5432",
|
||||||
|
"template_path": template_path
|
||||||
|
}
|
||||||
|
|
||||||
|
# データベース接続情報のログ出力(パスワードは除く)
|
||||||
|
logger.info(f"Attempting database connection to {variables['host']}:{variables['port']} "
|
||||||
|
f"with user {variables['username']} and database {variables['db']}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
excel = SumasenExcel(document="certificate", variables=variables, docbase=docbase_path)
|
||||||
|
# ./docbase/certificate.ini の定義をベースに、
|
||||||
|
# ./docbase/certificate_template.xlsxを読み込み
|
||||||
|
# ./docbase/certificate_(zekken_number).xlsxを作成する
|
||||||
|
|
||||||
|
# シート初期化
|
||||||
|
logger.info("Generating report with variables: %s",
|
||||||
|
{k: v for k, v in variables.items() if k != 'password'}) # パスワードを除外
|
||||||
|
|
||||||
|
ret = excel.make_report(variables=variables)
|
||||||
|
if ret["status"]==True:
|
||||||
|
filepath=ret["filepath"]
|
||||||
|
logging.info(f"Excelファイル作成 : ret.filepath={filepath}")
|
||||||
|
else:
|
||||||
|
message = ret.get("message", "No message provided")
|
||||||
|
logging.error(f"Excelファイル作成失敗 : ret.message={message}")
|
||||||
|
|
||||||
|
output_path = ret.get("filepath")
|
||||||
|
if not output_path or not os.path.exists(output_path):
|
||||||
|
logger.error(f"Output file not found: {output_path}")
|
||||||
|
return Response(
|
||||||
|
{"error": "Generated file not found"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
excel_path = output_path
|
||||||
|
|
||||||
|
# PDFのファイル名を生成
|
||||||
|
pdf_filename = f'certificate_{zekken_number}_{event_code}.pdf'
|
||||||
|
pdf_path = os.path.join(docbase_path, pdf_filename)
|
||||||
|
|
||||||
|
# フォーマット指定(excel or pdf)
|
||||||
|
format_type = request.query_params.get('format', 'pdf') # 'excel'
|
||||||
|
|
||||||
|
if format_type.lower() == 'pdf':
|
||||||
|
try:
|
||||||
|
# 一時ディレクトリを作成
|
||||||
|
temp_dir = tempfile.mkdtemp()
|
||||||
|
temp_excel = os.path.join(temp_dir, f'certificate_{zekken_number}.xlsx')
|
||||||
|
temp_pdf = os.path.join(temp_dir, f'certificate_{zekken_number}.pdf')
|
||||||
|
|
||||||
|
# Excelファイルを一時ディレクトリにコピー
|
||||||
|
shutil.copy2(excel_path, temp_excel)
|
||||||
|
|
||||||
|
# 一時ディレクトリのパーミッションを設定
|
||||||
|
os.chmod(temp_dir, 0o777)
|
||||||
|
os.chmod(temp_excel, 0o666)
|
||||||
|
|
||||||
|
logger.info(f"Converting Excel to PDF in temp directory: {temp_dir}")
|
||||||
|
|
||||||
|
|
||||||
|
# LibreOfficeを使用してExcelをPDFに変換
|
||||||
|
conversion_command = [
|
||||||
|
'soffice',
|
||||||
|
'--headless',
|
||||||
|
'--convert-to',
|
||||||
|
'pdf',
|
||||||
|
'--outdir',
|
||||||
|
temp_dir,
|
||||||
|
temp_excel
|
||||||
|
]
|
||||||
|
|
||||||
|
logger.debug(f"Running conversion command: {' '.join(conversion_command)}")
|
||||||
|
|
||||||
|
# 環境変数を設定
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['HOME'] = temp_dir # LibreOfficeの設定ディレクトリを一時ディレクトリに設定
|
||||||
|
|
||||||
|
# 変換プロセスを実行
|
||||||
|
process = subprocess.run(
|
||||||
|
conversion_command,
|
||||||
|
env=env,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(f"Conversion output: {process.stdout}")
|
||||||
|
|
||||||
|
# PDFファイルの存在確認
|
||||||
|
if not os.path.exists(temp_pdf):
|
||||||
|
logger.error("PDF conversion failed - output file not found")
|
||||||
|
return Response(
|
||||||
|
{"error": "PDF conversion failed - output file not found"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
# PDFファイルを読み込んでレスポンスを返す
|
||||||
|
with open(temp_pdf, 'rb') as pdf_file:
|
||||||
|
pdf_content = pdf_file.read()
|
||||||
|
|
||||||
|
response = HttpResponse(pdf_content, content_type='application/pdf')
|
||||||
|
response['Content-Disposition'] = f'attachment; filename="certificate_{zekken_number}_{event_code}.pdf"'
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.error(f"Error converting to PDF: {str(e)}\nSTDOUT: {e.stdout}\nSTDERR: {e.stderr}")
|
||||||
|
return Response(
|
||||||
|
{"error": "PDF conversion failed"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
# 一時ディレクトリの削除
|
||||||
|
if temp_dir and os.path.exists(temp_dir):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
logger.debug(f"Temporary directory removed: {temp_dir}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to remove temporary directory: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
else: # Excel形式の場合
|
||||||
|
with open(excel_path, 'rb') as excel_file:
|
||||||
|
response = HttpResponse(
|
||||||
|
excel_file.read(),
|
||||||
|
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
|
)
|
||||||
|
excel_filename = f'certificate_{zekken_number}_{event_code}.xlsx'
|
||||||
|
response['Content-Disposition'] = f'attachment; filename="{excel_filename}"'
|
||||||
|
return response
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in Excel/PDF generation: {str(e)}", exc_info=True)
|
||||||
|
return Response(
|
||||||
|
{"error": f"File generation failed: {str(e)}"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in export_excel: {str(e)}", exc_info=True)
|
||||||
|
return Response(
|
||||||
|
{"error": "Failed to export file", "detail": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
# 確実に一時ディレクトリを削除
|
||||||
|
if temp_dir and os.path.exists(temp_dir):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to remove temporary directory in finally block: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
# ----- for Supervisor -----
|
# ----- for Supervisor -----
|
||||||
|
|
||||||
|
|||||||
@ -1132,14 +1132,98 @@ async function saveGoalTime(goalTimeStr, zekkenNumber, eventCode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通過証明書出力・印刷機能の実装
|
||||||
// 通過証明書出力機能の実装
|
|
||||||
async function exportExcel() {
|
async function exportExcel() {
|
||||||
const zekkenNumber = document.querySelector('#zekkenNumber').value;
|
const zekkenNumber = document.querySelector('#zekkenNumber').value;
|
||||||
const eventCode = document.querySelector('#eventCode').value;
|
const eventCode = document.querySelector('#eventCode').value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/export_excel/${zekkenNumber}/${eventCode}`);
|
const response = await fetch(`${API_BASE_URL}/export_excel/${zekkenNumber}/${eventCode}/`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blobとしてレスポンスを取得
|
||||||
|
const blob = await response.blob();
|
||||||
|
|
||||||
|
// BlobをURLに変換
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
// 印刷方法の選択を提供する関数
|
||||||
|
const printPDF = () => {
|
||||||
|
// IEとその他のブラウザで異なる処理を行う
|
||||||
|
if (window.navigator.msSaveOrOpenBlob) {
|
||||||
|
// IEの場合
|
||||||
|
window.navigator.msSaveOrOpenBlob(blob, `通過証明書_${zekkenNumber}_${eventCode}.pdf`);
|
||||||
|
} else {
|
||||||
|
// その他のブラウザの場合
|
||||||
|
// iframeを作成して印刷用のコンテナとして使用
|
||||||
|
const printFrame = document.createElement('iframe');
|
||||||
|
printFrame.style.display = 'none';
|
||||||
|
printFrame.src = url;
|
||||||
|
|
||||||
|
printFrame.onload = () => {
|
||||||
|
try {
|
||||||
|
// iframe内のPDFを印刷
|
||||||
|
printFrame.contentWindow.print();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('印刷プロセス中にエラーが発生しました:', error);
|
||||||
|
// 印刷に失敗した場合、新しいタブでPDFを開く
|
||||||
|
window.open(url, '_blank');
|
||||||
|
} finally {
|
||||||
|
// 少し遅延してからクリーンアップ
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(printFrame);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.appendChild(printFrame);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 確認ダイアログを表示
|
||||||
|
const userChoice = window.confirm('PDFを印刷しますか?\n「キャンセル」を選択すると保存できます。');
|
||||||
|
|
||||||
|
if (userChoice) {
|
||||||
|
// 印刷を実行
|
||||||
|
printPDF();
|
||||||
|
} else {
|
||||||
|
// PDFを保存
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `通過証明書_${zekkenNumber}_${eventCode}.pdf`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
// クリーンアップ
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('エクスポート中にエラーが発生しました:', error);
|
||||||
|
alert('エクスポート中にエラーが発生しました');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// エラーハンドリングのためのユーティリティ関数
|
||||||
|
function handlePrintError(error) {
|
||||||
|
console.error('印刷中にエラーが発生しました:', error);
|
||||||
|
alert('印刷中にエラーが発生しました。PDFを新しいタブで開きます。');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通過証明書出力機能の実装
|
||||||
|
async function exportExcel_old() {
|
||||||
|
const zekkenNumber = document.querySelector('#zekkenNumber').value;
|
||||||
|
const eventCode = document.querySelector('#eventCode').value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/export_excel/${zekkenNumber}/${eventCode}/`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user