udistrital / polux_cliente

Cliente angular del proyecto Polux
0 stars 0 forks source link

Creación de vista para modulo de reportes - Parte 1. #718

Closed diagutierrezro closed 1 month ago

diagutierrezro commented 1 month ago

Se requiere realizar la implementación del modulo de reportes teniendo en cuenta los mockups creados en #724 la cual debe permitir generar y descargar los reportes que se requieran.

Sub Tareas

Criterios de aceptación

Requerimientos

No aplica

Definition of Ready - DoR

Definition of Done - DoD - Desarrollo

CrisEs2506 commented 1 month ago

SubTareas

  1. Se debe crear los menús desde la base de datos que corresponde al llamado de la siguiente petición:
image image
  1. Agregar los nuevos archivos HTML (template) y Javascript (controller) al archivo "app.js":
image
  1. Agregar la ubicación del nuevo script Javascript en el archivo "index.html":
image
  1. Se creo el texto "Generar Reportes", "Generar Reporte General" y "Generar Reporte de Solicitudes" en la carpeta 'decorators' en el archivo "text_translate.js", para idioma español e inglés ("Generate Reports", "Generate General Report" y "Generate Request Report").

  2. La vista "generar_reportes.html" fue creado un fragmento con CSS puro para que quedará igual al mockup, ya que las clases de Bootstrap presentaban conflicto con el centrado de los botones, quedando de la siguiente manera:

<style>
    .button-container {
        display: flex;
        justify-content: center;
        gap: 150px;
    }

    .custom-btn {
        padding: 10px 20px;
        font-size: 16px;
        cursor: pointer;
        background-color: #28a745;
        color: white;
        border: none;
        border-radius: 4px;
        width: 200px;
        text-align: center;
    }

    .custom-btn:hover {
        background-color: #218838;
    }
</style>

<div class="panel panel-default">
    <div class="panel-heading">
        {{'GENERAR_REPORTE' | translate}}
    </div>

    <div class="panel-body">
        <div class="button-container">
            <button type="button" class="custom-btn" ng-click="generarReporte.generar_reporte_general()">
                {{'GENERAR_REPORTE_GENERAL' | translate}}
            </button>
            <button type="button" class="custom-btn" ng-click="generarReporte.generar_reporte_solicitud()">
                {{'GENERAR_REPORTE_SOLICITUD' | translate}}
            </button>
        </div>
    </div>
</div>
  1. La interfaz gráfica quedó finalmente de la siguiente manera:
image image
  1. Se tuvo que modificar los Helpers y Controladores de POLUX_MID encargados de generar los reportes en formato Excel para que enviará el buffer del archivo con el llamado del endpoint:

"reporte_general.go"

package controllers

import (
    "github.com/astaxie/beego"
    "github.com/udistrital/polux_mid/helpers"
)

// Reporte_generalController operations for Reporte_general
type ReporteGeneralController struct {
    beego.Controller
}

// URLMapping ...
func (c *ReporteGeneralController) URLMapping() {
    c.Mapping("Post", c.Post)
}

// Post ...
// @Title Create
// @Description create reporte_general
// @Success 201
// @Failure 403
// @router / [post]
func (c *ReporteGeneralController) Post() {
    //Generar el archivo Excel usando el helper
    if file, err := helpers.BuildReporteGeneral(); err == nil {
        //Establecer el tipo de contenido para la descarga del archivo
        c.Ctx.Output.ContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

        //Establecer el nombre del archivo que se descargará
        c.Ctx.Output.Header("Content-Disposition", "attachment; filename=ReporteGeneral.xlsx")

        //Guardar el archivo Excel en un buffer (espacio) de memoria
        if err := file.Write(c.Ctx.ResponseWriter); err != nil {
            //Manejar errores en la escritura del archivo al Cliente
            c.Data["json"] = "Error al generar el archivo."
            c.Ctx.Output.SetStatus(500)
        } else {
            //Establecer el estado de éxito
            c.Data["json"] = "Reporte generado correctamente."
            c.Ctx.Output.SetStatus(201)
        }
    } else {
        //Manejar errores al generar el reporte
        c.Data["json"] = err.Error()
        c.Ctx.Output.SetStatus(403)
    }
    c.ServeJSON()
}
package helpers

import (
    "encoding/xml"
    "fmt"
    "io/ioutil"
    "net/http"
    "strconv"

    "github.com/astaxie/beego/logs"
    "github.com/udistrital/polux_mid/models"
    "github.com/xuri/excelize/v2"
)

func BuildReporteGeneral() (*excelize.File, error) {
    var reporteGeneral []models.ReporteGeneral

    //Se traen todos los datos de reporte general
    url := "/v1/reporte_general"
    if err := GetRequestNew("PoluxCrudUrl", url, &reporteGeneral); err != nil {
        logs.Error("Error al obtener ReporteGeneral: ", err.Error())
        return nil, err
    }

    var parametros []models.Parametro

    //Se trae los Estados, la Modalidad del Trabajo de Grado y las Areas de Conocimiento
    url = "parametro?query=TipoParametroId__in:73|76|3|4&limit=0"
    if err := GetRequestNew("UrlCrudParametros", url, &parametros); err != nil {
        logs.Error("Error al obtener Parametros: ", err.Error())
        return nil, err
    }

    //Crear un mapa de parámetros para facilitar la búsqueda
    parametroMap := make(map[int]string)
    for _, parametro := range parametros {
        parametroMap[parametro.Id] = parametro.Nombre
    }

    //Mapa para almacenar los nombres y carreras de estudiantes ya consultados
    nombresCache := make(map[string]models.DatosBasicosEstudiante)

    //Iterar sobre reporteGeneral y modificar los campos necesarios
    for i, rg := range reporteGeneral {
        if modalidadID, err := strconv.Atoi(rg.Modalidad); err == nil {
            if nombre, ok := parametroMap[modalidadID]; ok {
                reporteGeneral[i].Modalidad = nombre
            }
        }

        if estadoID, err := strconv.Atoi(rg.EstadoTrabajoGrado); err == nil {
            if nombre, ok := parametroMap[estadoID]; ok {
                reporteGeneral[i].EstadoTrabajoGrado = nombre
            }
        }

        if areaID, err := strconv.Atoi(rg.AreaConocimiento); err == nil {
            if nombre, ok := parametroMap[areaID]; ok {
                reporteGeneral[i].AreaConocimiento = nombre
            }
        }

        //Procesar IdEstudiante
        if rg.IdEstudiante != "" {
            if datos, exists := nombresCache[rg.IdEstudiante]; exists {
                //Si el nombre y carrera ya están en el cache, usarlos directamente
                reporteGeneral[i].NombreEstudiante = datos.Nombre
                reporteGeneral[i].ProgramaAcademico = datos.Carrera
            } else {
                //Si no están en el cache, obtenerlos y guardarlos
                datos, err := obtenerDatosEstudiante(rg.IdEstudiante)
                if err != nil {
                    logs.Error("Error al obtener datos del estudiante: ", err.Error())
                } else {
                    reporteGeneral[i].NombreEstudiante = datos.Nombre
                    reporteGeneral[i].ProgramaAcademico = datos.Carrera
                    nombresCache[rg.IdEstudiante] = datos // Guardar en el cache
                }
            }
        }

        //Procesar IdCoestudiante (sin modificar ProgramaAcademico)
        if rg.IdCoestudiante != "" {
            if datos, exists := nombresCache[rg.IdCoestudiante]; exists {
                //Si el nombre ya está en el cache, usarlo directamente
                reporteGeneral[i].NombreCoestudiante = datos.Nombre
            } else {
                //Si no están en el cache, obtenerlos y guardarlos
                datos, err := obtenerDatosEstudiante(rg.IdCoestudiante)
                if err != nil {
                    logs.Error("Error al obtener datos del coestudiante: ", err.Error())
                } else {
                    reporteGeneral[i].NombreCoestudiante = datos.Nombre
                    nombresCache[rg.IdCoestudiante] = datos // Guardar en el cache
                }
            }
        }
    }

    //Traer docentes
    docenteMap, err := obtenerDocentes()
    if err != nil {
        logs.Error("Error al obtener docentes: ", err.Error())
        return nil, err
    }

    //Mapa para almacenar los nombres de carreras ya consultadas
    carreraCache := make(map[string]string)

    //Hubo la necesidad de iterar nuevamente sobre reporteGeneral, ya que se necesitaba que se añadiera primero el id de la carrera a ProgramaAcademico a través del anterior for, para luego obtener el nombre de la carrera y está fue la única manera (aunque a mi parecer no tan óptima)
    for i, rg := range reporteGeneral {
        //Obtener nombre de la carrera a partir del ID almacenado en ProgramaAcademico
        if rg.ProgramaAcademico != "" {
            if nombreCarrera, exists := carreraCache[rg.ProgramaAcademico]; exists {
                //Si el nombre de la carrera ya está en el cache, usarlo directamente
                reporteGeneral[i].ProgramaAcademico = nombreCarrera
            } else {
                //Si no está en el cache, obtenerlo y guardarlo
                nombreCarrera, err := obtenerNombreCarrera(rg.ProgramaAcademico)
                if err != nil {
                    logs.Error("Error al obtener el nombre de la carrera: ", err.Error())
                } else {
                    reporteGeneral[i].ProgramaAcademico = nombreCarrera
                    carreraCache[rg.ProgramaAcademico] = nombreCarrera // Guardar en el cache
                }
            }
        }

        //Asignar nombres de docentes
        if nombre, exists := docenteMap[rg.DocenteDirector]; exists {
            reporteGeneral[i].NombreDocenteDirector = nombre
        }
        if nombre, exists := docenteMap[rg.DocenteCodirector]; exists {
            reporteGeneral[i].NombreDocenteCodirector = nombre
        }
        if nombre, exists := docenteMap[rg.Evaluador]; exists {
            reporteGeneral[i].NombreEvaluador = nombre
        }
    }

    //Título de las Columnas del Excel
    headers := map[string]string{
        "A1": "Trabajo Grado",
        "B1": "Título",
        "C1": "Modalidad",
        "D1": "Estado",
        "E1": "ID Estudiante",
        "F1": "Nombre Estudiante",
        "G1": "ID Estudiante",
        "H1": "Nombre Estudiante",
        "I1": "Programa Academico",
        "J1": "Area Conocimiento",
        "K1": "ID Docente Director",
        "L1": "Nombre Docente Director",
        "M1": "ID Docente Codirector",
        "N1": "Nombre Docente Codirector",
        "O1": "ID Evaluador",
        "P1": "Nombre Evaluador",
        "Q1": "Fecha Inicio",
        "R1": "Fecha Fin",
        "S1": "Calificación 1",
        "T1": "Calificación 2",
    }

    //Creación e Inicialización del Excel
    file := excelize.NewFile()

    //Definir el estilo para los Encabezados
    style := &excelize.Style{
        Fill: excelize.Fill{
            Type:    "pattern",
            Color:   []string{"#FFFF00"},
            Pattern: 1,
        },
        Border: []excelize.Border{
            {Type: "left", Color: "000000", Style: 1},
            {Type: "top", Color: "000000", Style: 1},
            {Type: "bottom", Color: "000000", Style: 1},
            {Type: "right", Color: "000000", Style: 1},
        },
    }

    //Precargar los estilos a la hoja de calculo
    styleID, err := file.NewStyle(style)
    if err != nil {
        fmt.Println(err)
        return nil, err
    }

    //Recorrer los headers y añadir a la hoja de cálculo del Excel
    for k, v := range headers {
        file.SetCellValue("Sheet1", k, v)
        file.SetCellStyle("Sheet1", k, k, styleID) // Aplicar el estilo de fondo amarillo
    }

    //Recorrer cada elemento del Slice de ReporteGeneral y escribir en cada fila del Excel su respectiva información
    for i := 0; i < len(reporteGeneral); i++ {
        rowCount := i + 2

        file.SetCellValue("Sheet1", fmt.Sprintf("A%v", rowCount), reporteGeneral[i].TrabajoGrado)
        file.SetCellValue("Sheet1", fmt.Sprintf("B%v", rowCount), reporteGeneral[i].Titulo)
        file.SetCellValue("Sheet1", fmt.Sprintf("C%v", rowCount), reporteGeneral[i].Modalidad)
        file.SetCellValue("Sheet1", fmt.Sprintf("D%v", rowCount), reporteGeneral[i].EstadoTrabajoGrado)
        file.SetCellValue("Sheet1", fmt.Sprintf("E%v", rowCount), reporteGeneral[i].IdEstudiante)
        file.SetCellValue("Sheet1", fmt.Sprintf("F%v", rowCount), reporteGeneral[i].NombreEstudiante)
        file.SetCellValue("Sheet1", fmt.Sprintf("G%v", rowCount), reporteGeneral[i].IdCoestudiante)
        file.SetCellValue("Sheet1", fmt.Sprintf("H%v", rowCount), reporteGeneral[i].NombreCoestudiante)
        file.SetCellValue("Sheet1", fmt.Sprintf("I%v", rowCount), reporteGeneral[i].ProgramaAcademico)
        file.SetCellValue("Sheet1", fmt.Sprintf("J%v", rowCount), reporteGeneral[i].AreaConocimiento)
        file.SetCellValue("Sheet1", fmt.Sprintf("K%v", rowCount), reporteGeneral[i].DocenteDirector)
        file.SetCellValue("Sheet1", fmt.Sprintf("L%v", rowCount), reporteGeneral[i].NombreDocenteDirector)
        file.SetCellValue("Sheet1", fmt.Sprintf("M%v", rowCount), reporteGeneral[i].DocenteCodirector)
        file.SetCellValue("Sheet1", fmt.Sprintf("N%v", rowCount), reporteGeneral[i].NombreDocenteCodirector)
        file.SetCellValue("Sheet1", fmt.Sprintf("O%v", rowCount), reporteGeneral[i].Evaluador)
        file.SetCellValue("Sheet1", fmt.Sprintf("P%v", rowCount), reporteGeneral[i].NombreEvaluador)
        file.SetCellValue("Sheet1", fmt.Sprintf("Q%v", rowCount), reporteGeneral[i].FechaInicio.Format("2006-01-02"))
        file.SetCellValue("Sheet1", fmt.Sprintf("R%v", rowCount), reporteGeneral[i].FechaFin.Format("2006-01-02"))
        file.SetCellValue("Sheet1", fmt.Sprintf("S%v", rowCount), reporteGeneral[i].CalificacionUno)
        file.SetCellValue("Sheet1", fmt.Sprintf("T%v", rowCount), reporteGeneral[i].CalificacionDos)
    }

    //Guardar el archivo Excel en este caso en la raíz del proyecto
    if err := file.SaveAs("ReporteGeneral.xlsx"); err != nil {
        fmt.Println(err)
    }

    return file, nil
}

func obtenerDatosEstudiante(idEstudiante string) (models.DatosBasicosEstudiante, error) {
    url := fmt.Sprintf("http://busservicios.intranetoas.udistrital.edu.co:8282/wso2eiserver/services/servicios_academicos/datos_basicos_estudiante/%s", idEstudiante)

    resp, err := http.Get(url)
    if err != nil {
        return models.DatosBasicosEstudiante{}, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return models.DatosBasicosEstudiante{}, fmt.Errorf("Error al realizar la solicitud: %s", resp.Status)
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return models.DatosBasicosEstudiante{}, err
    }

    var datos models.DatosEstudianteCollection
    if err := xml.Unmarshal(body, &datos); err != nil {
        return models.DatosBasicosEstudiante{}, err
    }

    if len(datos.DatosBasicosEstudiante) > 0 {
        return datos.DatosBasicosEstudiante[0], nil
    }

    return models.DatosBasicosEstudiante{}, fmt.Errorf("No se encontraron datos para el estudiante %s", idEstudiante)
}

func obtenerNombreCarrera(idCarrera string) (string, error) {
    url := fmt.Sprintf("http://busservicios.intranetoas.udistrital.edu.co:8282/wso2eiserver/services/servicios_academicos/carrera/%s", idCarrera)

    resp, err := http.Get(url)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return "", fmt.Errorf("Error al realizar la solicitud: %s", resp.Status)
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }

    var carreraCollection struct {
        Carrera struct {
            Nombre string `xml:"nombre"`
        } `xml:"carrera"`
    }

    if err := xml.Unmarshal(body, &carreraCollection); err != nil {
        return "", err
    }

    return carreraCollection.Carrera.Nombre, nil
}

func obtenerDocentes() (map[int]string, error) {
    url := "http://busservicios.intranetoas.udistrital.edu.co:8282/wso2eiserver/services/servicios_academicos/get_docentes_tg"

    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("error al realizar la solicitud: %s", resp.Status)
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    var docentes struct {
        Docentes []struct {
            ID     int    `xml:"id"`
            Nombre string `xml:"NOMBRE"`
        } `xml:"docente"`
    }

    if err := xml.Unmarshal(body, &docentes); err != nil {
        return nil, err
    }

    docenteMap := make(map[int]string)
    for _, docente := range docentes.Docentes {
        docenteMap[docente.ID] = docente.Nombre
    }

    return docenteMap, nil
}

"reporte_solicitud.go"

package controllers

import (
    "github.com/astaxie/beego"
    "github.com/udistrital/polux_mid/helpers"
)

// Reporte_generalController operations for Reporte_general
type ReporteSolicitudController struct {
    beego.Controller
}

// URLMapping ...
func (c *ReporteSolicitudController) URLMapping() {
    c.Mapping("Post", c.Post)
}

// Post ...
// @Title Create
// @Description create reporte_general
// @Success 201
// @Failure 403
// @router / [post]
func (c *ReporteSolicitudController) Post() {
    //Generar el archivo Excel usando el helper
    if file, err := helpers.BuildReporteSolicitud(); err == nil {
        //Establecer el tipo de contenido para la descarga del archivo
        c.Ctx.Output.ContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

        //Establecer el nombre del archivo que se descargará
        c.Ctx.Output.Header("Content-Disposition", "attachment; filename=ReporteSolicitud.xlsx")

        //Guardar el archivo Excel en un buffer (espacio) de memoria
        if err := file.Write(c.Ctx.ResponseWriter); err != nil {
            //Manejar errores en la escritura del archivo al Cliente
            c.Data["json"] = "Error al generar el archivo."
            c.Ctx.Output.SetStatus(500)
        } else {
            //Establecer el estado de éxito
            c.Data["json"] = "Reporte generado correctamente."
            c.Ctx.Output.SetStatus(201)
        }
    } else {
        //Manejar errores al generar el reporte
        c.Data["json"] = err.Error()
        c.Ctx.Output.SetStatus(403)
    }
    c.ServeJSON()
}
package helpers

import (
    "encoding/xml"
    "fmt"
    "io/ioutil"
    "net/http"
    "strconv"

    "github.com/astaxie/beego/logs"
    "github.com/udistrital/polux_mid/models"
    "github.com/xuri/excelize/v2"
)

func BuildReporteSolicitud() (*excelize.File, error) {
    var reporteSolicitud []models.ReporteSolicitud

    //Se traen todos los datos de reporte solicitud del CRUD
    url := "/v1/reporte_solicitud"
    if err := GetRequestNew("PoluxCrudUrl", url, &reporteSolicitud); err != nil {
        logs.Error("Error al obtener ReporteSolicitud: ", err.Error())
        return nil, err
    }

    var parametros []models.Parametro

    //Se trae los Estados, la Modalidades, los Tipo Solicitud y los Estados de Solicitud de Trabajo de Grado de Parametros
    url = "parametro?query=TipoParametroId__in:73|76|77|78&limit=0"
    if err := GetRequestNew("UrlCrudParametros", url, &parametros); err != nil {
        logs.Error("Error al obtener Parametros: ", err.Error())
        return nil, err
    }

    //Crear un mapa de parámetros para facilitar la búsqueda
    parametroMap := make(map[int]string)
    for _, parametro := range parametros {
        parametroMap[parametro.Id] = parametro.Nombre
    }

    //Mapa para almacenar los nombres y carreras de estudiantes ya consultados
    nombresCache := make(map[string]models.DatosBasicosEstudiante)

    //Iterar sobre reporteSolicitud y modificar los campos necesarios
    for i, rs := range reporteSolicitud {
        if modalidadID, err := strconv.Atoi(rs.Modalidad); err == nil {
            if nombre, ok := parametroMap[modalidadID]; ok {
                reporteSolicitud[i].Modalidad = nombre
            }
        }

        if estadoID, err := strconv.Atoi(rs.EstadoTrabajoGrado); err == nil {
            if nombre, ok := parametroMap[estadoID]; ok {
                reporteSolicitud[i].EstadoTrabajoGrado = nombre
            }
        }

        if tipoSolicitudID, err := strconv.Atoi(rs.Solicitud); err == nil {
            if nombre, ok := parametroMap[tipoSolicitudID]; ok {
                reporteSolicitud[i].Solicitud = nombre
            }
        }

        if estadoSolicitudID, err := strconv.Atoi(rs.Respuesta); err == nil {
            if nombre, ok := parametroMap[estadoSolicitudID]; ok {
                reporteSolicitud[i].Respuesta = nombre
            }
        }

        //Procesar IdEstudiante
        if rs.IdEstudiante != "" {
            if datos, exists := nombresCache[rs.IdEstudiante]; exists {
                //Si el nombre y carrera ya están en el cache, usarlos directamente
                reporteSolicitud[i].NombreEstudiante = datos.Nombre
                reporteSolicitud[i].ProgramaAcademico = datos.Carrera
            } else {
                //Si no están en el cache, obtenerlos y guardarlos
                datos, err := obtenerDatosEstudiante(rs.IdEstudiante)
                if err != nil {
                    logs.Error("Error al obtener datos del estudiante: ", err.Error())
                } else {
                    reporteSolicitud[i].NombreEstudiante = datos.Nombre
                    reporteSolicitud[i].ProgramaAcademico = datos.Carrera
                    nombresCache[rs.IdEstudiante] = datos // Guardar en el cache
                }
            }
        }

        //Procesar IdCoestudiante (sin modificar ProgramaAcademico)
        if rs.IdCoestudiante != "" {
            if datos, exists := nombresCache[rs.IdCoestudiante]; exists {
                //Si el nombre ya está en el cache, usarlo directamente
                reporteSolicitud[i].NombreCoestudiante = datos.Nombre
            } else {
                //Si no están en el cache, obtenerlos y guardarlos
                datos, err := obtenerDatosEstudiante(rs.IdCoestudiante)
                if err != nil {
                    logs.Error("Error al obtener datos del coestudiante: ", err.Error())
                } else {
                    reporteSolicitud[i].NombreCoestudiante = datos.Nombre
                    nombresCache[rs.IdCoestudiante] = datos // Guardar en el cache
                }
            }
        }
    }

    //Traer docentes
    docenteMap, err := obtenerDocentes()
    if err != nil {
        logs.Error("Error al obtener docentes: ", err.Error())
        return nil, err
    }

    //Mapa para almacenar los nombres de carreras ya consultadas
    coordinadorCache := make(map[string]string)

    //Mapa para almacenar los nombres de carreras ya consultadas
    carreraCache := make(map[string]string)

    //Hubo la necesidad de iterar nuevamente sobre reporteSolicitud, ya que se necesitaba que se añadiera primero el id de la carrera a ProgramaAcademico a través del anterior for, para luego obtener el nombre de la carrera y está fue la única manera (aunque a mi parecer no tan óptima)
    for i, rs := range reporteSolicitud {
        //Obtener nombre del coordinador a partir del ID almacenado en ProgramaAcademico
        if rs.ProgramaAcademico != "" {
            if nombreCoordinador, exists := coordinadorCache[rs.ProgramaAcademico]; exists {
                //Si el nombre de la carrera ya está en el cache, usarlo directamente
                reporteSolicitud[i].NombreCoordinador = nombreCoordinador
            } else {
                //Si no está en el cache, obtenerlo y guardarlo
                nombreCoordinador, err := obtenerNombreCoordinador(rs.ProgramaAcademico)
                if err != nil {
                    logs.Error("Error al obtener el nombre de la carrera: ", err.Error())
                } else {
                    reporteSolicitud[i].NombreCoordinador = nombreCoordinador
                    coordinadorCache[rs.ProgramaAcademico] = nombreCoordinador // Guardar en el cache
                }
            }
        }

        //Obtener nombre de la carrera a partir del ID almacenado en ProgramaAcademico
        if rs.ProgramaAcademico != "" {
            if nombreCarrera, exists := carreraCache[rs.ProgramaAcademico]; exists {
                //Si el nombre de la carrera ya está en el cache, usarlo directamente
                reporteSolicitud[i].ProgramaAcademico = nombreCarrera
            } else {
                //Si no está en el cache, obtenerlo y guardarlo
                nombreCarrera, err := obtenerNombreCarrera(rs.ProgramaAcademico)
                if err != nil {
                    logs.Error("Error al obtener el nombre de la carrera: ", err.Error())
                } else {
                    reporteSolicitud[i].ProgramaAcademico = nombreCarrera
                    carreraCache[rs.ProgramaAcademico] = nombreCarrera // Guardar en el cache
                }
            }
        }

        //Asignar nombres de docentes
        if nombre, exists := docenteMap[rs.DocenteDirector]; exists {
            reporteSolicitud[i].NombreDocenteDirector = nombre
        }
        if nombre, exists := docenteMap[rs.DocenteCodirector]; exists {
            reporteSolicitud[i].NombreDocenteCodirector = nombre
        }
        if nombre, exists := docenteMap[rs.Evaluador]; exists {
            reporteSolicitud[i].NombreEvaluador = nombre
        }
    }

    //Título de las Columnas del Excel
    headers := map[string]string{
        "A1": "ID Solicitud",
        "B1": "Trabajo Grado",
        "C1": "Título",
        "D1": "Modalidad",
        "E1": "Estado Trabajo Grado",
        "F1": "ID Estudiante",
        "G1": "Nombre Estudiante",
        "H1": "ID Estudiante",
        "I1": "Nombre Estudiante",
        "J1": "Programa Academico",
        "K1": "Nombre Coordinador",
        "L1": "ID Docente Director",
        "M1": "Nombre Docente Director",
        "N1": "ID Docente Codirector",
        "O1": "Nombre Docente Codirector",
        "P1": "ID Evaluador",
        "Q1": "Nombre Evaluador",
        "R1": "Fecha Solicitud",
        "S1": "Fecha Revision",
        "T1": "Concepto de Revision",
        "U1": "Observaciones",
        "V1": "Respuesta",
    }

    //Creación e Inicialización del Excel
    file := excelize.NewFile()

    //Definir el estilo para los Encabezados
    style := &excelize.Style{
        Fill: excelize.Fill{
            Type:    "pattern",
            Color:   []string{"#FFFF00"},
            Pattern: 1,
        },
        Border: []excelize.Border{
            {Type: "left", Color: "000000", Style: 1},
            {Type: "top", Color: "000000", Style: 1},
            {Type: "bottom", Color: "000000", Style: 1},
            {Type: "right", Color: "000000", Style: 1},
        },
    }

    //Precarsar los estilos a la hoja de calculo
    styleID, err := file.NewStyle(style)
    if err != nil {
        fmt.Println(err)
        return nil, err
    }

    //Recorrer los headers y añadir a la hoja de cálculo del Excel
    for k, v := range headers {
        file.SetCellValue("Sheet1", k, v)
        file.SetCellStyle("Sheet1", k, k, styleID) // Aplicar el estilo de fondo amarillo
    }

    //Recorrer cada elemento del Slice de ReporteSolicitud y escribir en cada fila del Excel su respectiva información
    for i := 0; i < len(reporteSolicitud); i++ {
        rowCount := i + 2

        file.SetCellValue("Sheet1", fmt.Sprintf("A%v", rowCount), reporteSolicitud[i].Id)
        file.SetCellValue("Sheet1", fmt.Sprintf("B%v", rowCount), reporteSolicitud[i].TrabajoGrado)
        file.SetCellValue("Sheet1", fmt.Sprintf("C%v", rowCount), reporteSolicitud[i].Titulo)
        file.SetCellValue("Sheet1", fmt.Sprintf("D%v", rowCount), reporteSolicitud[i].Modalidad)
        file.SetCellValue("Sheet1", fmt.Sprintf("E%v", rowCount), reporteSolicitud[i].EstadoTrabajoGrado)
        file.SetCellValue("Sheet1", fmt.Sprintf("F%v", rowCount), reporteSolicitud[i].IdEstudiante)
        file.SetCellValue("Sheet1", fmt.Sprintf("G%v", rowCount), reporteSolicitud[i].NombreEstudiante)
        file.SetCellValue("Sheet1", fmt.Sprintf("H%v", rowCount), reporteSolicitud[i].IdCoestudiante)
        file.SetCellValue("Sheet1", fmt.Sprintf("I%v", rowCount), reporteSolicitud[i].NombreCoestudiante)
        file.SetCellValue("Sheet1", fmt.Sprintf("J%v", rowCount), reporteSolicitud[i].ProgramaAcademico)
        file.SetCellValue("Sheet1", fmt.Sprintf("K%v", rowCount), reporteSolicitud[i].NombreCoordinador)
        file.SetCellValue("Sheet1", fmt.Sprintf("L%v", rowCount), reporteSolicitud[i].DocenteDirector)
        file.SetCellValue("Sheet1", fmt.Sprintf("M%v", rowCount), reporteSolicitud[i].NombreDocenteDirector)
        file.SetCellValue("Sheet1", fmt.Sprintf("N%v", rowCount), reporteSolicitud[i].DocenteCodirector)
        file.SetCellValue("Sheet1", fmt.Sprintf("O%v", rowCount), reporteSolicitud[i].NombreDocenteCodirector)
        file.SetCellValue("Sheet1", fmt.Sprintf("P%v", rowCount), reporteSolicitud[i].Evaluador)
        file.SetCellValue("Sheet1", fmt.Sprintf("Q%v", rowCount), reporteSolicitud[i].NombreEvaluador)
        file.SetCellValue("Sheet1", fmt.Sprintf("R%v", rowCount), reporteSolicitud[i].FechaSolicitud.Format("2006-01-02"))
        file.SetCellValue("Sheet1", fmt.Sprintf("S%v", rowCount), reporteSolicitud[i].FechaRevision.Format("2006-01-02"))
        file.SetCellValue("Sheet1", fmt.Sprintf("T%v", rowCount), reporteSolicitud[i].Solicitud)
        file.SetCellValue("Sheet1", fmt.Sprintf("U%v", rowCount), reporteSolicitud[i].Observacion)
        file.SetCellValue("Sheet1", fmt.Sprintf("V%v", rowCount), reporteSolicitud[i].Respuesta)
    }

    //Guardar el archivo Excel en este caso en la raíz del proyecto
    if err := file.SaveAs("ReporteSolicitud.xlsx"); err != nil {
        fmt.Println(err)
    }

    return file, nil
}

func obtenerNombreCoordinador(idCarrera string) (string, error) {
    url := fmt.Sprintf("http://busservicios.intranetoas.udistrital.edu.co:8282/wso2eiserver/services/servicios_academicos/coordinador_proyecto/%s", idCarrera)

    resp, err := http.Get(url)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return "", fmt.Errorf("Error al realizar la solicitud: %s", resp.Status)
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }

    var coordinadorCollection struct {
        Coordinador struct {
            Nombre string `xml:"nombre_coordinador"`
        } `xml:"coordinador"`
    }

    if err := xml.Unmarshal(body, &coordinadorCollection); err != nil {
        return "", err
    }

    return coordinadorCollection.Coordinador.Nombre, nil
}
  1. En el archivo "generar_reporte.js" se crearon dos funciones cada una dedicada a cada botón del HTML para que cuando se de click se reciba el archivo binario del Excel traído por el endpoint de POLUX_MID y al mismo tiempo se descargue automáticamente en la vista del cliente, quedando de la siguiente manera:
'use strict';

/**
 * @ngdoc controller
 * @name poluxClienteApp.controller:GenerarReporteCtrl
 * @description
 * # GenerarReporteCtrl
 * Controller of the poluxClienteApp
 * Controlador que permite al coordinador generar reportes generales y de solicitudes

 * @requires $scope
 * @requires $window
 * @requires decorators/poluxClienteApp.decorator:TextTranslate
 * @requires services/poluxMidService.service:poluxMidRequest
 * @requires services/poluxService.service:nuxeoClient
 */

angular.module('poluxClienteApp').controller('GenerarReporteCtrl',
    function ($scope, $window, poluxMidRequest) {
        var ctrl = this;

        /**
         * @ngdoc method
         * @name generar_reporte_general
         * @methodOf poluxClienteApp.controller:GenerarReporteCtrl
         * @description 
         * Esta función llama al endpoint de POLUX_MID correspondiente de generar y descargar el Reporte General en un archivo Excel.
         * @returns {undefined} No retorna nigún valor. 
         */
        ctrl.generar_reporte_general = function() {   
            poluxMidRequest.post("reporte_general", {}, { responseType: 'blob' }).then(function(response) {
                //Usamos el objeto Blob para crear un archivo a partir de la respuesta de la solicitud HTTP
                var blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
                var downloadUrl = URL.createObjectURL(blob);
                var a = document.createElement("a");

                //Creamos un enlace (<a>) de descarga dinámicamente, hacemos clic y luego lo eliminamos del DOM para descargar el archivo sin recargar la página
                a.href = downloadUrl;
                a.download = "ReporteGeneral.xlsx";  //Nombre del archivo
                document.body.appendChild(a);
                a.click();

                //Eliminar el enlace después de descargar
                document.body.removeChild(a);
            }).catch(function(error) {
                console.error('Error al generar el reporte general: ', error);
            });
        }

        /**
         * @ngdoc method
         * @name generar_reporte_solicitud
         * @methodOf poluxClienteApp.controller:GenerarReporteCtrl
         * @description 
         * Esta función llama al endpoint de POLUX_MID correspondiente de generar y descargar el Reporte Solicitud en un archivo Excel.
         * @returns {undefined} No retorna nigún valor. 
         */
        ctrl.generar_reporte_general = function() {   
            poluxMidRequest.post("reporte_solicitud", {}, { responseType: 'blob' }).then(function(response) {
                //Usamos el objeto Blob para crear un archivo a partir de la respuesta de la solicitud HTTP
                var blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
                var downloadUrl = URL.createObjectURL(blob);
                var a = document.createElement("a");

                //Creamos un enlace (<a>) de descarga dinámicamente, hacemos clic y luego lo eliminamos del DOM para descargar el archivo sin recargar la página
                a.href = downloadUrl;
                a.download = "ReporteSolicitud.xlsx";  //Nombre del archivo
                document.body.appendChild(a);
                a.click();

                // Eliminar el enlace después de descargar
                document.body.removeChild(a);
            }).catch(function(error) {
                console.error('Error al generar el reporte de solicitudes: ', error);
            });
        }
    }
);
CrisEs2506 commented 1 month ago

Nota: Al parecer algunos servicios que son utilizados para generar los reportes se encuentran caídos por lo que no se pudo comprobar el funcionamiento adecuado del nuevo módulo creado (se adjunta evidencia):

image
diagutierrezro commented 1 month ago

Se continuará revisión de la vista del modulo de reportes en otra issue, buen trabajo Cristian.