A Rectangle must have a non-negative height

67 views Asked by At

I am trying to generate an invoice from a firebase payload in a gcp function using borb. The error I am getting is that the rectangle must have non-negative height.

The gcp function is:

from borb.pdf.canvas.layout.page_layout.multi_column_layout import SingleColumnLayout
from borb.pdf.canvas.layout.page_layout.page_layout import PageLayout
from borb.pdf.canvas.layout.text.paragraph import Paragraph
from borb.pdf.document.document import Document
from borb.pdf.page.page import Page
from borb.pdf.pdf import PDF
from borb.pdf.canvas.layout.page_layout.multi_column_layout import SingleColumnLayout
from decimal import Decimal
from borb.pdf.canvas.layout.table.fixed_column_width_table import (
    FixedColumnWidthTable as Table,
)
from borb.pdf.canvas.layout.layout_element import Alignment
from borb.pdf.canvas.layout.image.image import Image
from datetime import datetime
import random
from borb.pdf.pdf import PDF
from borb.pdf.canvas.color.color import HexColor, X11Color
from borb.pdf.canvas.layout.table.fixed_column_width_table import (
    FixedColumnWidthTable as Table,
)
from borb.pdf.canvas.layout.table.table import TableCell
from borb.pdf import Page
import json
from google.cloud import storage
from datetime import datetime
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
import logging

# Pendiente de enviar email al cliente final, guardar dentro de invoices capreta por mes en cloud storage y luego emitiar factura excel contasol y de poner en el nombre el id del comprador y de acbar de poner tablas de precios etc.

# Global Variables
export_time = datetime.now().strftime("%d_%m_%Y")
invoice_date_month = datetime.now().strftime("%m_%Y")
# Use the application default credentials.
cred = credentials.ApplicationDefault()

firebase_admin.initialize_app(cred)
db = firestore.client()

logging.basicConfig(format='%(message)s')

# Function
def generate_pdf_invoice(data, context):
    """ Triggered by a change to a Firestore document.
    Args:
        data (dict): The event payload.
        context (google.cloud.functions.Context): Metadata for the event.
    """

    logging.warning(data)
    logging.warning(context)
    # Increase the padding for the cells to ensure data fits comfortably
    padding_value = Decimal(5)  # Increase as needed
    
    # Build Table
    table_001 = Table(number_of_rows=5, number_of_columns=4)

    table_001.add(
        Paragraph(
            "Raó Social", font="Helvetica-Bold", horizontal_alignment=Alignment.LEFT
        )
    )
    table_001.add(
        Paragraph(
            data["value"]["fields"]["seller_details"]["mapValue"]["fields"][
                "seller_name"
            ]["stringValue"]
        )
    )

    table_001.add(
        Paragraph("Data", font="Helvetica-Bold", horizontal_alignment=Alignment.RIGHT)
    )
    now = datetime.now()
    table_001.add(Paragraph("%d/%d/%d" % (now.day, now.month, now.year)))

    table_001.add(
        Paragraph("Carrer", font="Helvetica-Bold", horizontal_alignment=Alignment.LEFT)
    )
    table_001.add(
        Paragraph(
            data["value"]["fields"]["seller_details"]["mapValue"]["fields"]["seller_address_street"]["stringValue"]
        )
    )
    table_001.add(
        Paragraph(
            "Factura #", font="Helvetica-Bold", horizontal_alignment=Alignment.RIGHT,
        )
    )
    table_001.add(
        Paragraph(str(data["value"]["fields"]["invoice_number"]["integerValue"]))
    )

    table_001.add(
        Paragraph(
            "Població", font="Helvetica-Bold", horizontal_alignment=Alignment.LEFT
        )
    )

    table_001.add(
        Paragraph(
            data["value"]["fields"]["seller_details"]["mapValue"]["fields"]["seller_address_city_postal_code_country"]["stringValue"]
        )
    )

    table_001.add(
        Paragraph(
            "Telèfon", font="Helvetica-Bold", horizontal_alignment=Alignment.RIGHT
        )
    )

    table_001.add(
        Paragraph(
            data["value"]["fields"]["seller_details"]["mapValue"]["fields"][
                "seller_phone"
            ]["stringValue"]
        )
    )
    table_001.add(
        Paragraph(
            "Venciment", font="Helvetica-Bold", horizontal_alignment=Alignment.LEFT
        )
    )
    table_001.add(Paragraph("%d/%d/%d" % (now.day+4, now.month, now.year)))

    table_001.add(
        Paragraph("Email", font="Helvetica-Bold", horizontal_alignment=Alignment.RIGHT)
    )
    table_001.add(
        Paragraph(
            data["value"]["fields"]["seller_details"]["mapValue"]["fields"][
                "seller_email"
            ]["stringValue"]
        )
    )

    table_001.add(
        Paragraph(
            "BIC", font="Helvetica-Bold", horizontal_alignment=Alignment.LEFT
        )
    )
    table_001.add(Paragraph(data["value"]["fields"]["seller_details"]["mapValue"]["fields"]["seller_BIC"]["stringValue"]))

    small_font_size = 8  # Change as per your requirement

    table_001.add(
        Paragraph(
            "IBAN", font="Helvetica-Bold", horizontal_alignment=Alignment.RIGHT
        )
    )
    table_001.add(Paragraph(data["value"]["fields"]["seller_details"]["mapValue"]["fields"]["seller_IBAN"]["stringValue"], font_size=small_font_size))

    table_001.set_padding_on_all_cells(padding_value, padding_value, padding_value, padding_value)
    table_001.no_borders()

    #Billing and shipping information
    table_002 = Table(number_of_rows=6, number_of_columns=1)
    table_002.add(
        Paragraph(
            "Facturat a",
            background_color=HexColor("263238"),
            font_color=X11Color("White"),
        )
    )
    # BILLING
    table_002.add(
        Paragraph(data["value"]["fields"]["buyer_details"]["mapValue"]["fields"]["buyer_name"]["stringValue"])
    )  # BILLING
    table_002.add(
        Paragraph(data["value"]["fields"]["buyer_details"]["mapValue"]["fields"]["buyer_address_street"]["stringValue"])
    )  # BILLING
    table_002.add(
        Paragraph(
            data["value"]["fields"]["buyer_details"]["mapValue"]["fields"]["buyer_address_city_postal_code_country"]["stringValue"]
        )
    )  # BILLING
    table_002.add(
        Paragraph(data["value"]["fields"]["buyer_details"]["mapValue"]["fields"]["buyer_phone"]["stringValue"])
    )  # BILLING
    table_002.add(
        Paragraph(data["value"]["fields"]["buyer_details"]["mapValue"]["fields"]["buyer_email"]["stringValue"])
    )  # BILLING
    table_002.set_padding_on_all_cells(padding_value, padding_value, padding_value, padding_value)
    table_002.no_borders()

    # Build_itemized_description_table
    table_003 = Table(number_of_rows=7, number_of_columns=4)
    for h in ["Descripció", "Quantitat", "Preu per unitat", "Import"]:
        table_003.add(
            TableCell(
                Paragraph(h, font_color=X11Color("White")),
                background_color=HexColor("000000"),
            )
        )

    odd_color = HexColor("BBBBBB")
    even_color = HexColor("FFFFFF")
    for row_number, item in enumerate(
        [
            (
                data["value"]["fields"]["product"]["mapValue"]["fields"]["description"]["stringValue"],
                data["value"]["fields"]["product"]["mapValue"]["fields"]["quantity"]["doubleValue"],
                data["value"]["fields"]["product"]["mapValue"]["fields"]["amount"]["doubleValue"],
                list(data["value"]["fields"]["product"]["mapValue"]["fields"]["amount"].values())[0]*data["value"]["fields"]["product"]["mapValue"]["fields"]["quantity"]["doubleValue"]
            ),
        ]
    ):
        c = even_color if row_number % 2 == 0 else odd_color
        table_003.add(TableCell(Paragraph(item[0]), background_color=c))
        table_003.add(TableCell(Paragraph(str(item[1])), background_color=c))
        table_003.add(TableCell(Paragraph("€ " + str(item[2])), background_color=c))
        table_003.add(TableCell(Paragraph("€ " + str(item[3])), background_color=c))

    # Optionally add some empty rows to have a fixed number of rows for styling purposes
    for row_number in range(3, 5):
        c = even_color if row_number % 2 == 0 else odd_color
        for _ in range(0, 4):
            table_003.add(TableCell(Paragraph(" "), background_color=c))

    table_003.add(
        TableCell(
            Paragraph(
                "Subtotal", font="Helvetica-Bold", horizontal_alignment=Alignment.RIGHT,
            ),
            col_span=3,
        )
    )
    table_003.add(
        TableCell(
            Paragraph(
                str(data["value"]["fields"]["product"]["mapValue"]["fields"]["amount"]["doubleValue"]),
                horizontal_alignment=Alignment.RIGHT,
            )
        )
    )
    table_003.add(
        TableCell(
            Paragraph(
                "IGI", font="Helvetica-Bold", horizontal_alignment=Alignment.RIGHT
            ),
            col_span=3,
        )
    )
    table_003.add(
        TableCell(
            Paragraph(
                "{0:.2f}".format(
                    data["value"]["fields"]["product"]["mapValue"]["fields"]["amount"]["doubleValue"]* (4.5 / 100)
                ),
                horizontal_alignment=Alignment.RIGHT,
            )
        )
    )
    table_003.add(
        TableCell(
            Paragraph(
                "Total", font="Helvetica-Bold", horizontal_alignment=Alignment.RIGHT
            ),
            col_span=3,
        )
    )
    table_003.add(
        TableCell(
            Paragraph(
                "{0:.2f}".format(
                    data["value"]["fields"]["product"]["mapValue"]["fields"]["amount"]["doubleValue"]* (1 + 4.5 / 100)
                ),
                horizontal_alignment=Alignment.RIGHT,
            )
        )
    )
    table_003.set_padding_on_all_cells(padding_value, padding_value, padding_value, padding_value)
    table_003.no_borders()


    # Create document
    pdf = Document()
    # Add page
    page = Page()
    pdf.add_page(page)

    page_layout = SingleColumnLayout(page)
    logging.warning("Page height:")
    logging.warning(page.get_page_info().get_height())
    logging.warning("Table")
    logging.warning(table_001)
    page_layout.vertical_margin = page.get_page_info().get_height() * Decimal(0.02)

    page_layout.add(
        Image(
            "https://firebasestorage.googleapis.com/v0/b/my-app.appspot.com/o/stores%2F2322e310-1743-11ed-87c2-9f7ebec4c7dc?alt=media&token=71ebb1be-a9ae-4f49-b3e1-b46619cdfb4a",
            width=Decimal(128),
            height=Decimal(128),
        )
    )

    # Add more logging for debugging
    logging.warning("Adding Table 001...")
    logging.warning(table_001)
    page_layout.add(table_001)
    logging.warning("Table 001 added successfully.")

    # Empty paragraph for spacing
    page_layout.add(Paragraph(" "))

    logging.warning("Adding Table 002...")
    logging.warning(table_002)
    page_layout.add(table_002)
    logging.warning("Table 002 added successfully.")

    logging.warning("Adding Table 003...")
    logging.warning(table_003)
    page_layout.add(table_003)
    logging.warning("Table 003 added successfully.")

    #Getting the invoice ID
    # Initialize Firestore client
    db = firestore.Client()

    # Get reference to a Firestore document
    doc_ref = db.collection('my_collection').document('my_document')

    # Get the ID of the document
    doc_id = doc_ref.id    

    # Uploading to google cloud
    storage_client = storage.Client()
    bucket = storage_client.bucket("my-app-prod.appspot.com")
    invoice_id = doc_id
    file_name = f"My_App_pagaments_Restaurant_Invoice_{export_time}_{invoice_id}.pdf"
    blob = bucket.blob(f"Invoices/{invoice_date_month}/{file_name}")
    
    with open("/tmp/output.pdf", "wb") as pdf_file_handle:
        PDF.dumps(pdf_file_handle, pdf)

    blob.upload_from_filename("/tmp/output.pdf")

    # Making the blob public
    blob.make_public()
    public_url = blob.public_url

    #Sending the email
    
    # Create an initial document to update
    seller_email = data["value"]["fields"]["seller_details"]["mapValue"]["fields"]["seller_email"]["stringValue"]
    seller_name = data["value"]["fields"]["seller_details"]["mapValue"]["fields"]["seller_name"]["stringValue"]
    mail_ref = db.collection(u'mail').document()
    mail_ref.set({
        u'to': [seller_email, '[email protected]'],
        u'template': {
            u'name': 'invoice_store_ca',
            u'data': {
                u'name': seller_name,
                u'invoiceNumber': data["value"]["fields"]["invoice_number"]["integerValue"],
                u'invoiceName': file_name,
                u'invoiceUrl': [public_url],
            },
        
        }})

    print(
        f"Blob {blob.name} is publicly accessible at {blob.public_url}"
    )
    
    
    # Updating the pendingAmount in the store id
    store_id = data["value"]["fields"]["seller_details"]["mapValue"]["fields"]["seller_id"]["stringValue"]
    decrease_amount = float(data["value"]["fields"]["product"]["mapValue"]["fields"]["amount"]["doubleValue"])

    store = db.collection('stores').document(store_id)
    logging.warning(store_id)
    logging.warning(store)  
    increaseBy = firestore.FieldValue.increment(-decrease_amount);
    store.update({"pendingNetAmount": increaseBy}) 

And the error I get is:


restaurants_invoices57tg2dhgll0j Traceback (most recent call last): File "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", line 2529, in wsgi_app response = self.full_dispatch_request() File "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", line 1825, in full_dispatch_request rv = self.handle_user_exception(e) File "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", line 1823, in full_dispatch_request rv = self.dispatch_request() File "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", line 1799, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) File "/layers/google.python.pip/pip/lib/python3.9/site-packages/functions_framework/__init__.py", line 171, in view_func function(data, context) File "/workspace/main.py", line 302, in generate_pdf_invoice page_layout.add(table_001) File "/layers/google.python.pip/pip/lib/python3.9/site-packages/borb/pdf/canvas/layout/page_layout/multi_column_layout.py", line 201, in add layout_rect = layout_element.layout( File "/layers/google.python.pip/pip/lib/python3.9/site-packages/borb/pdf/canvas/layout/layout_element.py", line 307, in layout return self.calculate_layout_box_and_do_layout(page, bounding_box) File "/layers/google.python.pip/pip/lib/python3.9/site-packages/borb/pdf/canvas/layout/layout_element.py", line 320, in calculate_layout_box_and_do_layout layout_box = self._calculate_layout_box(page, bounding_box) File "/layers/google.python.pip/pip/lib/python3.9/site-packages/borb/pdf/canvas/layout/layout_element.py", line 230, in _calculate_layout_box returned_layout_box = self._calculate_layout_box_without_padding( File "/layers/google.python.pip/pip/lib/python3.9/site-packages/borb/pdf/canvas/layout/layout_element.py", line 258, in _calculate_layout_box_without_padding layout_rect = self._do_layout_without_padding(page, bounding_box) File "/layers/google.python.pip/pip/lib/python3.9/site-packages/borb/pdf/canvas/layout/table/fixed_column_width_table.py", line 131, in _do_layout_without_padding t.layout(page, Rectangle(x, y, w, h)) File "/layers/google.python.pip/pip/lib/python3.9/site-packages/borb/pdf/canvas/layout/table/table.py", line 145, in layout modified_layout_box: Rectangle = Rectangle( File "/layers/google.python.pip/pip/lib/python3.9/site-packages/borb/pdf/canvas/geometry/rectangle.py", line 26, in __init__ assert height >= 0, "A Rectangle must have a non-negative height." AssertionError: A Rectangle must have a non-negative height.

Example of payload:

restaurants_invoices57tg2dhgll0j {'oldValue': {}, 'updateMask': {}, 'value': {'createTime': '2023-09-28T19:59:18.390991Z', 'fields': {'buyer_details': {'mapValue': {'fields': {'buyer_address_city_postal_code_country': {'stringValue': 'Errrrrrr-Errrrrrr, NNNNNN, BC500'}, 'buyer_address_street': {'stringValue': 'Test 1,2'}, 'buyer_email': {'stringValue': '[email protected]'}, 'buyer_id': {'stringValue': 'xrr1p3s5Fh7NLzSknd1a'}, 'buyer_name': {'stringValue': 'test'}, 'buyer_phone': {'stringValue': '+235234566'}}}}, 'invoice_number': {'integerValue': '12'}, 'product': {'mapValue': {'fields': {'amount': {'doubleValue': 7.49}, 'description': {'stringValue': 'Payments genreated during the peroid'}, 'quantity': {'doubleValue': 1.0}}}}, 'seller_details': {'mapValue': {'fields': {'seller_BIC': {'stringValue': 'CDAERTEWXXX'}, 'seller_IBAN': {'stringValue': 'BC2356789954332123456778'}, 'seller_address_city_postal_code_country': {'stringValue': 'NDORWE TT TELLE BC700 bertyeq'}, 'seller_address_street': {'stringValue': ''}, 'seller_email': {'stringValue': '[email protected]'}, 'seller_id': {'stringValue': 'mOCXjlKsbwTAgUMo9uKz'}, 'seller_name': {'stringValue': 'tewqertts | dedddfe'}, 'seller_phone': {'stringValue': '+11 22 2222222'}}}}, 'status': {'stringValue': 'pending'}}, 'name': 'projects/my-app-prod/databases/(default)/documents/invoices_restaurants/7mzWObLnyZEGjsS0qAao', 'updateTime': '2023-09-28T19:59:18.390991Z'}}

I was expecting an invoice pdf generated from the seller to the buyer with the quanity and amounts needed.

0

There are 0 answers