# -*- coding: iso-8859-15 -*-

"""
Library package containing the definition of flow generation classes used in
the PDF generation with ReportLab platypus. These classes generate the flow
content for an entity Refund or Expense in the Fresh application. This
flow content will be displayed on various pages by the FreshDocTemplate
through the platypus machinery.
"""

from reportlab.lib.units import cm
from reportlab.lib.colors import black

from reportlab.platypus import Paragraph, Spacer, KeepTogether
from reportlab.platypus import Table, TableStyle

from logilab.mtconverter import xml_escape

from cubicweb_expense.pdfgen.utils import format_number
from cubicweb_expense.pdfgen.styles import style_metadata, style_ids, style_address
from cubicweb_expense.pdfgen.styles import style_table_header, style_table_data
from cubicweb_expense.pdfgen.styles import style_table_data_num


class AbstractFlowGenerator:
    """
    Abstract class containing the common parts for generating the PDF flow
    content for the various kinds of documents generated by the Fresh
    application (Refund, Expense). Specific parts are generated in the
    derived classes (one for each kind of document) (cf. Template design
    pattern).
    For a correct behaviour, you must use a flow generator corresponding to
    the kind of document you want to render in PDF.
    The PDF flow content is a list of Flowable that has to be displayed on
    various pages.
    """

    def __init__(self):
        """
        Instanciates the flow generator. This constructor should never be
        called directly as this class is abstract, it will be called by a
        child class constructor.
        """
        if self.__class__ is AbstractFlowGenerator:
            raise NotImplementedError()

    def generate_flow(self, entity):
        """
        Generates the flow content (a list of Flowable) for the entity.

        entity: Entity. Any entity of class Refund or Expense that must
                be displayed in PDF.
        Returns: [Flowable, ...]
        """
        flow_list = []  # [Flowable, ...]

        # top table containing on the left the metadata and on the right the
        # name of the person concerned by the document
        top_table = Table(
            [
                [self.get_metadata(entity), "", self.get_person_name(entity)],
            ],
            [11.5 * cm, 0.5 * cm, 7 * cm],
        )
        top_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                    ("VALIGN", (0, 0), (-1, -1), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("TOPPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, -1), 0 * cm),
                ]
            )
        )

        flow_list.append(top_table)

        flow_list.append(Spacer(1, 0.5 * cm))

        # main table containing the expenses description
        flow_list.append(self.get_expenses_table(entity))

        flow_list.append(Spacer(1, 1 * cm))

        # bottom table containing on the right the total table and on the left
        # other useful data (payment, subtotals)
        bottom_table = Table(
            [
                [self.get_total_relative_data(entity), " ", self.get_total(entity)],
            ],
            [12.5 * cm, 0.5 * cm, 6 * cm],
        )
        bottom_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                    ("VALIGN", (0, 0), (-1, -1), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("TOPPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, -1), 0 * cm),
                ]
            )
        )

        flow_list.append(KeepTogether(bottom_table))

        return flow_list

    def get_metadata(self, entity):
        """
        Gives the table that contains metadata about the entity (title,
        workcase, etc.)
        This abstract method will be defined in the children classes.

        entity: Entity. Any entity of class Expense or Refund.
        Returns: Flowable (very likely a Table).
        """
        raise NotImplementedError()

    def get_person_name(self, entity):
        """
        Gives the table that contains the name and address of the person
        concerned by th document.
        This abstract method will be defined in the children classes.

        entity: Entity. Any entity of class Expense or Refund.
        Returns: Flowable (very likely a Table).
        """
        raise NotImplementedError()

    def get_total(self, entity):
        """
        Gives the table that contains the total.
        This abstract method will be defined in the children classes.

        entity: Entity. Any entity of class Expense or Refund.
        Returns: Flowable (very likely a Table).
        """
        raise NotImplementedError()

    def get_total_relative_data(self, entity):
        """
        Gives the table that contains useful data relative to the total
        (subtotals, payment information, etc.)
        This abstract method will be defined in the children classes.

        entity: Entity. Any entity of class Expense or Refund.
        Returns: Flowable (very likely a Table).
        """
        raise NotImplementedError()

    def get_expenses_table(self, entity):
        """
        Gives the main table of the document (that contains lines describing
        the actual expenses).
        This abstract method will be defined in the children classes.

        entity: Entity. Any entity of class Expense or Refund.
        Returns: Flowable (very likely a Table).
        """
        raise NotImplementedError()


class ExpenseFlowGenerator(AbstractFlowGenerator):
    """
    Class building the specific contents for PDF flow content generation.
    This class must be used when we want to generate the flow for an
    Expense.
    """

    def __init__(self):
        """
        Initializes the flow content generator for Expense entities.
        """
        AbstractFlowGenerator.__init__(self)

    def get_metadata(self, entity):
        """
        Gives the table that contains metadata about the entity (title,
        workcase, etc.)

        entity: Entity. Any entity of class Expense.         .
        Returns: Flowable (very likely a Table).
        """
        _ = entity._cw._
        col_data = []

        s = "%s%s%s" % (
            xml_escape(_("Subject")),
            xml_escape(_(": ")),
            xml_escape(entity.title),
        )
        col_data.append(Paragraph(s, style_metadata))

        try:
            wcase = entity.spent_for[0]
        except (IndexError, AttributeError, KeyError):
            pass
        else:
            s = xml_escape(wcase.ref)
            s = "%s%s%s" % (
                xml_escape(_("Workcase")),
                xml_escape(_(": ")),
                xml_escape(wcase.ref),
            )
            col_data.append(Paragraph(s, style_metadata))

        s = "%s%s#%s" % (xml_escape(_("Reference")), xml_escape(_(": ")), entity.eid)
        col_data.append(Paragraph(s, style_metadata))

        meta_table = Table(
            [
                [col_data],
            ],
            [11.5 * cm],
        )
        meta_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                    ("VALIGN", (0, 0), (-1, -1), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("TOPPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("BOX", (0, 0), (-1, -1), 0.05 * cm, black),
                ]
            )
        )

        return meta_table

    def get_person_name(self, entity):
        """
        Gives the table that contains the name and address of the person
        concerned by th document.

        entity: Entity. Any entity of class Expense.
        Returns: Flowable (very likely a Table).
        """
        return Spacer(0, 0 * cm)

    def get_total(self, entity):
        """
        Gives the table that contains the total.

        entity: Entity. Any entity of class Expense.
        Returns: Flowable (very likely a Table).
        """
        _ = entity._cw._
        tab_data = []

        # First line
        lin_data = []
        s = "<b>%s</b>" % xml_escape(_("Total (EUR)"))
        lin_data.append(Paragraph(s, style_table_data))

        s = "<b>%s</b>" % format_number(entity.total, 2)
        lin_data.append(Paragraph(s, style_table_data_num))

        tab_data.append(lin_data)

        # Second line
        lin_data = []
        s = "<i>%s</i>" % xml_escape(_("included taxes (EUR)"))
        lin_data.append(Paragraph(s, style_table_data))

        s = "<i>%s</i>" % format_number(entity.euro_taxes(), 2)
        lin_data.append(Paragraph(s, style_table_data_num))

        tab_data.append(lin_data)

        tot_table = Table(tab_data, [3.25 * cm, 2.75 * cm])
        tot_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                    ("VALIGN", (0, 0), (-1, -1), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("TOPPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("BOX", (0, 0), (-1, -1), 0.05 * cm, black),
                    ("INNERGRID", (0, 0), (-1, -1), 0.02 * cm, black),
                ]
            )
        )

        return tot_table

    def get_total_relative_data(self, entity):
        """
        Gives the table that contains useful data relative to the total
        (subtotals, payment information, etc.)

        entity: Entity. Any entity of class Expense.
        Returns: Flowable (very likely a Table).
        """
        _ = entity._cw._
        col_data = []

        refundable_tot = entity.totals_paid_by()
        for euser in refundable_tot.keys():
            s = "%s %s %s %s" % (
                format_number(refundable_tot[euser], 2),
                xml_escape(_("EUR")),
                xml_escape(_("to be refunded to")),
                xml_escape(euser.dc_title()),
            )
            col_data.append(Paragraph(s, style_table_data))

        if col_data != []:
            rel_table = Table(
                [
                    [col_data],
                ],
                [12.5 * cm],
            )
            rel_table.setStyle(
                TableStyle(
                    [
                        ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                        ("VALIGN", (0, 0), (-1, -1), "TOP"),
                        ("LEFTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                        ("RIGHTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                        ("TOPPADDING", (0, 0), (-1, -1), 0.2 * cm),
                        ("BOTTOMPADDING", (0, 0), (-1, -1), 0.2 * cm),
                        ("BOX", (0, 0), (-1, -1), 0.05 * cm, black),
                    ]
                )
            )

            return rel_table

        else:
            return Spacer(0, 0 * cm)

    def get_expenses_table(self, entity):
        """
        Gives the main table of the document (that contains lines describing
        the actual expenses).

        This table contains only one column. In this column, a table is
        inserted for each expense line. This table also contains one column.
        In this column, we insert two tables (one per line) because each line
        has different column sizes. See the _build_inner_tables method for
        details.

        entity: Entity. Any entity of class Expense.
        Returns: Flowable (very likely a Table).
        """
        _ = entity._cw._
        tab_data = []

        # Writes the header lines

        # First line
        lin_data_1 = []

        s = "<b>%s</b>" % xml_escape(_("Date"))
        lin_data_1.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Description"))
        lin_data_1.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Expense Type"))
        lin_data_1.append(Paragraph(s, style_table_header))

        # Second line
        lin_data_2 = []

        s = "<b>%s</b>" % xml_escape(_("Paid by"))
        lin_data_2.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Paid for"))
        lin_data_2.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Amount"))
        lin_data_2.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Curr."))
        lin_data_2.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Rate"))
        lin_data_2.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Amount (EUR)"))
        lin_data_2.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Taxes (EUR)"))
        lin_data_2.append(Paragraph(s, style_table_header))

        # adds a table built with the two previously defined lines
        tab_data.append([self._build_inner_tables(lin_data_1, lin_data_2)])

        # Writes the accounting lines
        for exp_line in entity.has_lines:
            # First line
            lin_data_1 = []

            s = xml_escape(exp_line.printable_value("diem"))
            lin_data_1.append(Paragraph(s, style_table_data))

            s = xml_escape(exp_line.title)
            lin_data_1.append(Paragraph(s, style_table_data))

            s = xml_escape(_(exp_line.type))
            lin_data_1.append(Paragraph(s, style_table_data))

            # Second line
            lin_data_2 = []

            s = xml_escape(exp_line.paid_by[0].label)
            lin_data_2.append(Paragraph(s, style_table_data))

            col_data = []
            for dest in exp_line.paid_for:
                s = xml_escape(dest.label)
                col_data.append(Paragraph(s, style_table_data))
            lin_data_2.append(col_data)

            s = format_number(exp_line.amount, 2)
            lin_data_2.append(Paragraph(s, style_table_data_num))

            s = xml_escape(exp_line.currency)
            lin_data_2.append(Paragraph(s, style_table_data))

            s = format_number(exp_line.exchange_rate, 5)
            lin_data_2.append(Paragraph(s, style_table_data_num))

            s = "<b>%s</b>" % format_number(exp_line.euro_amount(), 2)
            lin_data_2.append(Paragraph(s, style_table_data_num))

            s = "<i>%s</i>" % format_number(exp_line.euro_taxes(), 2)
            lin_data_2.append(Paragraph(s, style_table_data_num))

            # adds a table built with the two previously defined lines
            tab_data.append([self._build_inner_tables(lin_data_1, lin_data_2)])

        # Builds the main table that will be returned
        main_table = Table(tab_data, [19 * cm])
        main_table.repeatRows = 1
        main_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                    ("VALIGN", (0, 0), (-1, -1), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("TOPPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, -2), 0.3 * cm),
                    ("BOTTOMPADDING", (0, -1), (-1, -1), 0 * cm),
                    ("BOX", (0, 0), (-1, -1), 0.05 * cm, black),
                ]
            )
        )

        return main_table

    def _build_inner_tables(self, line1_data, line2_data):
        """
        Builds inner tables that will be displayed into the main table (the
        one that contains ExpenseLines). Hence, an ExpenseLine cannot be
        displayed on a single line, thus we build two tables of one line to
        display one ExpenseLine. Each table has a different number of columns
        and different sizes of columns.

        line1_data: [Flowable, ...] first line
        line2_data: [Flowable, ...] second line
        """
        # Builds table for the first line
        line1_table = Table([line1_data], [2.25 * cm, 14 * cm, 2.75 * cm])
        line1_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, 0), "LEFT"),
                    ("VALIGN", (0, 0), (-1, 0), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, 0), 0.1 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, 0), 0.1 * cm),
                    ("TOPPADDING", (0, 0), (-1, 0), 0.1 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, 0), 0.1 * cm),
                    ("INNERGRID", (0, 0), (-1, 0), 0.02 * cm, black),
                ]
            )
        )

        # Builds table for the second line
        line2_table = Table(
            [line2_data],
            [
                4.25 * cm,
                4.5 * cm,
                2.25 * cm,
                1.25 * cm,
                1.75 * cm,
                2.75 * cm,
                2.25 * cm,
            ],
        )
        line2_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, 0), "LEFT"),
                    ("VALIGN", (0, 0), (-1, 0), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, 0), 0.1 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, 0), 0.1 * cm),
                    ("TOPPADDING", (0, 0), (-1, 0), 0.1 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, 0), 0.1 * cm),
                    ("INNERGRID", (0, 0), (-1, 0), 0.02 * cm, black),
                    ("LINEABOVE", (0, 0), (-1, 0), 0.02 * cm, black),
                ]
            )
        )

        # Builds the table that contains the two lines describing the expense
        both_lines_table = Table([[line1_table], [line2_table]], [19 * cm])
        both_lines_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                    ("VALIGN", (0, 0), (-1, -1), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("TOPPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, -1), 0 * cm),
                    ("BOX", (0, 0), (-1, -1), 0.02 * cm, black),
                ]
            )
        )

        return both_lines_table


class RefundFlowGenerator(AbstractFlowGenerator):
    """
    Class building the specific contents for PDF flow content generation.
    This class must be used when we want to generate the flow for a
    Refund.
    """

    def __init__(self):
        """
        Initializes the flow content generator for Refund entities.
        """
        AbstractFlowGenerator.__init__(self)

    def get_metadata(self, entity):
        """
        Gives the table that contains metadata about the entity (title,
        workcase, etc.)

        entity: Entity. Any entity of class Refund.
        Returns: Flowable (very likely a Table).
        """
        return Spacer(0, 0 * cm)

    def get_person_name(self, entity):
        """
        Gives the table that contains the name and address of the person
        concerned by th document.

        entity: Entity. Any entity of class Refund.
        Returns: Flowable (very likely a Table).
        """
        _ = entity._cw._
        col_data = []

        euser = entity.for_user()
        if euser:
            s = "%s%s%s" % (
                xml_escape(_("SS number")),
                xml_escape(_(": ")),
                xml_escape(euser.ssnum or "missing SS number"),
            )
            col_data.append(Paragraph(s, style_ids))

            col_data.append(Spacer(1, 0.2 * cm))

            s = "<b>%s</b>" % xml_escape(euser.name())
            col_data.append(Paragraph(s, style_address))

            if euser.lives_at:
                addr = euser.lives_at[0]

                s = xml_escape(addr.street)
                col_data.append(Paragraph(s, style_address))

                s = "%s %s" % (xml_escape(addr.postalcode), xml_escape(addr.city))
                col_data.append(Paragraph(s, style_address))

        else:  # could not find related euser
            s = "<b>???</b>"
            col_data.append(Paragraph(s, style_address))

        # Builds address table
        addr_table = Table(
            [
                [col_data],
            ],
            [7 * cm],
        )
        addr_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                    ("VALIGN", (0, 0), (-1, -1), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("TOPPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("BOX", (0, 0), (-1, -1), 0.05 * cm, black),
                ]
            )
        )

        return addr_table

    def get_total(self, entity):
        """
        Gives the table that contains the total.

        entity: Entity. Any entity of class Refund.
        Returns: Flowable (very likely a Table).
        """
        _ = entity._cw._
        lin_data = []

        s = "<b>%s</b>" % xml_escape(_("To refund (EUR)"))
        lin_data.append(Paragraph(s, style_table_data))

        s = "<b>%s</b>" % format_number(entity.total, 2)
        lin_data.append(Paragraph(s, style_table_data_num))

        # Builds the total table
        tot_table = Table([lin_data], [3.25 * cm, 2.75 * cm])
        tot_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                    ("VALIGN", (0, 0), (-1, -1), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("TOPPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("BOX", (0, 0), (-1, -1), 0.05 * cm, black),
                    ("INNERGRID", (0, 0), (-1, -1), 0.02 * cm, black),
                ]
            )
        )

        return tot_table

    def get_total_relative_data(self, entity):
        """
        Gives the table that contains useful data relative to the total
        (subtotals, payment information, etc.)

        entity: Entity. Any entity of class Refund.
        Returns: Flowable (very likely a Table).
        """
        _ = entity._cw._
        col_data = []

        if entity.payment_date is None:
            date = "\u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 "
        else:
            date = entity.printable_value("payment_date")
        if entity.payment_mode is None:
            mode = ""
        else:
            mode = entity.payment_mode
        s = "%s %s %s %s" % (
            xml_escape(_("Refunded on")),
            xml_escape(date),
            xml_escape(_("by")),
            xml_escape(mode),
        )
        col_data.append(Paragraph(s, style_table_data))

        # Builds payment table
        payment_table = Table(
            [
                [col_data],
            ],
            [12.5 * cm],
        )
        payment_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                    ("VALIGN", (0, 0), (-1, -1), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("TOPPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, -1), 0.2 * cm),
                    ("BOX", (0, 0), (-1, -1), 0.05 * cm, black),
                ]
            )
        )

        return payment_table

    def get_expenses_table(self, entity):
        """
        Gives the main table of the document (that contains lines describing
        the actual expenses).

        entity: Entity. Any entity of class Refund.
        Returns: Flowable (very likely a Table).
        """
        _ = entity._cw._
        tab_data = []

        # Writes the header line
        lin_data = []

        s = "<b>%s</b>" % xml_escape(_("Date"))
        lin_data.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Description"))
        lin_data.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Workcase"))
        lin_data.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Amount"))
        lin_data.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Curr."))
        lin_data.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Rate"))
        lin_data.append(Paragraph(s, style_table_header))

        s = "<b>%s</b>" % xml_escape(_("Amount (EUR)"))
        lin_data.append(Paragraph(s, style_table_header))

        tab_data.append(lin_data)

        # Writes the accounting lines
        for exp_line in entity.has_lines:
            lin_data = []

            s = xml_escape(exp_line.printable_value("diem"))
            lin_data.append(Paragraph(s, style_table_data))

            s = xml_escape(exp_line.title)
            lin_data.append(Paragraph(s, style_table_data))

            s = ""
            expense = exp_line.parent_expense
            if expense:
                # XXX use if 'spent_for' in schema
                try:
                    wcase = expense.spent_for[0]
                except (IndexError, AttributeError, KeyError):
                    pass
                else:
                    s = xml_escape(wcase.ref)
            lin_data.append(Paragraph(s, style_table_data))

            s = format_number(exp_line.amount, 2)
            lin_data.append(Paragraph(s, style_table_data_num))

            s = xml_escape(exp_line.currency)
            lin_data.append(Paragraph(s, style_table_data))

            s = format_number(exp_line.exchange_rate, 5)
            lin_data.append(Paragraph(s, style_table_data_num))

            s = format_number(exp_line.euro_amount(), 2)
            lin_data.append(Paragraph(s, style_table_data_num))

            tab_data.append(lin_data)

        # Builds the main table containing the expenses lines
        main_table = Table(
            tab_data,
            [2.25 * cm, 7 * cm, 1.75 * cm, 2.25 * cm, 1.25 * cm, 1.75 * cm, 2.75 * cm],
        )
        main_table.repeatRows = 1
        main_table.setStyle(
            TableStyle(
                [
                    ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                    ("VALIGN", (0, 0), (-1, -1), "TOP"),
                    ("LEFTPADDING", (0, 0), (-1, -1), 0.1 * cm),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 0.1 * cm),
                    ("TOPPADDING", (0, 0), (-1, -1), 0.1 * cm),
                    ("BOTTOMPADDING", (0, 0), (-1, -1), 0.1 * cm),
                    ("BOX", (0, 0), (-1, -1), 0.05 * cm, black),
                    ("LINEAFTER", (0, 0), (0, -1), 0.02 * cm, black),
                    ("LINEAFTER", (1, 0), (1, -1), 0.02 * cm, black),
                    ("LINEAFTER", (2, 0), (2, -1), 0.05 * cm, black),
                    ("LINEAFTER", (3, 0), (3, -1), 0.02 * cm, black),
                    ("LINEAFTER", (4, 0), (4, -1), 0.02 * cm, black),
                    ("LINEAFTER", (5, 0), (5, -1), 0.05 * cm, black),
                    ("LINEBELOW", (0, 0), (-1, 0), 0.05 * cm, black),
                ]
            )
        )

        return main_table
