Generate Excel dev stege final
This commit is contained in:
@ -3,3 +3,4 @@ pandas>=1.0.0
|
||||
pillow>=8.0.0
|
||||
configparser>=5.0.0
|
||||
psycopg2-binary==2.9.9
|
||||
requests
|
||||
|
||||
Binary file not shown.
@ -24,6 +24,14 @@ from .config_handler import ConfigHandler # ini file のロード
|
||||
#from .conditional import ConditionalFormatManager
|
||||
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
|
||||
|
||||
import logging
|
||||
|
||||
logging.basicConfig(
|
||||
@ -165,9 +173,18 @@ class SumasenExcel:
|
||||
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))
|
||||
self.worksheet = new_sheet
|
||||
new_sheet_name = section_config.get("sheet_name", section)
|
||||
# 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.user=variables.get('username')
|
||||
self.password=variables.get('password')
|
||||
@ -195,12 +212,17 @@ class SumasenExcel:
|
||||
# シートの幅を設定
|
||||
fit_to_width = section_config.get("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")
|
||||
new_sheet.sheet_view.orientation = orientation if orientation else "portrait"
|
||||
self.current_worksheet = new_sheet
|
||||
self.current_sheet.sheet_view.orientation = orientation if orientation else "portrait"
|
||||
|
||||
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")
|
||||
@ -253,6 +275,78 @@ class SumasenExcel:
|
||||
logging.error(f"Error in proceed_group: {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')
|
||||
|
||||
# 文字列の場合、URLかどうかをチェック
|
||||
if isinstance(field_value, str):
|
||||
# URL形式かどうかチェック
|
||||
try:
|
||||
result = urlparse(field_value)
|
||||
# URLで、かつ画像ファイルの拡張子を持つ場合
|
||||
if all([result.scheme, result.netloc]) and \
|
||||
re.search(r'\.(jpg|jpeg|png|gif|bmp)$', result.path, re.I):
|
||||
try:
|
||||
# 画像をダウンロード
|
||||
response = requests.get(field_value)
|
||||
img = Image.open(BytesIO(response.content))
|
||||
|
||||
# セルの大きさを取得(ピクセル単位)
|
||||
cell_width = cell.column_dimensions.width * 6 # 概算のピクセル変換
|
||||
cell_height = cell.row_dimensions.height
|
||||
|
||||
# アスペクト比を保持しながらリサイズ
|
||||
img_aspect = img.width / img.height
|
||||
cell_aspect = cell_width / cell_height
|
||||
|
||||
if img_aspect > cell_aspect:
|
||||
width = int(cell_width)
|
||||
height = int(width / img_aspect)
|
||||
else:
|
||||
height = int(cell_height)
|
||||
width = int(height * img_aspect)
|
||||
|
||||
# 画像をリサイズしてセルに追加
|
||||
img = img.resize((width, height))
|
||||
|
||||
# 画像をセルに追加(実装方法はExcelライブラリに依存)
|
||||
if hasattr(cell, 'add_image'): # 使用するライブラリによって適切なメソッドを使用
|
||||
cell.add_image(img)
|
||||
return "" # 画像を追加したので、テキスト値は空にする
|
||||
except Exception as e:
|
||||
logging.warning(f"画像の処理に失敗: {str(e)}")
|
||||
return field_value # エラーの場合はURLをそのまま返す
|
||||
except Exception as e:
|
||||
logging.warning(f"形式変換の処理に失敗: {str(e)}")
|
||||
return field_value # エラーの場合はURLをそのまま返す
|
||||
|
||||
# その他の場合は文字列に変換
|
||||
return str(field_value)
|
||||
|
||||
|
||||
|
||||
def proceed_one_record(self,table:str,where:str,group_range:str,variables: Dict[str, Any]):
|
||||
"""1レコードのデータを取得してシートの値を置き換える
|
||||
|
||||
@ -268,6 +362,7 @@ class SumasenExcel:
|
||||
# まずself.template_sheetの指定範囲のセルをself.current_sheetにコピーする。
|
||||
self.copy_template_to_current(group_range,group_range)
|
||||
|
||||
|
||||
print(f"step-1")
|
||||
cursor = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||
|
||||
@ -280,7 +375,7 @@ class SumasenExcel:
|
||||
print(f"record={record}")
|
||||
if record:
|
||||
# group_rangeの範囲内のセルを走査
|
||||
for row in self.current_worksheet:
|
||||
for row in self.current_sheet:
|
||||
for cell in row:
|
||||
if cell.value and isinstance(cell.value, str):
|
||||
# [field_name]形式の文字列を検索
|
||||
@ -292,12 +387,14 @@ class SumasenExcel:
|
||||
new_value = cell.value
|
||||
for field_name in matches:
|
||||
if field_name in record:
|
||||
# 新しい形式変換関数を使用
|
||||
formatted_value = self.format_cell_value(record[field_name], cell)
|
||||
new_value = new_value.replace(
|
||||
f'[{field_name}]',
|
||||
str(record[field_name])
|
||||
)
|
||||
f'[{field_name}]',
|
||||
formatted_value
|
||||
)
|
||||
cell.value = new_value
|
||||
|
||||
|
||||
cursor.close()
|
||||
return {"status": True, "message": f"Success generating group: "}
|
||||
|
||||
@ -378,7 +475,7 @@ class SumasenExcel:
|
||||
|
||||
# コピーした範囲内のセルを走査して値を置換
|
||||
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):
|
||||
# [field_name]形式の文字列を検索
|
||||
import re
|
||||
@ -389,9 +486,11 @@ class SumasenExcel:
|
||||
new_value = cell.value
|
||||
for field_name in matches:
|
||||
if field_name in record:
|
||||
# 新しい形式変換関数を使用
|
||||
formatted_value = self.format_cell_value(record[field_name], cell)
|
||||
new_value = new_value.replace(
|
||||
f'[{field_name}]',
|
||||
str(record[field_name])
|
||||
f'[{field_name}]',
|
||||
formatted_value
|
||||
)
|
||||
cell.value = new_value
|
||||
|
||||
@ -405,40 +504,224 @@ 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 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):
|
||||
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)
|
||||
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 = {target_min_col}, {target_min_row}, {target_max_col}, {target_max_row}")
|
||||
#print(f"min_col, min_row, max_col, max_row = {orig_min_col}, {orig_min_row}, {orig_max_col}, {orig_max_row}")
|
||||
|
||||
# Get template sheet name from ini file
|
||||
section_config = self.conf.get_section(self.section_list[0]) # 現在の処理中のセクション
|
||||
template_sheet_name = section_config.get("template_sheet")
|
||||
|
||||
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
|
||||
new_sheet_name = section_config.get("sheet_name",template_sheet_name)
|
||||
|
||||
# Copy row heights
|
||||
for row in range(orig_min_row, orig_max_row + 1):
|
||||
@ -449,19 +732,24 @@ class SumasenExcel:
|
||||
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].height = source_height
|
||||
#print(f"Row(target_row).height = {source_height}")
|
||||
|
||||
# Copy merged cells
|
||||
for merged_range in self.template_sheet.merged_cells:
|
||||
min_col, min_row, max_col, max_row = range_boundaries(str(merged_range))
|
||||
# Check if merge range intersects with our copy 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):
|
||||
# Calculate target merge range
|
||||
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}"
|
||||
self.current_sheet.merge_cells(target_merge_range)
|
||||
self.copy_template_range(orig_min_row, orig_min_col, orig_max_row, orig_max_col, target_min_row)
|
||||
|
||||
#for merged_range in self.template_sheet.merged_cells:
|
||||
# min_col, min_row, max_col, max_row = range_boundaries(str(merged_range))
|
||||
# #print(f"Merge cell: min_col, min_row, max_col, max_row = {min_col}, {min_row}, {max_col}, {max_row}")
|
||||
# # Check if merge range intersects with our copy 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):
|
||||
# # Calculate target merge range
|
||||
# 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}"
|
||||
# self.current_sheet.merge_cells(target_merge_range)
|
||||
# #print(f"Merge : {target_merge_range}")
|
||||
|
||||
# Copy cell contents and styles
|
||||
row_offset = target_min_row - orig_min_row
|
||||
@ -470,17 +758,18 @@ class SumasenExcel:
|
||||
source_cell = self.template_sheet.cell(row=row, column=col)
|
||||
target_cell = self.current_sheet.cell(row=row+row_offset, column=col)
|
||||
|
||||
# Copy value
|
||||
target_cell.value = source_cell.value
|
||||
|
||||
# Copy styles
|
||||
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)
|
||||
if source_cell.value:
|
||||
# Copy value
|
||||
target_cell.value = source_cell.value
|
||||
#print(f"({col},{row}) : {target_cell.value}")
|
||||
# Copy styles
|
||||
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)
|
||||
|
||||
# Copy 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.
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
|
||||
doc_file=certificate_[zekken_number].xlsx
|
||||
sections=section1
|
||||
maxcol=8
|
||||
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
|
||||
@ -13,12 +15,12 @@ orientation=portrait
|
||||
[section1.group1]
|
||||
table_name=mv_entry_details
|
||||
where=zekken_number='[zekken_number]' and event_name='[event_code]'
|
||||
group_range=A1:H11
|
||||
group_range=A1:J12
|
||||
|
||||
|
||||
[section1.group2]
|
||||
table_name=gps_checkins
|
||||
table_name=v_checkins_locations
|
||||
where=zekken_number='[zekken_number]' and event_code='[event_code]'
|
||||
sort=path_order
|
||||
group_range=A12:H12
|
||||
group_range=A13:J13
|
||||
|
||||
|
||||
Reference in New Issue
Block a user