Enable to do button is so slow

I am developing a streamlit application to generate a shipping order and but the process is slow when the application tries to enable the button to generate the order and when I edit the quantity to be transferred it has to be recalculated from top to bottom. I tried to apply multithread, apply cache_data and it doesn’t work for me.

How do I ensure that when I edit the quantities I do not have to recalculate from top to bottom and that I enable the order dispatch button in a short time?

Este es el código:

# Traslado.py
# Descripción: genera las ordenes de despacho en función de la A TRASLADAR, 
# bien sea por la información de los productos más vendidos en ambos almacenes o por el % de reposición. 
import streamlit as st  # Importa la librería Streamlit para crear aplicaciones web.
from SystemResources.GestorIADashBoard.Componentes import ComponenteTraslado
from SystemResources.Utilidades import CustomLabel
import threading
import pandas as pd  # Importa pandas para el manejo de datos.

def obtener_fechas():
    global startDate, endDate
    startDate, endDate = ComponenteTraslado.obtenerFechas()

def leer_porcentajes():
    global porcentaje_df
    porcentaje_df = ComponenteTraslado.leer_porcentajes()
    porcentaje_df = {
                        "PORCENTAJE_BELLO_MONTE" if k == "Bello Monte" else "PORCENTAJE_VALENCIA" if k == "Valencia" else k: v 
                        for k, v in porcentaje_df.items()
                }
    porcentaje_df = pd.DataFrame.from_dict([porcentaje_df]).reset_index()
    porcentaje_df['PORCENTAJE_BELLO_MONTE'] = porcentaje_df['PORCENTAJE_BELLO_MONTE']/100
    porcentaje_df['PORCENTAJE_VALENCIA'] = porcentaje_df['PORCENTAJE_VALENCIA']/100

def listar_almacenes():
    global listaAlmacenes
    listaAlmacenes = ComponenteTraslado.listar_almacenes()


def main():

    def local_css(file_name):
        with open(file_name) as f:
            st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)

    local_css("style.css")
    if st.session_state.get('estado', False):
        st.experimental_rerun()
        return
    else: 
        
        from SystemResources.GestorIADashBoard.DashBoard import DashControlador as DashControlador  # Ajusta las rutas de importación si son necesarias
        from SystemResources.GestorIADashBoard import TablaDatosControlador #maneja las funciones para graficar y para configuar las tablas de visor de datos
        from SystemResources.GestorIADashBoard.ModuloTraslado import CalculadoraControlador as CalculadoraTraslado #controlador para realizar calculos de cantidades a trasladar
        


        fecha_desde,fecha_hasta = None,None
        with st.spinner('Cargando modulo de Traslado...'):	
                # Crear threads para cada tarea
                thread_fechas = threading.Thread(target=obtener_fechas)
                thread_porcentajes = threading.Thread(target=leer_porcentajes)
                thread_almacenes = threading.Thread(target=listar_almacenes)

                # Iniciar los threads
                thread_fechas.start()
                thread_porcentajes.start()
                thread_almacenes.start()

                # Esperar a que los threads terminen
                thread_fechas.join()
                thread_porcentajes.join()
                thread_almacenes.join()
                
                #startDate,endDate = ComponenteTraslado.obtenerFechas()
                #print(f'{startDate},{endDate}')
                almOrigen = ''
                almDestino = ''
                #porcentaje_df = ComponenteTraslado.leer_porcentajes()
                # Reemplaza las claves
              
                reposicion = ['Productos Vendidos', f'% de traslado']
                
                if 'filtro' not in st.session_state:
                    st.session_state['filtro'] = False
                
                if 'recalcular' not in st.session_state:
                    st.session_state['recalcular'] = False

              

        reposicion = ['Productos Vendidos', f'% de traslado']
        
        if 'filtro' not in st.session_state:
            st.session_state['filtro'] = False
        
        if 'recalcular' not in st.session_state:
            st.session_state['recalcular'] = False 

        
        #Codigo para personalizar la barra de presentacion del modulo
        st.markdown(
            """
            <style>
            .custom-bar {
                background-color: #8bdd29; /* Cambia esto por el color que prefieras */
                color: #262730;              /* Color del texto */
                padding: 0.1px;             /* Tamaño de la barra */
                font-size: 20px;           /* Tamaño de la fuente */
                font-weight: bold;        /* Hace el texto en negrilla */ 
                border-radius: 10px;        /* Bordes redondeados */
                text-align: center;        /* Centrar el texto */
                width: 100%;               /* Asegura que ocupe el ancho completo */
            }
            </style>
            <div class="custom-bar">
                Traslado de Inventario
            </div>
            """,
            unsafe_allow_html=True  
        )


        st.sidebar.header("Filtros a Aplicar:")
        with st.spinner('Cargando lista de Almacenes...'):
            listaAlmacenes= ComponenteTraslado.listar_almacenes() #carga la lista de los almacenes disponibles
        

        # Variables para manejar las selecciones dependientes
        almacen_valencia = "VALENCIA"
        almacen_bello_monte = "BELLO MONTE"
        porcentajeTraslado = 30

        # Crear columnas para los selectboxes
        col1, col2,col3 = st.columns(3)

        # Inicialmente, se definen los valores por defecto
        origen_default = "VALENCIA"
        destino_default = "BELLO MONTE"

        # Utilizamos el estado de sesión de Streamlit para manejar las selecciones
        if 'origen' not in st.session_state:
            st.session_state.origen = origen_default
        if 'destino' not in st.session_state:
            st.session_state.destino = destino_default
        if 'tipo' not in st.session_state:
            st.session_state.tipo = 'Seleccionar'
        if 'origen_select' not in st.session_state:
            st.session_state.origen_select = origen_default
        #if 'destino_select' not in st.session_state:
         #    st.session_state.destino_select = destino_default
        
        
        with col1:
            #selector de tipos de reposición
            # CSS para ajustar el espaciado entre elementos
    
            tipoReposicion = st.sidebar.selectbox('.',key='tipo_reposicion',options=reposicion,placeholder="Tipo de Reposición",label_visibility='collapsed') 
            
            #permuta los selectores en base al almacén origen o almacé destino seleccionada
        with col2:
            def update_destino():
                #print("hola mundo")
                # Lógica para actualizar el destino cuando se selecciona un nuevo origen
                selected_origin = st.session_state['origen_select']
                
                #verifica el origen seleccionado si es VALENCIA el destino es BELLO MONTE, y viceversa
                if selected_origin == "BELLO MONTE":
                    st.session_state['seleccionado'] = reposicion[1]
                    st.session_state['tipo_reposicion'] = reposicion[1]
                    st.session_state['destino_select']= "VALENCIA"
                elif selected_origin == "VALENCIA":
                    st.session_state['destino_select'] = "BELLO MONTE"
  
                st.session_state['filtro'] = True

           
            almOrigen = st.sidebar.selectbox('.', options=listaAlmacenes,key='origen_select',on_change=update_destino,index=0,placeholder="Almacen Origen",label_visibility='collapsed')


        with col3:   
            # Ajustar la lista de opciones para Almacen Destino dependiendo de la selección de Almacen Origen
            if almOrigen == almacen_valencia:
                opciones_destino = [almacen_bello_monte]
            elif almOrigen == almacen_bello_monte:
                opciones_destino = [almacen_valencia]
            else:
                opciones_destino = listaAlmacenes.copy()
                if almOrigen in opciones_destino:
                    opciones_destino.remove(almOrigen)

            def update_origen():
                # Lógica para actualizar el origen cuando se selecciona un nuevo destino
                selected_destino = st.session_state['destino_select']
                
                if selected_destino == "BELLO MONTE":
                    st.session_state['origen_select'] = "VALENCIA"
                elif selected_destino == "VALENCIA":
                    st.session_state['origen_select'] = "BELLO MONTE"
                

                st.session_state['filtro'] = True
    
            almDestino =st.sidebar.selectbox('.', options=listaAlmacenes,index=1,key='destino_select',on_change=update_origen,placeholder="Almacen Destino",label_visibility='collapsed')

            #valida el tipo de reposicion 
        if tipoReposicion=='Productos Vendidos':

            # Añade un encabezado en la barra lateral para los filtros.
            #st.subheader('Historial de Productos vendidos')
            fechaCol1, fechaCol2 = st.columns((2))  # Crea dos columnas para entrada de fechas.
            with fechaCol1:
                inicioMes = DashControlador.obtener_inicio_de_mes(endDate)
             
                fecha_desde = pd.to_datetime(st.sidebar.date_input(".", inicioMes, min_value=startDate, max_value=endDate, label_visibility='collapsed',key='fechaDesde_key'))  # Crea un selector de fecha de inicio.
                
                if 'fecha_desde' not in st.session_state:
                     st.session_state.fecha_desde = startDate

                if st.session_state.fecha_desde != fecha_desde:
                     print('entrando')
                     st.session_state['filtro'] = True
                     st.session_state['recalcular']=False
                
                st.session_state.fecha_desde = fecha_desde
                if ("FechaError" in st.session_state) and (st.session_state["FechaError"]):
                            st.error("Debe seleccionar una fecha de venta.")

            with fechaCol2:
             
                fecha_hasta = pd.to_datetime(st.sidebar.date_input(".", endDate, min_value=startDate, max_value=endDate, label_visibility='collapsed',key='fechaHasta_key'))  # Crea un selector de fecha de finalización.
                
                if 'fecha_hasta' not in st.session_state:
                     st.session_state.fecha_hasta= endDate
                
                if st.session_state.fecha_hasta != fecha_hasta:
                    st.session_state['filtro'] = True
                    st.session_state['recalcular']=False
                
                st.session_state.fecha_hasta = fecha_hasta 

                if ("FechaError" in st.session_state) and (st.session_state["FechaError"]):
                    st.error("Debe seleccionar una fecha de venta.")
            
          #  vendidos_df = ComponenteTraslado.productos_vendidos(fecha_desde,fecha_hasta,st.session_state['destino_select'])
         #Despliega el slider de % de reposicion en caso de seleccionar % de reposicion 
            
          
            #convierte a dataframe
            #reemplaza las claves
           #porcentajeTraslado = st.slider("Seleccione el porcentaje de Traslado",min_value=0,max_value=100,value=30)  
           #porcentaje_df = ComponenteTraslado.obtener_rango_traslado(porcentajeTraslado)
        
        if tipoReposicion=='Productos Vendidos':
                    print('calculando A TRASLADAR en base a productos mas vendidos')
                    _callback_arg = {}
                    _callback_proc = CalculadoraTraslado.traslado_enFuncHistVentas #callback para definir la funcion de traslado en base a los productos más vendidos
                    _callback_arg = {  #parámetros necesarios
                            "origen" : almOrigen, #nombre del almacén origen
                            "destino" : almDestino, #nombre del almacén destino
                       #     "ventas" : vendidos_df, #data frame de los productos más vendidos
                            "porcentajeTraslado": porcentajeTraslado #porcentaje de existencia actual, en caso que el almacen origen esté por debajo de ese porcentaje
                        }
        else:
                    print('calculando A TRASLADAR en base al porcentaje de reposicion')    
                    _callback_arg = {}
                    _callback_proc = CalculadoraTraslado.traslado_enFuncPorcReposicion #callback para definir la funcion de traslado en base al % de reposición
                    _callback_arg = { #parámetros necesarios
                            "origen" : almOrigen, #nombre del almacén origen
                            "destino" : almDestino, #nombre del almacén destino
                            "porcentaje_VA" : porcentaje_df["PORCENTAJE_VALENCIA"].iloc[0], #porcentaje de existencia actual aceptable en Valencia(por ejemplo 70%)
                            "porcentaje_BM" : porcentaje_df["PORCENTAJE_BELLO_MONTE"].iloc[0] #porcentaje de existencia actual aceptable en Bello Monte(por ejemplo 30%)
                        }
        #realiza la funcion de calculo de traslacion de inventario y actualiza estos resultados
        # en la columna de Cantidad_Trasladar en el visor de existencia actual de productos por depósito  
        # 

        if st.session_state['recalcular'] == False: 
           # print('obteniendo la existencia de productos') 
            existencias_df = ComponenteTraslado.existencia_actual_productos(st.session_state['destino_select'],fecha_desde,fecha_hasta)
            #print(existencias_df)
            existTabla_df = ComponenteTraslado.calcular(existencias_df,_callback_proc,_callback_arg)
            #print(existTabla_df)
            #existTabla_df = existencias_df.copy()
        else:
            existTabla_df = st.session_state['df_existTabla']

        if tipoReposicion!=st.session_state['tipo']:
             st.session_state['recalcular'] =True 
             st.session_state['tipo']=tipoReposicion
        
      
        if not existTabla_df.empty:

            existTabla_df = existTabla_df[['CATEGORIA','CODIGO PRODUCTO','NOMBRE PRODUCTO','EXISTENCIA VA','EXISTENCIA BM','EXISTENCIA TOTAL','PESO','A TRASLADAR']]
            

            #agregar columna 'PESO(Kg.)'
            
            existTabla_df['PESO(Kg.)'] = existTabla_df['PESO'] * existTabla_df['A TRASLADAR'] 
            #solo filtra en existTabla todos los que tienen A TRASLADAR mayor que 0
            existTabla_df = existTabla_df[existTabla_df['A TRASLADAR'] > 0]

            
            st.session_state['df_existTabla'] = TablaDatosControlador.crearTablaExistencia(existTabla_df,'Existencia actual productos por deposito','Greens',filtrador="codigo_producto_existente",nombre_key="existencias",nombreAlmacen=almOrigen) 
                
            with st.spinner('Activando Botón de Generación de Orden Despacho...'):	
                    TablaDatosControlador.exportarODB(st.session_state['df_existTabla'],arg={
                                    "origen":almOrigen,
                                    "destino":almDestino,
                                    "porcentaje": porcentajeTraslado/100
                            })
        else:
             st.write('No hay productos a trasladar. Ya que no hay ventas efectuadas en este periodo o que no hay productos en existencia.')
  
#ODDReporte Component
import pandas as pd
import shutil
import os
from io import BytesIO
from openpyxl import load_workbook
from openpyxl.utils.dataframe import dataframe_to_rows
from openpyxl.styles import numbers

"""
    Genera una orden de despacho en formato xls partiendo del dataframe definido.

    Parámetros:
    df (pd.DataFrame): DataFrame que contiene la información de los productos a despachar.
    deposito_origen (str): Nombre del depósito de origen.
    deposito_destino (str): Nombre del depósito de destino.
    porcentaje (float): Porcentaje relacionado con el despacho.

    Retorna:
    bytes: Contenido del archivo Excel generado con la orden de despacho.
"""

def generarOrdenDespacho(df, deposito_origen, deposito_destino, porcentaje):
    # Definir la ruta de la plantilla
    plantilla = os.getcwd() +  "/ModulosTemplates/Plantillas/TrasladoInventario/plantilla_ordenDeDespacho.xlsm"

    # Clonar la plantilla en un buffer de bytes
    with open(plantilla, 'rb') as f:
        template_content = f.read()
    output = BytesIO()
    output.write(template_content)
    output.seek(0)

    # Cargar la hoja de Excel clonada desde el buffer
    book = load_workbook(output, keep_vba=True)
    hoja = book['ORDEN DE DESPACHO']
    hoja_traslado = book['GUIA DE TRASLADO']



    # Definir los códigos de los almacenes
    codigo_origen = '02' if deposito_origen == 'BELLO MONTE' else '01'
    codigo_destino = '02' if deposito_destino == 'BELLO MONTE' else '01'

    # Escribir los nombres de los almacenes origen y destino
    hoja.cell(row=2, column=2, value=f"{codigo_origen}-{deposito_origen}")
    hoja.cell(row=2, column=3, value=f"{codigo_destino}-{deposito_destino}")
    hoja.cell(row=2, column=12, value=porcentaje)

    #escribir los nomnbres de los almacenes origen y destino en la hoja de Guia de Traslado
    hoja_traslado.cell(row=2, column=2, value=f"{codigo_origen}-{deposito_origen}")
    hoja_traslado.cell(row=2, column=3, value=f"{codigo_destino}-{deposito_destino}")
    

    # Definir las variables para controlar la posición de escritura
    start_row = 5

    # Escribir los datos del DataFrame en la hoja de Excel
    for i, row in enumerate(dataframe_to_rows(df, index=False, header=False), start_row):
        j = 1
        for value in row:
            if j == 7:
                j = 10
            if j == 13:
                j = 14
            hoja.cell(row=i, column=j, value=value)
            j += 1

        # Aplicar la fórmula Di * Ei en la columna F
        hoja.cell(row=i, column=6, value=f"=D{i}*E{i}")
          #aplica la formula de razon entre la existencia 
        if codigo_origen == '02':
            hoja.cell(row=i, column=12, value=f"=IF(K{i}>0,J{i}/K{i},0)")
        else:
            hoja.cell(row=i, column=12, value=f"=IF(J{i}>0,K{i}/J{i},0)")

        hoja[f"L{i}"].number_format="0%"
        #aplica el criterio de solicitud
        hoja.cell(row=i, column=13, value=f"=IF(L{i}<=$L$2,E{i},0)")


    
    for row in range(start_row,i+1):
        for col in range(1,3):
           hoja_traslado.cell(row=row, column=col, value=f"='ORDEN DE DESPACHO'!{chr(64+col) + str(row)}") 

        for col in range(4,7):
           hoja_traslado.cell(row=row, column=col-1, value=f"='ORDEN DE DESPACHO'!{chr(64+col) + str(row)}") 


    # Definir las fórmulas de sumatoria de cantidad y peso total
    hoja.cell(row=2, column=6, value=f"=SUM(F{start_row}:F{i})")
    hoja.cell(row=3, column=6, value=f"=SUM(E{start_row}:E{i})")
    hoja_traslado.cell(row=2, column=6, value=f"=SUM(F{start_row}:F{i})")
    hoja_traslado.cell(row=3, column=6, value=f"=SUM(E{start_row}:E{i})")
    

    # Guardar los cambios en el buffer
    final_output = BytesIO()
    book.save(final_output)
    final_output.seek(0)

    return final_output.getvalue()  # Retorna los bytes para la descarga

I will be grateful.

Hi can you provide some test input for generarOrdenDespacho function.

Hello, how can we provide you with the proof that you indicate?