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

Odoo电子发票认证失败:留存错误信息同时阻止单据过账

Odoo电子发票认证异常处理解决方案

核心问题

开发第三方发票认证集成时遇到两难场景:

  • 使用raise UserError弹出错误提示,会触发事务回滚,导致已关联到发票的错误信息丢失
  • 不抛出异常,错误信息能正常保存,但Odoo会继续执行发票过账流程,违背业务规则

需要同时实现:留存错误信息 + 阻断过账流程 + 弹出错误提示

解决方案

关键在于先手动提交错误记录的数据库变更,再抛出异常终止流程。这样错误信息不会因事务回滚丢失,同时异常会阻断后续过账并向用户提示错误。

修改后的代码如下:

def certificar_documento(self, url, headers, tree):
    certificar_xml = ET.tostring(tree.getroot(), encoding="UTF-8", method="xml")
    respuesta = requests.post(url, headers=headers, data=certificar_xml)
    respuesta_json = respuesta.json()

    return certificar_xml, respuesta_json

def establecer_error(self, json):
    errores = self.env["errores.fel"]
    for error in json["descripcion_errores"]:
        errores.create(
            {
                "mensaje_error": error.get("mensaje_error"),
                "fecha_hora_error": datetime.now(),
                "account_move_factuas_ids": self.id,
            }
        )
    # 手动提交错误记录,避免后续异常回滚导致信息丢失
    self.env.cr.commit()

def facturacion_electronica(self):
    url = "https://certificador.feel.com.gt/fel/procesounificado/transaccion/v2/xml"

    headers = {
        "UsuarioFirma": f"{self.company_id.usuario}",
        "LlaveFirma": f"{self.company_id.llave_firma}",
        "UsuarioApi": f"{self.company_id.usuario}",
        "LlaveApi": f"{self.company_id.llave_api}",
        "Content-Type": "application/xml",
    }

    for factura in self:
        if (
            factura.tipo_factura == "none"
            and factura.move_type != "out_refund"
            and factura.move_type != "in_invoice"
            and not factura.debit_origin_id
        ):
            raise UserError("Debe ingresar el tipo de documento que va a emitir")

        elif factura.debit_origin_id:
            tree = NDEB.generar_nota_debito(self, factura)

        elif factura.move_type == "out_refund":
            tree = NCRE.generar_nota_credito(self, factura)

        elif factura.tipo_factura == "fact" or factura.tipo_factura == "fact_cambiaria":
            tree = FACT.generar_factura(self, factura)

        elif factura.factura_especial:
            tree = FESP.generar_especial(self, factura)

        else:
            continue

        certificar_xml, respuesta_json = self.certificar_documento(url, headers, tree)
        if respuesta_json["resultado"] == False:
            mensajes_error = "\n".join(error["mensaje_error"] for error in respuesta_json["descripcion_errores"])
            # 先创建并保存错误信息
            self.establecer_error(respuesta_json)
            # 抛出异常终止过账流程并提示用户
            raise UserError(f"La factura no se certificó:\n{mensajes_error}")
        else:
            factura.numero_autorizacion = respuesta_json["uuid"]
            factura.serie = respuesta_json["serie"]
            factura.numero_dte = respuesta_json["numero"]
            factura.xml_generado = certificar_xml
            factura.fecha_emision = datetime.now()
            fecha_hora_certificado = respuesta_json["fecha"].split("T")
            hora_certificado = fecha_hora_certificado[1].split("-")
            fecha_hora_str = f"{fecha_hora_certificado[0]} {hora_certificado[0]}"
            fecha_hora_time = datetime.strptime(fecha_hora_str, "%Y-%m-%d %H:%M:%S")
            fecha_hora_modificada = fecha_hora_time + timedelta(hours=6)
            factura.fecha_certificacion = fecha_hora_modificada
            factura.xml_certificado = respuesta_json["xml_certificado"]
            factura.certificada = True

            var_numero_autorizacion = respuesta_json["uuid"]
            var_serie = respuesta_json["serie"]
            var_numero_dte = respuesta_json["numero"]
            var_fecha = date.today()

            link_factura = (
                f"https://report.feel.com.gt/ingfacereport/ingfacereport_documento?uuid={var_numero_autorizacion}"
            )

            mensaje = f"""
                <strong style='display:block;'>Datos FEL de la factura</strong>
                <ul>
                    <li>
                        Número de autorización: {var_numero_autorizacion}
                    </li>
                    <li>
                        Serie: {var_serie}
                    </li>
                    <li>
                        Número DTE: {var_numero_dte}
                    </li>
                    <li>
                        Fecha: {var_fecha}
                    </li>
                </ul>
                <a href='{link_factura}' target='_blank'>Ver factura<a/>
            """

            factura.message_post(
                body=mensaje,
                body_is_html=True,
                author_id=self.env.ref("base.partner_root").id,
                subject="Factura certificada correctamente",
                message_type="comment",
                subtype_xmlid="mail.mt_comment",
            )

修改要点

  1. 在establecer_error方法末尾添加self.env.cr.commit():手动提交错误记录的数据库变更,确保后续抛出异常时,错误信息不会被回滚删除。
  2. 恢复异常抛出逻辑:调用establecer_error保存错误后,重新抛出UserError,终止过账流程并向用户展示错误详情。

注意事项

  • 手动提交事务需谨慎,确保仅提交错误记录相关变更,避免影响其他未完成操作。
  • 若需更精细的事务控制,也可使用Odoo的savepoint机制,但直接提交错误记录是最直接的解决方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 16:44:53