| import gradio as gr |
| from datetime import datetime |
| from reportlab.pdfgen import canvas |
| from reportlab.lib.pagesizes import letter |
| import os |
| from openai import OpenAI |
| from dotenv import load_dotenv |
| import json |
| import base64 |
| from sendgrid import SendGridAPIClient |
| from sendgrid.helpers.mail import Mail, Attachment, FileContent, FileName, FileType, Disposition |
| from babel.numbers import format_currency |
| from reportlab.lib.styles import getSampleStyleSheet |
| from reportlab.platypus import Paragraph |
|
|
| load_dotenv(override=True) |
| client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) |
|
|
| import gradio as gr |
| from datetime import datetime |
| from reportlab.pdfgen import canvas |
| from reportlab.lib.pagesizes import letter |
| import os |
| from openai import OpenAI |
| from dotenv import load_dotenv |
| import json |
| import base64 |
| from sendgrid import SendGridAPIClient |
| from sendgrid.helpers.mail import Mail, Attachment, FileContent, FileName, FileType, Disposition |
| from babel.numbers import format_currency |
| from reportlab.lib.styles import getSampleStyleSheet |
| from reportlab.platypus import Paragraph |
|
|
| load_dotenv(override=True) |
| client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) |
|
|
| def format_inr(amount): |
| """Format amount as Indian Rupees (INR) using 'Rs.' instead of symbol""" |
| formatted = format_currency(amount, 'INR', locale='en_IN') |
| |
| return formatted.replace('₹', 'Rs.') |
|
|
| def parse_natural_language(prompt_text): |
| """Use OpenAI to parse plain text into structured product data with per-item GST""" |
| system_prompt = ( |
| "You are a helpful assistant. Extract the customer name, address (if any), gstin (if any), " |
| "and a list of products with description, code (optional), quantity, price per unit, " |
| "and GST percentage for each product from the following text. " |
| "Respond ONLY with a JSON object like this: " |
| "{" |
| "\"customer_name\": \"...\", " |
| "\"gstin\": \"...\", " |
| "\"address\": \"...\", " |
| "\"products\": [{\"description\": \"...\", \"code\": \"...\", \"qty\": ..., \"rate\": ..., \"gst_percentage\": ...}] " |
| "}" |
| ) |
|
|
| messages = [ |
| {"role": "system", "content": system_prompt}, |
| {"role": "user", "content": prompt_text} |
| ] |
|
|
| response = client.chat.completions.create( |
| model="gpt-3.5-turbo", |
| messages=messages |
| ) |
|
|
| text = response.choices[0].message.content |
| print("LLM returned:", text) |
|
|
| try: |
| parsed = json.loads(text) |
| except json.JSONDecodeError: |
| raise ValueError(f"Could not decode response: {text}") |
|
|
| return parsed |
|
|
|
|
| def generate_proforma_invoice_from_prompt(prompt_text): |
| parsed = parse_natural_language(prompt_text) |
|
|
| customer_name = parsed.get("customer_name", "Unknown") |
| address = parsed.get("address", "Not provided") |
| gstin = parsed.get("gstin", "") |
| products = parsed.get("products", []) |
|
|
| today = datetime.today().strftime("%Y%m%d") |
| safe_customer_name = customer_name.replace(" ", "_") |
| filename = f"Proforma_Invoice_{safe_customer_name}_{today}.pdf" |
|
|
| grand_total = 0 |
| product_rows = [] |
|
|
| for row in products: |
| try: |
| description = str(row["description"]) |
| code = str(row.get("code", "")) |
| qty = float(row["qty"]) |
| rate = float(row["rate"]) |
| gst_pct = float(row.get("gst_percentage", 0)) |
|
|
| row_subtotal = qty * rate |
| row_gst = (row_subtotal * gst_pct) / 100 |
| row_total_with_gst = row_subtotal + row_gst |
|
|
| grand_total += row_total_with_gst |
|
|
| product_rows.append({ |
| 'description': description, |
| 'code': code, |
| 'qty': qty, |
| 'rate': rate, |
| 'subtotal': row_subtotal, |
| 'gst_pct': gst_pct, |
| 'gst_value': row_gst, |
| 'total_with_gst': row_total_with_gst |
| }) |
|
|
| except (KeyError, ValueError) as e: |
| print(f"Error processing product row {row}: {e}") |
| continue |
|
|
| c = canvas.Canvas(filename, pagesize=letter) |
| width, height = letter |
|
|
| |
| logo_path = "Truevybeslogo.png" |
| if os.path.exists(logo_path): |
| LOGO_WIDTH = 120 |
| LOGO_HEIGHT = 40 |
| x_pos = width - 50 - LOGO_WIDTH |
| y_pos = height - 50 - LOGO_HEIGHT |
| c.drawImage( |
| logo_path, |
| x_pos, |
| y_pos, |
| width=LOGO_WIDTH, |
| height=LOGO_HEIGHT, |
| preserveAspectRatio=True |
| ) |
|
|
| c.setFont("Helvetica-Bold", 16) |
| c.drawString(50, height - 70, "PROFORMA INVOICE") |
|
|
| c.setFont("Helvetica", 10) |
| c.drawString(50, height - 100, "Consignor:") |
| c.drawString(120, height - 100, "True Vybes") |
| c.drawString(50, height - 115, "D-1/902, Nilkanth Greens Maple CHS,") |
| c.drawString(50, height - 130, "GB Road, Manpada,") |
| c.drawString(50, height - 145, "Thane - 400607") |
| c.drawString(50, height - 160, "GSTIN: 27BV0PS7767J2ZF") |
|
|
| c.drawString(50, height - 190, "Consignee:") |
| c.drawString(120, height - 190, customer_name) |
|
|
| address_lines = address.split('\n') |
| for i, line in enumerate(address_lines): |
| c.drawString(120, height - 205 - (i * 15), line) |
|
|
| c.drawString(120, height - 205 - (len(address_lines) * 15), f"GSTIN: {gstin}") |
| c.drawString(400, height - 190, f"Date: {datetime.today().strftime('%d-%b-%Y')}") |
|
|
| |
| table_top = height - 250 - (len(address_lines) * 15) |
| c.line(50, table_top, width - 50, table_top) |
| c.setFont("Helvetica-Bold", 9) |
| c.drawString(50, table_top - 15, "Description") |
| c.drawString(170, table_top - 15, "Code") |
| c.drawString(240, table_top - 15, "Qty") |
| c.drawString(280, table_top - 15, "Rate/Unit") |
| c.drawString(360, table_top - 15, "GST %") |
| c.drawString(410, table_top - 15, "GST Value") |
| c.drawString(480, table_top - 15, "Total w/ GST") |
| c.line(50, table_top - 20, width - 50, table_top - 20) |
|
|
| |
| c.setFont("Helvetica", 9) |
| row_y = table_top - 35 |
|
|
| |
| styles = getSampleStyleSheet() |
| style = styles["Normal"] |
| style.fontName = "Helvetica" |
| style.fontSize = 9 |
| style.leading = 10 |
| style.splitLongWords = True |
| style.alignment = 0 |
|
|
| for product in product_rows: |
| |
| desc_para = Paragraph(product['description'], style) |
| desc_width = 110 |
| desc_height = 20 |
| |
| |
| desc_para.wrapOn(c, desc_width, 800) |
| desc_para.drawOn(c, 50, row_y - desc_para.height + 5) |
| |
| |
| row_height = max(desc_para.height, 20) |
| |
| |
| c.drawString(170, row_y, product['code']) |
| c.drawString(240, row_y, str(product['qty'])) |
| c.drawString(280, row_y, format_inr(product['rate'])) |
| c.drawString(360, row_y, f"{product['gst_pct']}%") |
| c.drawString(410, row_y, format_inr(product['gst_value'])) |
| c.drawString(480, row_y, format_inr(product['total_with_gst'])) |
| |
| |
| row_y -= row_height + 5 |
|
|
| |
| c.line(350, row_y - 10, width - 50, row_y - 10) |
| c.setFont("Helvetica-Bold", 10) |
| c.drawString(400, row_y - 25, "GRAND TOTAL") |
| c.drawString(480, row_y - 25, format_inr(grand_total)) |
|
|
| |
| bank_y = row_y - 60 |
| c.setFont("Helvetica", 10) |
| c.drawString(50, bank_y, "Bank Details For True Vybes") |
| c.drawString(50, bank_y - 15, "A/C Name: True Vybes") |
| c.drawString(50, bank_y - 30, "A/C No.: 9747864920") |
| c.drawString(50, bank_y - 45, "A/C Type: Current") |
| c.drawString(50, bank_y - 60, "IFSC Code: KKBK0000656") |
|
|
| terms_y = bank_y - 90 |
| c.drawString(50, terms_y, "Other terms and conditions:") |
| c.drawString(50, terms_y - 15, "GST: Extra at actuals") |
| c.drawString(50, terms_y - 30, "Payment terms: 50% advance and 50% before dispatch") |
| c.drawString(50, terms_y - 45, "Lead Time: 1 to 2 days from the date of confirmed order and receipt of payment") |
| c.drawString(50, terms_y - 60, "Validity: The validity of this price is 10 days from today") |
| c.drawString(50, terms_y - 75, "Freight: Extra on To-Pay basis") |
|
|
| c.line(width - 150, terms_y - 90, width - 50, terms_y - 90) |
| c.drawString(width - 140, terms_y - 105, "Authorised Signatory") |
|
|
| c.save() |
|
|
| send_invoice_email(filename) |
|
|
| return filename |
|
|
| def send_invoice_email(pdf_filename): |
| SENDGRID_API_KEY = os.getenv("SENDGRID_API_KEY") |
| if not SENDGRID_API_KEY: |
| raise RuntimeError("SENDGRID_API_KEY is not set in your environment") |
|
|
| message = Mail( |
| from_email="truevybescg@gmail.com", |
| to_emails="truevybescg@gmail.com", |
| subject="New Proforma Invoice", |
| html_content="<p>Dear True Vybes Team,<br><br>Please find attached the new Proforma Invoice.<br><br>Regards,<br>Automated System</p>" |
| ) |
|
|
| with open(pdf_filename, "rb") as f: |
| data = f.read() |
| encoded = base64.b64encode(data).decode() |
|
|
| attachment = Attachment() |
| attachment.file_content = FileContent(encoded) |
| attachment.file_type = FileType("application/pdf") |
| attachment.file_name = FileName(os.path.basename(pdf_filename)) |
| attachment.disposition = Disposition("attachment") |
| message.attachment = attachment |
|
|
| try: |
| sg = SendGridAPIClient(SENDGRID_API_KEY) |
| response = sg.send(message) |
| print(f"Email sent. Status code: {response.status_code}") |
| except Exception as e: |
| print(f"Error sending email: {e}") |
|
|
| |
| with gr.Blocks() as demo: |
| gr.Markdown("# 📄 True Vybes NLP Proforma Invoice Generator") |
|
|
| user_prompt = gr.Textbox( |
| label="Proforma Invoice Request", |
| placeholder="E.g. Create PI for Customer, GST-18902801280128, Addresss -Andheri Mumbai, 300 candles at 20 each with 5% GST, 500 umbrella at 200 each with GST 18%", |
| lines=4 |
| ) |
|
|
| generate_btn = gr.Button("Generate Invoice") |
| output_file = gr.File(label="Download Invoice") |
|
|
| generate_btn.click( |
| fn=generate_proforma_invoice_from_prompt, |
| inputs=user_prompt, |
| outputs=output_file |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch(share=True) |