CoolFace
Apppublic

fmatituy/selectospromanager

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
solicitud_materiales.py178 linesDownload Raw Back to proyectos
1from nicegui import ui
2from services.auth import require_auth, get_current_user
3from modules.layout import SelectosLayout
4from database.db import DB_PATH
5import sqlite3
6from datetime import datetime
7
8@ui.page('/proyectos/materiales/solicitud')
9@require_auth
10def solicitud_materiales_page():
11    user = get_current_user()
12    
13    # Obtener proyectos activos
14    def get_active_projects():
15        conn = sqlite3.connect(DB_PATH)
16        c = conn.cursor()
17        c.execute("SELECT id, nombre FROM proyectos WHERE estado != 'Cerrado'")
18        res = c.fetchall()
19        conn.close()
20        return {r[0]: r[1] for r in res}
21
22    proyectos = get_active_projects()
23    
24    # Listas de items para los checklists
25    ITEMS_CONSTRUCCION = ["Cemento Gris (Bulto)", "Arena de Río (m3)", "Ladrillo Limpio", "Varilla 1/2\"", "Alambre Negro", "Malla Electrosoldada"]
26    HERRAMIENTAS = ["Taladro Percutor", "Esmeril Angular", "Nivel Láser", "Pala Redonda", "Carretilla Reforzada", "Andamio Certificado"]
27    REPUESTOS = ["Disco de Corte 4.5\"", "Broca para Concreto", "Filtro de Aire Compresor", "Cable de Acero 1/4\"", "Válvula Reguladora"]
28
29    with SelectosLayout('Solicitud de Recursos'):
30        with ui.column().classes('w-full max-w-4xl mx-auto gap-8'):
31            # Header
32            with ui.row().classes('w-full justify-between items-center bg-emerald-900 p-10 rounded-[2.5rem] shadow-2xl relative overflow-hidden'):
33                ui.element('div').classes('absolute -right-10 -bottom-10 w-48 h-48 bg-emerald-800 rounded-full opacity-50 blur-2xl')
34                with ui.column().classes('gap-1'):
35                    ui.label('SUMINISTROS Y LOGÍSTICA').classes('text-[10px] font-black text-emerald-300 tracking-[0.4em]')
36                    ui.label('Solicitud de Materiales y Repuestos').classes('text-3xl font-black text-white')
37                    ui.label('Gestiona los recursos necesarios para la ejecución del proyecto.').classes('text-sm text-emerald-100/60 font-medium')
38                ui.icon('inventory_2', size='64px', color='white').classes('opacity-20')
39
40            with ui.card().classes('w-full p-8 rounded-[2rem] border-none shadow-xl bg-white'):
41                ui.label('DATOS DE LA SOLICITUD').classes('text-[10px] font-black text-slate-400 tracking-widest uppercase mb-6')
42                
43                with ui.row().classes('w-full grid grid-cols-1 md:grid-cols-2 gap-6 mb-8'):
44                    proy_sel = ui.select(proyectos, label='Proyecto Destino *').props('outlined rounded-xl').classes('w-full')
45                    prioridad = ui.select(['Baja', 'Media', 'Alta', 'CRÍTICO'], label='Prioridad', value='Media').props('outlined rounded-xl').classes('w-full')
46
47                # SECCIONES DE ITEMS
48                with ui.tabs().classes('w-full bg-slate-50 p-1 rounded-2xl mb-6 shadow-inner') as tabs:
49                    t_mat = ui.tab('MATERIALES', icon='architecture')
50                    t_her = ui.tab('HERRAMIENTAS', icon='construction')
51                    t_rep = ui.tab('REPUESTOS', icon='settings_suggest')
52
53                with ui.tab_panels(tabs, value=t_mat).classes('w-full bg-transparent'):
54                    # Pestaña Materiales
55                    with ui.tab_panel(t_mat).classes('p-0 pt-4'):
56                        ui.label('Seleccione los materiales básicos requeridos:').classes('text-xs font-bold text-slate-500 mb-4')
57                        sel_materiales = {}
58                        with ui.row().classes('w-full grid grid-cols-1 sm:grid-cols-2 gap-4'):
59                            for item in ITEMS_CONSTRUCCION:
60                                with ui.card().classes('p-4 rounded-xl border border-slate-100 flex-row items-center gap-3 bg-slate-50/50'):
61                                    sel_materiales[item] = ui.checkbox(item).props('color=emerald')
62                                    ui.number('Cant.', value=0).props('dense borderless').classes('w-16 ml-auto font-bold')
63
64                    # Pestaña Herramientas
65                    with ui.tab_panel(t_her).classes('p-0 pt-4'):
66                        ui.label('Equipos y herramientas de construcción:').classes('text-xs font-bold text-slate-500 mb-4')
67                        sel_herramientas = {}
68                        with ui.row().classes('w-full grid grid-cols-1 sm:grid-cols-2 gap-4'):
69                            for item in HERRAMIENTAS:
70                                with ui.card().classes('p-4 rounded-xl border border-slate-100 flex-row items-center gap-3 bg-slate-50/50'):
71                                    sel_herramientas[item] = ui.checkbox(item).props('color=blue')
72                                    ui.number('Cant.', value=1).props('dense borderless').classes('w-16 ml-auto font-bold')
73
74                    # Pestaña Repuestos
75                    with ui.tab_panel(t_rep).classes('p-0 pt-4'):
76                        ui.label('Repuestos y consumibles específicos:').classes('text-xs font-bold text-slate-500 mb-4')
77                        sel_repuestos = {}
78                        with ui.row().classes('w-full grid grid-cols-1 sm:grid-cols-2 gap-4'):
79                            for item in REPUESTOS:
80                                with ui.card().classes('p-4 rounded-xl border border-slate-100 flex-row items-center gap-3 bg-slate-50/50'):
81                                    sel_repuestos[item] = ui.checkbox(item).props('color=amber')
82                                    ui.number('Cant.', value=0).props('dense borderless').classes('w-16 ml-auto font-bold')
83
84                ui.label('OBSERVACIONES ADICIONALES').classes('text-[10px] font-black text-slate-400 tracking-widest uppercase mt-8 mb-4')
85                obs = ui.textarea('Detalles específicos del pedido...').props('outlined rounded-2xl bg-slate-50 border-none px-4').classes('w-full')
86
87                def enviar_solicitud():
88                    if not proy_sel.value:
89                        return ui.notify('Seleccione un proyecto para continuar', type='warning')
90                    
91                    # Consolidar JSON de items
92                    solicitados = []
93                    # Materiales
94                    for item, obj in sel_materiales.items():
95                        if obj.value: solicitados.append({"item": item, "tipo": "Material"})
96                    # Herramientas
97                    for item, obj in sel_herramientas.items():
98                        if obj.value: solicitados.append({"item": item, "tipo": "Herramienta"})
99                    # Repuestos
100                    for item, obj in sel_repuestos.items():
101                        if obj.value: solicitados.append({"item": item, "tipo": "Repuesto"})
102                    
103                    if not solicitados:
104                        return ui.notify('No ha seleccionado ningún item', type='warning')
105
106                    import json
107                    from database.queries import db_execute
108                    
109                    db_execute("""
110                        INSERT INTO material_requests (usuario_id, proyecto_id, prioridad, items_json, observaciones, fecha_solicitud)
111                        VALUES (?, ?, ?, ?, ?, ?)
112                    """, (user['id'], proy_sel.value, prioridad.value, json.dumps(solicitados), obs.value, datetime.now().strftime('%Y-%m-%d %H:%M')))
113                    
114                    ui.notify('SOLICITUD RADICADA EXITOSAMENTE', type='positive', position='top')
115                    refresh_list()
116                    # Limpiar campos
117                    obs.value = ""
118                    for obj in {**sel_materiales, **sel_herramientas, **sel_repuestos}.values():
119                        obj.value = False
120
121                with ui.row().classes('w-full justify-end mt-12'):
122                    ui.button('ENVIAR PEDIDO DE RECURSOS', icon='send', on_click=enviar_solicitud).props('unelevated rounded-2xl color=emerald-700 px-10 h-14').classes('font-black shadow-xl shadow-emerald-100')
123
124            # --- LISTADO DE SEGUIMIENTO DE SOLICITUDES ---
125            ui.label('ESTADO DE MIS SOLICITUDES RECIENTES').classes('text-xs font-black text-slate-400 tracking-[0.3em] mt-10 mb-4')
126            container = ui.column().classes('w-full gap-4')
127
128            def refresh_list():
129                container.clear()
130                from services.navigation import is_manager
131                from database.queries import db_fetchall
132                
133                query = """
134                    SELECT m.*, p.nombre as proyecto_nombre, u.nombre as solicitante
135                    FROM material_requests m
136                    JOIN proyectos p ON m.proyecto_id = p.id
137                    JOIN usuarios u ON m.usuario_id = u.id
138                """
139                params = []
140                if not is_manager(user.get('rol')):
141                    query += " WHERE m.usuario_id = ?"
142                    params.append(user['id'])
143                
144                query += " ORDER BY m.id DESC LIMIT 10"
145                
146                rows = db_fetchall(query, params)
147                with container:
148                    if not rows:
149                        ui.label('No tienes solicitudes pendientes.').classes('text-slate-400 italic')
150                    for r in rows:
151                        color_status = {
152                            'Pendiente': 'amber',
153                            'En Proceso': 'blue',
154                            'Comprado': 'emerald',
155                            'Entregado': 'indigo',
156                            'Rechazado': 'rose'
157                        }.get(r['estado'], 'slate')
158                        
159                        with ui.card().classes('w-full p-6 rounded-2xl border border-slate-100 shadow-sm bg-white hover:border-emerald-200 transition-all'):
160                            with ui.row().classes('w-full justify-between items-center'):
161                                with ui.column().classes('gap-1'):
162                                    ui.label(f"Solicitud #{r['id']} - {r['proyecto_nombre']}").classes('font-black text-slate-800')
163                                    ui.label(f"Fecha: {r['fecha_solicitud']}").classes('text-[10px] text-slate-400 font-bold uppercase tracking-widest')
164                                
165                                ui.badge(r['estado']).props(f'color={color_status}-100 text-color={color_status}-800 px-4 py-1').classes('font-bold rounded-pill')
166                            
167                            import json
168                            items = json.loads(r['items_json'])
169                            items_summary = ", ".join([i['item'] for i in items])
170                            ui.label(items_summary).classes('text-xs text-slate-600 mt-3 line-clamp-1')
171                            
172                            if r['respuesta_gestion'] if 'respuesta_gestion' in r else None:
173                                with ui.row().classes('mt-4 gap-2 items-center bg-blue-50 p-2 rounded-lg'):
174                                    ui.icon('info', color='blue-600', size='xs')
175                                    ui.label(f"Nota Gerencia: {r['respuesta_gestion']}").classes('text-[11px] text-blue-700 font-medium')
176
177            refresh_list()
178