|
|
import streamlit as st |
|
|
import pandas as pd |
|
|
from reportlab.lib import colors |
|
|
from reportlab.lib.pagesizes import A4 |
|
|
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer |
|
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle |
|
|
from reportlab.lib.units import cm |
|
|
from io import BytesIO |
|
|
from datetime import date |
|
|
|
|
|
|
|
|
st.set_page_config(page_title="Generador de Facturas", layout="wide") |
|
|
st.title("🧾 Generador de Facturas") |
|
|
|
|
|
|
|
|
clientes = { |
|
|
"Nuevo cliente": { |
|
|
"Nombre": "Cliente", |
|
|
"Dirección": "Calle", |
|
|
"Código Postal": "24400", |
|
|
"Ciudad": "Ponferrada", |
|
|
"Comunidad Autónoma": "Castilla y León", |
|
|
"País": "España", |
|
|
"CIF/NIF": "71111111K" |
|
|
}, |
|
|
"ECO BIERZO": { |
|
|
"Nombre": "ECO BIERZO COMPOSITE SL", |
|
|
"Dirección": "P.I. LA ROSALEDA ISAAC PRADO BODELON SN", |
|
|
"Código Postal": "24516", |
|
|
"Ciudad": "Parandones", |
|
|
"Comunidad Autónoma": "Castilla y León", |
|
|
"País": "España", |
|
|
"CIF/NIF": "A28047884" |
|
|
}, |
|
|
"AUTOTRANSPORTE TURÍSTICO": { |
|
|
"Nombre": "AUTOTRANSPORTE TURÍSTICO ESPAÑOL S.A", |
|
|
"Dirección": "Avenida Del Ensanche De Vallecas 27", |
|
|
"Código Postal": "28051", |
|
|
"Ciudad": "Madrid", |
|
|
"Comunidad Autónoma": "Comunidad de Madrid", |
|
|
"País": "España", |
|
|
"CIF/NIF": "A28047884" |
|
|
} |
|
|
} |
|
|
|
|
|
def generate_pdf(data, client_data, services): |
|
|
buffer = BytesIO() |
|
|
doc = SimpleDocTemplate(buffer, pagesize=A4, leftMargin=1.5*cm, rightMargin=1.5*cm, topMargin=1.5*cm, bottomMargin=1.5*cm) |
|
|
elements = [] |
|
|
|
|
|
styles = getSampleStyleSheet() |
|
|
styles.add(ParagraphStyle(name='CustomTitle', fontName='Helvetica-Bold', fontSize=20, textColor=colors.darkblue, spaceAfter=20)) |
|
|
styles.add(ParagraphStyle(name='CustomSubTitle', fontName='Helvetica-Bold', fontSize=12, textColor=colors.darkblue, spaceAfter=12)) |
|
|
styles.add(ParagraphStyle(name='CustomNormal', fontName='Helvetica', fontSize=10, spaceAfter=6)) |
|
|
|
|
|
|
|
|
elements.append(Paragraph("FACTURA", styles['CustomTitle'])) |
|
|
elements.append(Spacer(1, 20)) |
|
|
|
|
|
|
|
|
invoice_info_data = [ |
|
|
[Paragraph("<para leftIndent=-40>N° de factura:</para>", styles['CustomNormal']), |
|
|
Paragraph(f"<para leftIndent=-40><font color='darkblue'>{data['invoice_number']}</font></para>", styles['CustomNormal']), |
|
|
Paragraph("Fecha:", styles['CustomNormal']), |
|
|
Paragraph(f"<para leftIndent=-40><font color='darkblue'>{data['date']}</font></para>", styles['CustomNormal'])], |
|
|
[Paragraph("<para leftIndent=-40>Forma de pago:</para>", styles['CustomNormal']), |
|
|
Paragraph(f"<para leftIndent=-40><font color='darkblue'>{data['payment_method']}</font></para>", styles['CustomNormal']), "", ""] |
|
|
] |
|
|
|
|
|
invoice_info_table = Table(invoice_info_data, colWidths=[3.5*cm, 4*cm, 3.5*cm, 4*cm]) |
|
|
invoice_info_table.setStyle(TableStyle([ |
|
|
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), |
|
|
('FONTSIZE', (0, 0), (-1, -1), 10), |
|
|
('TEXTCOLOR', (0, 0), (-1, -1), colors.darkblue), |
|
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'), |
|
|
('VALIGN', (0, 0), (-1, -1), 'TOP'), |
|
|
('BOTTOMPADDING', (0, 0), (-1, -1), 6), |
|
|
('LEFTPADDING', (0, 0), (-1, -1), 2), |
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 2), |
|
|
])) |
|
|
|
|
|
elements.append(invoice_info_table) |
|
|
elements.append(Spacer(1, 20)) |
|
|
|
|
|
|
|
|
client_info = f""" |
|
|
{client_data['Nombre']}<br/> |
|
|
{client_data['Dirección']}<br/> |
|
|
{client_data['Código Postal']}, {client_data['Ciudad']}, {client_data['Comunidad Autónoma']}, {client_data['País']}<br/> |
|
|
NIF/DNI: {client_data['CIF/NIF']} |
|
|
""" |
|
|
|
|
|
company_info = """ |
|
|
LAVADO A MANO DEL BIERZO CB<br/> |
|
|
Avda Galicia 132, 3d<br/> |
|
|
24404 Ponferrada, Castilla y León, España<br/> |
|
|
CIF/NIF: E24667024 |
|
|
""" |
|
|
|
|
|
|
|
|
client_company_data = [ |
|
|
["EMPRESA", "CLIENTE"], |
|
|
[Paragraph(company_info, styles['CustomNormal']), |
|
|
Paragraph(client_info, styles['CustomNormal'])] |
|
|
] |
|
|
|
|
|
client_company_table = Table(client_company_data, colWidths=[doc.width/2, doc.width/2]) |
|
|
client_company_table.setStyle(TableStyle([ |
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), |
|
|
('FONTSIZE', (0, 0), (-1, 0), 12), |
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.darkblue), |
|
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'), |
|
|
('VALIGN', (0, 0), (-1, -1), 'TOP'), |
|
|
('TOPPADDING', (0, 0), (-1, -1), 6), |
|
|
('BOTTOMPADDING', (0, 0), (-1, -1), 6), |
|
|
])) |
|
|
elements.append(client_company_table) |
|
|
elements.append(Spacer(1, 20)) |
|
|
|
|
|
|
|
|
elements.append(Paragraph("SERVICIOS PRESTADOS", styles['CustomSubTitle'])) |
|
|
table_data = [["Descripción", "Cantidad (h)", "Precio (€)", "Importe (€)"]] |
|
|
|
|
|
|
|
|
for service in services: |
|
|
table_data.append([ |
|
|
service['description'], |
|
|
str(service['quantity']), |
|
|
f"{service['price']:.2f} €", |
|
|
f"{service['amount']:.2f} €" |
|
|
]) |
|
|
|
|
|
|
|
|
subtotal = sum(service['amount'] for service in services) |
|
|
iva = subtotal * 0.21 |
|
|
total = subtotal + iva |
|
|
|
|
|
table_data.extend([ |
|
|
["", "", Paragraph("Subtotal:", styles['CustomNormal']), Paragraph(f"{subtotal:.2f} €", styles['CustomNormal'])], |
|
|
["", "", Paragraph("IVA 21%:", styles['CustomNormal']), Paragraph(f"{iva:.2f} €", styles['CustomNormal'])], |
|
|
["", "", Paragraph("Total (EUR):", styles['CustomNormal']), Paragraph(f"{total:.2f} €", styles['CustomNormal'])] |
|
|
]) |
|
|
|
|
|
services_table = Table(table_data, colWidths=[doc.width*0.4, doc.width*0.2, doc.width*0.2, doc.width*0.2]) |
|
|
services_table.setStyle(TableStyle([ |
|
|
('BACKGROUND', (0, 0), (-1, 0), colors.darkblue), |
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white), |
|
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'), |
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), |
|
|
('FONTSIZE', (0, 0), (-1, 0), 10), |
|
|
('BOTTOMPADDING', (0, 0), (-1, 0), 8), |
|
|
('TOPPADDING', (0, 1), (-1, -1), 8), |
|
|
('BACKGROUND', (0, 1), (-1, -1), colors.white), |
|
|
('TEXTCOLOR', (0, 1), (-1, -1), colors.black), |
|
|
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'), |
|
|
('FONTSIZE', (0, 1), (-1, -1), 10), |
|
|
('GRID', (0, 0), (-1, -1), 0.5, colors.lightgrey), |
|
|
])) |
|
|
elements.append(services_table) |
|
|
|
|
|
|
|
|
elements.append(Spacer(1, 20)) |
|
|
footer = Paragraph("CIF/NIF: E24667024 | Móvil: 643 34 00 52 | E-mail: mihai_lavado@yahoo.es", styles['CustomNormal']) |
|
|
elements.append(footer) |
|
|
|
|
|
doc.build(elements) |
|
|
buffer.seek(0) |
|
|
return buffer |
|
|
|
|
|
|
|
|
if 'services' not in st.session_state: |
|
|
st.session_state.services = [{'description': 'Lavado de coche', 'quantity': 1, 'price': 9.00, 'amount': 9.00}] |
|
|
|
|
|
|
|
|
def add_service(): |
|
|
service_count = len(st.session_state.services) + 1 |
|
|
st.session_state.services.append({'description': f'Servicio adicional {service_count}', 'quantity': 1, 'price': 10.00, 'amount': 10.00}) |
|
|
|
|
|
|
|
|
def update_totals(): |
|
|
for service in st.session_state.services: |
|
|
service['amount'] = service['quantity'] * service['price'] |
|
|
|
|
|
|
|
|
if st.button("Añadir otro servicio"): |
|
|
add_service() |
|
|
|
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
invoice_number = st.text_input("N° de factura", value="1LUGO") |
|
|
invoice_date = st.date_input("Fecha", value=date.today()) |
|
|
payment_method = st.selectbox("Forma de pago", ["Transferencia", "Efectivo", "Tarjeta"]) |
|
|
|
|
|
|
|
|
selected_client = st.selectbox("Seleccione Cliente", list(clientes.keys())) |
|
|
|
|
|
|
|
|
client_data = clientes[selected_client] |
|
|
|
|
|
|
|
|
with col2: |
|
|
client_name = st.text_input("Nombre del cliente", value=client_data["Nombre"]) |
|
|
client_address = st.text_input("Dirección del cliente", value=client_data["Dirección"]) |
|
|
client_postal_code = st.text_input("Código Postal", value=client_data["Código Postal"]) |
|
|
client_city = st.text_input("Ciudad del cliente", value=client_data["Ciudad"]) |
|
|
client_community = st.text_input("Comunidad Autónoma", value=client_data["Comunidad Autónoma"]) |
|
|
client_country = st.text_input("País del cliente", value=client_data["País"]) |
|
|
client_nif = st.text_input("CIF/NIF del cliente", value=client_data["CIF/NIF"]) |
|
|
|
|
|
|
|
|
st.subheader("Servicios prestados") |
|
|
for i, service in enumerate(st.session_state.services): |
|
|
col1, col2, col3 = st.columns(3) |
|
|
with col1: |
|
|
service['description'] = st.text_input(f"Descripción {i+1}", value=service['description'], key=f"desc_{i}", on_change=update_totals) |
|
|
with col2: |
|
|
service['quantity'] = st.number_input(f"Cantidad {i+1}", value=service['quantity'], min_value=0, key=f"qty_{i}", on_change=update_totals) |
|
|
with col3: |
|
|
service['price'] = st.number_input(f"Precio (€) {i+1}", value=service['price'], min_value=0.00, step=0.01, key=f"price_{i}", on_change=update_totals) |
|
|
|
|
|
|
|
|
if st.button("Generar Factura"): |
|
|
update_totals() |
|
|
services = [service for service in st.session_state.services if service['description'] and service['quantity'] and service['price']] |
|
|
|
|
|
subtotal = sum(service['amount'] for service in services) |
|
|
iva = subtotal * 0.21 |
|
|
total = subtotal + iva |
|
|
|
|
|
st.subheader("Resumen de la factura") |
|
|
st.write(f"Subtotal: {subtotal:.2f} €") |
|
|
st.write(f"IVA (21%): {iva:.2f} €") |
|
|
st.write(f"Total: {total:.2f} €") |
|
|
|
|
|
|
|
|
pdf = generate_pdf( |
|
|
{ |
|
|
"invoice_number": invoice_number, |
|
|
"date": invoice_date.strftime("%d/%m/%Y"), |
|
|
"payment_method": payment_method |
|
|
}, |
|
|
{ |
|
|
"Nombre": client_name, |
|
|
"Dirección": client_address, |
|
|
"Código Postal": client_postal_code, |
|
|
"Ciudad": client_city, |
|
|
"Comunidad Autónoma": client_community, |
|
|
"País": client_country, |
|
|
"CIF/NIF": client_nif |
|
|
}, |
|
|
services |
|
|
) |
|
|
|
|
|
|
|
|
file_name = f"factura_{invoice_number}_{invoice_date.strftime('%d_%m_%Y')}.pdf" |
|
|
|
|
|
|
|
|
st.download_button( |
|
|
label="Descargar Factura PDF", |
|
|
data=pdf, |
|
|
file_name=file_name, |
|
|
mime="application/pdf" |
|
|
) |