You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Odoo导出XLSX数据按钮代码故障排查求助

Odoo XLSX导出按钮故障排查与修复

在Odoo系统中实现的XLSX数据导出按钮无法正常运行,以下是原始实现代码及修改后的尝试代码,需要排查并解决问题:

原始代码

# coding: utf-8
from odoo import fields, models, api, _
import time
from datetime import datetime, date, timedelta
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
import xlsxwriter
from io import BytesIO

class AccountDailyBook(models.TransientModel):
    _name = "account.daily.book"

    date_start = fields.Date("Fecha desde", required=True, default=date.today().replace(day=1).strftime('%Y-%m-%d'))
    date_end = fields.Date("Fecha hasta", required=True, default=time.strftime('%Y-%m-%d'))
    company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)

    def imprimir_pdf(self):
        for rec in self:
            format_new = "%d/%m/%Y"
            data = {
                'ids': 0,
                'form': {
                    'date_from': self.date_start,
                    'date_to': self.date_end,
                    'date_from_str': self.date_start.strftime(format_new),
                    'date_to_str': self.date_end.strftime(format_new),
                    'company': self.company_id.id
                }
            }
            return self.env.ref('l10n_ve_full.report_daily_book').report_action(self, data=data)  # , config=False
        
    def imprimir_xlsx(self):
        
        for rec in self:
            format_new = "%d/%m/%Y"
            data = {
                'ids': 0,
                'form': {
                    'date_from': self.date_start,
                    'date_to': self.date_end,
                    'date_from_str': self.date_start.strftime(format_new),
                    'date_to_str': self.date_end.strftime(format_new),
                    'company': self.company_id.id
                }
            }


            # Crear el archivo
            output = BytesIO()
            workbook = xlsxwriter.Workbook(output, {'in_memory': True})
            sheet = workbook.add_worksheet('Reporte')

        # Escribir los encabezados
            sheet.write(0, 0, 'Código')
            sheet.write(0, 1, 'Nombre')
            sheet.write(0, 2, 'Débito')
            sheet.write(0, 3, 'Crédito')

        # Escribir los datos
            row = 1
            for doc in data['docs']:
                sheet.write(row, 0, doc['code'])
                sheet.write(row, 1, doc['name'])
                sheet.write(row, 2, doc['debit'])
                sheet.write(row, 3, doc['credit'])
                row += 1

        # Escribir los totales
            sheet.write(row, 1, 'Total')
            sheet.write(row, 2, data['debit_total'])
            sheet.write(row, 3, data['credit_total'])

        # Cerrar el archivo
            workbook.close()
            output.seek(0)

            return self.env.ref('l10n_ve_full.report_daily_book').report_action(self, data=data)

        # Devolver el archivo como una respuesta HTTP
            return {
                'name': 'report_daily_book.xlsx',
                'type': 'binary',
                'data': output.read(),
                'context': self.env.context,
            }

class AccountInventoryBookReport(models.AbstractModel):
    _name = 'report.l10n_ve_full.report_daily_book_template'

    @api.model
    def _get_report_values(self, docids, data=None):

        date_start = datetime.strptime(data['form']['date_from'], DATE_FORMAT)
        date_end = datetime.strptime(data['form']['date_to'], DATE_FORMAT)
        company_id = self.env['res.company'].search([('id','=',data['form']['company'])])
        move_line_ids = self.env['account.move.line'].search([])
        datos = []
        debit_total = 0
        credit_total = 0
        self._cr.execute("select aa.code, aa.name, sum(aml.debit) as debit, sum(aml.credit) as credit "
                         "from account_move_line as aml "
                         "left join account_account as aa on aml.account_id = aa.id "
                         "where date >= '%s' and date <= '%s' "
                         "group by aa.code, aa.name "
                         "order by aa.code " % (date_start,date_end))
        for r in self.env.cr.fetchall():
            datos.append({
                'code': r[0],
                'name': r[1],
                'debit': r[2],
                'credit': r[3]
            })
            debit_total += r[2]
            credit_total += r[3]
        return {
            'company_id': company_id,
            'company': company_id,
            'currency': company_id.currency_id,
            'date_start': data['form']['date_from_str'],
            'date_end': data['form']['date_to_str'],
            'fecha_actual': datetime.now().strftime("%d/%m/%Y"),
            'hora_actual': datetime.now().strftime("%H:%M:%S"),
            'doc': move_line_ids[0],
            'docs': datos,
            'debit_total': debit_total,
            'credit_total': credit_total,
        }

修改后的尝试代码

def imprimir_xlsx(self):
    
    for rec in self:
        format_new = "%d/%m/%Y"
        data = {
            'ids': 0,
            'form': {
                'date_from': self.date_start,
                'date_to': self.date_end,
                'date_from_str': self.date_start.strftime(format_new),
                'date_to_str': self.date_end.strftime(format_new),
                'company': self.company_id.id
            }
        }


        # Crear el archivo
        output = BytesIO()
        workbook = xlsxwriter.Workbook(output, {'in_memory': True})
        sheet = workbook.add_worksheet('Reporte')

    # Escribir los encabezados
        sheet.write(0, 0, 'Código')
        sheet.write(0, 1, 'Nombre')
        sheet.write(0, 2, 'Débito')
        sheet.write(0, 3, 'Crédito')

    # Escribir los datos
        row = 1
        for doc in data['docs']:
            sheet.write(row, 0, doc['code'])
            sheet.write(row, 1, doc['name'])
            sheet.write(row, 2, doc['debit'])
            sheet.write(row, 3, doc['credit'])
            row += 1

    # Escribir los totales
        sheet.write(row, 1, 'Total')
        sheet.write(row, 2, data['debit_total'])
        sheet.write(row, 3, data['credit_total'])

    # Cerrar el archivo
        workbook.close()
        output.seek(0)

        return self.env.ref('l10n_ve_full.report_daily_book').report_action(self, data=data)

    # Devolver el archivo como una respuesta HTTP
        return {
            'name': 'report_daily_book.xlsx',
            'type': 'binary',
            'data': output.read(),
            'context': self.env.context,
        }

问题分析

  1. KeyError异常:构造的data字典仅包含form字段,没有docs、debit_total、credit_total,直接遍历data['docs']会触发键不存在的错误。
  2. 逻辑执行中断:方法中先返回了report_action,后续生成并返回XLSX文件的代码永远不会执行,属于逻辑顺序错误。
  3. 缩进错误:文件写入表头、数据的代码缩进与for rec in self循环不匹配,导致逻辑混乱。
  4. 未获取业务数据:没有调用_get_report_values方法获取实际的账户数据,空的data字典没有业务内容。

修复方案

以下是修正后的完整代码,解决上述所有问题:

# coding: utf-8
from odoo import fields, models, api, _
import time
from datetime import datetime, date, timedelta
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
import xlsxwriter
from io import BytesIO

class AccountDailyBook(models.TransientModel):
    _name = "account.daily.book"

    date_start = fields.Date("Fecha desde", required=True, default=lambda self: date.today().replace(day=1))
    date_end = fields.Date("Fecha hasta", required=True, default=lambda self: date.today())
    company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)

    def imprimir_pdf(self):
        for rec in self:
            format_new = "%d/%m/%Y"
            data = {
                'ids': 0,
                'form': {
                    'date_from': rec.date_start.strftime(DATE_FORMAT),
                    'date_to': rec.date_end.strftime(DATE_FORMAT),
                    'date_from_str': rec.date_start.strftime(format_new),
                    'date_to_str': rec.date_end.strftime(format_new),
                    'company': rec.company_id.id
                }
            }
            return self.env.ref('l10n_ve_full.report_daily_book').report_action(self, data=data)
        
    def imprimir_xlsx(self):
        for rec in self:
            format_new = "%d/%m/%Y"
            # 构造报表所需数据结构
            data = {
                'ids': 0,
                'form': {
                    'date_from': rec.date_start.strftime(DATE_FORMAT),
                    'date_to': rec.date_end.strftime(DATE_FORMAT),
                    'date_from_str': rec.date_start.strftime(format_new),
                    'date_to_str': rec.date_end.strftime(format_new),
                    'company': rec.company_id.id
                }
            }
            # 调用报表模型获取业务数据
            report_values = self.env['report.l10n_ve_full.report_daily_book_template']._get_report_values(None, data=data)
            
            # 创建XLSX文件
            output = BytesIO()
            workbook = xlsxwriter.Workbook(output, {'in_memory': True})
            sheet = workbook.add_worksheet('Reporte')

            # 写入表头
            sheet.write(0, 0, 'Código')
            sheet.write(0, 1, 'Nombre')
            sheet.write(0, 2, 'Débito')
            sheet.write(0, 3, 'Crédito')

            # 写入业务数据
            row = 1
            for doc in report_values['docs']:
                sheet.write(row, 0, doc['code'])
                sheet.write(row, 1, doc['name'])
                sheet.write(row, 2, doc['debit'])
                sheet.write(row, 3, doc['credit'])
                row += 1

            # 写入总计行
            sheet.write(row, 1, 'Total')
            sheet.write(row, 2, report_values['debit_total'])
            sheet.write(row, 3, report_values['credit_total'])

            # 关闭工作簿并重置文件指针
            workbook.close()
            output.seek(0)

            # 返回XLSX文件下载响应
            return {
                'name': f'Libro_Diario_{rec.date_start.strftime("%Y%m%d")}_{rec.date_end.strftime("%Y%m%d")}.xlsx',
                'type': 'binary',
                'data': output.getvalue(),
                'content_type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                'context': self.env.context,
            }

class AccountInventoryBookReport(models.AbstractModel):
    _name = 'report.l10n_ve_full.report_daily_book_template'

    @api.model
    def _get_report_values(self, docids, data=None):
        # 修正日期格式转换,避免字符串与日期对象混用
        date_start = datetime.strptime(data['form']['date_from'], DATE_FORMAT).date()
        date_end = datetime.strptime(data['form']['date_to'], DATE_FORMAT).date()
        company_id = self.env['res.company'].browse(data['form']['company'])
        datos = []
        debit_total = 0.0
        credit_total = 0.0
        
        # 使用参数化查询避免SQL注入风险
        self._cr.execute("""
            select aa.code, aa.name, sum(aml.debit) as debit, sum(aml.credit) as credit 
            from account_move_line as aml 
            left join account_account as aa on aml.account_id = aa.id 
            where aml.date >= %s and aml.date <= %s and aml.company_id = %s
            group by aa.code, aa.name 
            order by aa.code 
        """, (date_start, date_end, company_id.id))
        
        for r in self.env.cr.fetchall():
            datos.append({
                'code': r[0],
                'name': r[1],
                'debit': r[2] or 0.0,
                'credit': r[3] or 0.0
            })
            debit_total += r[2] or 0.0
            credit_total += r[3] or 0.0
            
        return {
            'company_id': company_id,
            'company': company_id,
            'currency': company_id.currency_id,
            'date_start': data['form']['date_from_str'],
            'date_end': data['form']['date_to_str'],
            'fecha_actual': datetime.now().strftime("%d/%m/%Y"),
            'hora_actual': datetime.now().strftime("%H:%M:%S"),
            'docs': datos,
            'debit_total': debit_total,
            'credit_total': credit_total,
        }

主要修改点说明

  • 获取业务数据:调用报表模型的_get_report_values方法获取实际的账户汇总数据,避免手动构造空数据。
  • 修正逻辑顺序:移除错误的report_action返回,确保XLSX文件生成后直接返回下载响应。
  • 修复缩进问题:所有文件操作代码都包含在for rec in self循环内,保证单条记录处理逻辑正确。
  • 日期格式优化:默认字段值直接返回日期对象,避免提前转换为字符串;查询时使用参数化SQL,避免注入风险。
  • 空值处理:对查询返回的debit和credit做空值判断,避免计算时出现异常。

内容的提问来源于stack exchange,提问作者SirDomini x

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 14:27:02