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.