160 lines
6.9 KiB
Python
160 lines
6.9 KiB
Python
|
|
import base64
|
||
|
|
import io
|
||
|
|
import logging
|
||
|
|
from odoo import models, fields
|
||
|
|
from PIL import Image
|
||
|
|
|
||
|
|
_logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
|
||
|
|
class SurveyXlsReport(models.AbstractModel):
|
||
|
|
_name = 'report.survey_excel_export.survey_xls'
|
||
|
|
_inherit = 'report.report_xlsx.abstract'
|
||
|
|
|
||
|
|
def generate_xlsx_report(self, workbook, data, wizard):
|
||
|
|
# 1. Determine which questions to export
|
||
|
|
all_questions = wizard.survey_id.question_ids
|
||
|
|
if wizard.type_export == 'session':
|
||
|
|
questions = all_questions.filtered(lambda q: q.page_id.is_page)
|
||
|
|
elif wizard.type_export == 'no_session':
|
||
|
|
questions = all_questions.filtered(lambda q: not q.page_id.is_page)
|
||
|
|
else:
|
||
|
|
questions = all_questions.filtered(lambda q: not q.is_page)
|
||
|
|
|
||
|
|
# 2. Define Headers
|
||
|
|
headers = ['Survey Title', 'Respondent', 'Completion Date', 'Email'] + [q.title for q in questions]
|
||
|
|
sheet = workbook.add_worksheet('Survey Answers')
|
||
|
|
bold = workbook.add_format({'bold': True})
|
||
|
|
|
||
|
|
for col, header in enumerate(headers):
|
||
|
|
sheet.write(0, col, header, bold)
|
||
|
|
|
||
|
|
# 3. Fetch Survey Inputs
|
||
|
|
domain = [('survey_id', '=', wizard.survey_id.id)]
|
||
|
|
if wizard.export_type == 'completed':
|
||
|
|
domain.append(('state', '=', 'done'))
|
||
|
|
if wizard.date_from:
|
||
|
|
domain.append(('create_date', '>=', wizard.date_from))
|
||
|
|
if wizard.date_to:
|
||
|
|
domain.append(('create_date', '<=', wizard.date_to))
|
||
|
|
|
||
|
|
surveys = self.env['survey.user_input'].search(domain)
|
||
|
|
|
||
|
|
# 4. Write Data
|
||
|
|
row = 1
|
||
|
|
if wizard.export_type == 'group_by_partner':
|
||
|
|
partners = surveys.mapped('partner_id') or [False]
|
||
|
|
for partner in partners:
|
||
|
|
partner_name = partner.name if partner else "Undefined Partner"
|
||
|
|
sheet.merge_range(row, 0, row, len(headers) - 1, partner_name, bold)
|
||
|
|
row += 1
|
||
|
|
partner_surveys = surveys.filtered(lambda s: s.partner_id == partner)
|
||
|
|
row = self._write_survey_rows(sheet, partner_surveys, row, headers, questions)
|
||
|
|
else:
|
||
|
|
row = self._write_survey_rows(sheet, surveys, row, headers, questions)
|
||
|
|
|
||
|
|
def _get_answer_value(self, answer_lines, question):
|
||
|
|
"""Retrieve answer value based on Odoo 19 question_type fields."""
|
||
|
|
if not answer_lines:
|
||
|
|
return ""
|
||
|
|
|
||
|
|
try:
|
||
|
|
q_type = question.question_type
|
||
|
|
|
||
|
|
if q_type == 'text_box':
|
||
|
|
return answer_lines.value_text_box or ""
|
||
|
|
elif q_type == 'char_box':
|
||
|
|
return answer_lines.value_char_box or ""
|
||
|
|
elif q_type == 'numerical_box':
|
||
|
|
return str(answer_lines.value_numerical_box) if answer_lines.value_numerical_box is not None else ""
|
||
|
|
elif q_type == 'scale':
|
||
|
|
return str(answer_lines.value_scale) if answer_lines.value_scale else ""
|
||
|
|
elif q_type == 'date':
|
||
|
|
return answer_lines.value_date or ""
|
||
|
|
elif q_type == 'datetime':
|
||
|
|
return answer_lines.value_datetime or ""
|
||
|
|
|
||
|
|
# ✅ FIXED: Handle simple_choice & multiple_choice properly
|
||
|
|
elif question.question_type in ['simple_choice', 'multiple_choice']:
|
||
|
|
# Join values from suggested answers
|
||
|
|
return ", ".join(answer_lines.suggested_answer_id.mapped('value')) if answer_lines.suggested_answer_id else ""
|
||
|
|
# answer_lines may contain 1 record (simple) or N records (multiple)
|
||
|
|
# .mapped() safely extracts the value from all records
|
||
|
|
selected_values = answer_lines.mapped('value_suggested.value')
|
||
|
|
return ", ".join(filter(None, selected_values))
|
||
|
|
|
||
|
|
# ✅ FIXED: Handle matrix answers properly
|
||
|
|
elif q_type == 'matrix':
|
||
|
|
parts = []
|
||
|
|
for line in answer_lines:
|
||
|
|
row_val = line.matrix_row_id.value if line.matrix_row_id else ""
|
||
|
|
col_val = line.value_suggested.value if line.value_suggested else ""
|
||
|
|
if row_val and col_val:
|
||
|
|
parts.append(f"{row_val}: {col_val}")
|
||
|
|
return ", ".join(parts)
|
||
|
|
|
||
|
|
else:
|
||
|
|
return ""
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
_logger.error(f"Error getting answer for '{question.title}': {e}")
|
||
|
|
return ""
|
||
|
|
|
||
|
|
def _write_survey_rows(self, sheet, surveys, row, headers, questions):
|
||
|
|
DEFAULT_ROW_HEIGHT = 100
|
||
|
|
DEFAULT_COLUMN_WIDTH = 20
|
||
|
|
POINTS_TO_PIXELS = 1.33
|
||
|
|
CHARACTERS_TO_PIXELS = 7
|
||
|
|
|
||
|
|
for survey_input in surveys:
|
||
|
|
# Write Metadata
|
||
|
|
sheet.write(row, 0, survey_input.survey_id.title)
|
||
|
|
sheet.write(row, 1, survey_input.partner_id.name or survey_input.display_name)
|
||
|
|
sheet.write(row, 2,
|
||
|
|
survey_input.create_date.strftime('%Y-%m-%d %H:%M:%S') if survey_input.create_date else "")
|
||
|
|
sheet.write(row, 3, survey_input.email or "")
|
||
|
|
|
||
|
|
# Write Answers
|
||
|
|
for col_offset, question in enumerate(questions, start=4):
|
||
|
|
answer_line = survey_input.user_input_line_ids.filtered(
|
||
|
|
lambda l: l.question_id == question
|
||
|
|
)
|
||
|
|
value = self._get_answer_value(answer_line, question)
|
||
|
|
|
||
|
|
# Handle custom file/image question type safely
|
||
|
|
if question.question_type == 'que_sh_file' and value:
|
||
|
|
try:
|
||
|
|
# Decode base64 image data
|
||
|
|
image_bytes = base64.b64decode(value)
|
||
|
|
image_stream = io.BytesIO(image_bytes)
|
||
|
|
img = Image.open(image_stream)
|
||
|
|
|
||
|
|
# Resize to fit cell
|
||
|
|
MAX_W, MAX_H = 150, 100
|
||
|
|
img.thumbnail((MAX_W, MAX_H), Image.Resampling.LANCZOS)
|
||
|
|
|
||
|
|
resized_stream = io.BytesIO()
|
||
|
|
img.save(resized_stream, format='PNG')
|
||
|
|
resized_stream.seek(0)
|
||
|
|
|
||
|
|
w_px, h_px = img.size
|
||
|
|
cell_w_px = DEFAULT_COLUMN_WIDTH * CHARACTERS_TO_PIXELS
|
||
|
|
cell_h_px = DEFAULT_ROW_HEIGHT * POINTS_TO_PIXELS
|
||
|
|
|
||
|
|
x_offset = max(0, (cell_w_px - w_px) / 2)
|
||
|
|
y_offset = max(0, (cell_h_px - h_px) / 2)
|
||
|
|
|
||
|
|
sheet.set_row(row, DEFAULT_ROW_HEIGHT)
|
||
|
|
sheet.set_column(col_offset, col_offset, DEFAULT_COLUMN_WIDTH)
|
||
|
|
|
||
|
|
sheet.insert_image(
|
||
|
|
row, col_offset, "temp_image.png",
|
||
|
|
{'image_data': resized_stream, 'x_offset': x_offset, 'y_offset': y_offset}
|
||
|
|
)
|
||
|
|
except Exception as e:
|
||
|
|
sheet.write(row, col_offset, f"Image Error: {str(e)}")
|
||
|
|
else:
|
||
|
|
sheet.write(row, col_offset, value or '')
|
||
|
|
|
||
|
|
row += 1
|
||
|
|
return row
|