diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 8118de9..8165714 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -28,7 +28,7 @@ jobs:
run: sudo apt-get update --allow-releaseinfo-change && sudo apt-get install qpdf
- name: Install dependencies
run: |
- python -m pip install --upgrade pip setuptools fpdf2 phonenumbers python-barcode pytest pytest-cov
+ python -m pip install --upgrade pip setuptools fpdf2 phonenumbers python-barcode pytest qrcode pytest-cov
- name: Test
run: |
pytest --cov=./brazilfiscalreport --cov-report=xml --cov-branch --doctest-glob="docs/*.md"
diff --git a/README.md b/README.md
index 9d911db..ea9c2e5 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ Python library for generating Brazilian auxiliary fiscal documents in PDF from X
- DANFE - Documento Auxiliar da Nota Fiscal Eletrônica (NF-e)
- DACCe - Documento Auxiliar da Carta de Correção Eletrônica (CC-e )
+- DACTE - Documento Auxiliar do Conhecimento de Transporte Eletrônico (CT-e)
## Beta Stage Notice 🚧
@@ -22,6 +23,7 @@ This library is currently in the beta stage of development. While it has many of
- [FPDF2](https://github.com/py-pdf/fpdf2) - PDF creation library for Python
- phonenumbers
- python-barcode
+- qrcode
## To install 🔧
@@ -67,6 +69,25 @@ cce = DaCCe(xml=xml_content)
cce.output('cce.pdf')
```
+### DACTE
+
+```python
+from brazilfiscalreport.dacte import Dacte
+
+# Path to the XML file
+xml_file_path = 'dacte.xml'
+
+# Load XML Content
+with open(xml_file_path, "r", encoding="utf8") as file:
+ xml_content = file.read()
+
+# Instantiate the DACTE object with the loaded XML content
+dacte = Dacte(xml=xml_content)
+
+# Save the generated PDF to a file
+dacte.output('dacte.pdf')
+```
+
## Samples 📝
Some sample PDFs generated by our unit tests are available for viewing in the [tests/generated](https://github.com/Engenere/BrazilFiscalReport/tree/main/tests/generated) directory.
@@ -199,6 +220,75 @@ danfe = Danfe(xml_content, config=config)
danfe.output('output_danfe.pdf')
```
+## Customizing DACTE
+
+This section describes how to customize the PDF output of the DACTE using the DacteConfig class. You can adjust various settings such as margins, fonts, and tax configurations according to your needs.
+
+### Configuration Options
+
+Here is a breakdown of all the configuration options available in ``DanfeConfig``:
+
+1. **Logo**
+ - **Type**: ``str``, ``BytesIO``, or ``bytes``
+ - **Description**: Path to the logo file or binary image data to be included in the PDF. You can use a file path string or pass image data directly.
+ - **Example**:
+ ```python
+ config.logo = "path/to/logo.jpg" # Using a file path
+ ```
+ - **Default**: No logo.
+
+
+2. **Margins**
+ - **Type**: ``Margins``
+ - **Fields**: ``top``, ``right``, ``bottom``, ``left`` (all of type ``Number``)
+ - **Description**: Sets the page margins for the PDF document.
+ - **Example**:
+ ```python
+ config.margins = Margins(top=5, right=5, bottom=5, left=5)
+ ```
+ - **Default**: top, right, bottom and left is 2 mm.
+
+3. **Font Type**
+ - **Type**: ``FontType`` (Enum)
+ - **Values**: ``COURIER``, ``TIMES``
+ - **Description**: Font style used throughout the PDF document.
+ - **Example**::
+ ```python
+ config.font_type = FontType.COURIER
+ ```
+ - **Default**: ``TIMES``
+
+### Usage Example with Customization
+
+Here’s how to set up a DacteConfig object with a full set of customizations:
+
+```python
+from brazilfiscalreport.dacte import (
+ Dacte,
+ DacteConfig,
+ FontType,
+ Margins,
+)
+
+# Path to the XML file
+xml_file_path = 'cte.xml'
+
+# Load XML Content
+with open(xml_file_path, "r", encoding="utf8") as file:
+ xml_content = file.read()
+
+# Create a configuration instance
+config = DacteConfig(
+ logo='path/to/logo.png',
+ margins=Margins(top=10, right=10, bottom=10, left=10),
+ font_type=FontType.TIMES
+)
+
+# Use this config when creating a Dacte instance
+dacte = Dacte(xml_content, config=config)
+dacte.output('output_dacte.pdf')
+```
+
## Credits 🙌
This is a fork of the [nfe_utils](https://github.com/edsonbernar/nfe_utils) project, originally created by [Edson Bernardino](https://github.com/edsonbernar).
diff --git a/brazilfiscalreport/dacte/__init__.py b/brazilfiscalreport/dacte/__init__.py
new file mode 100644
index 0000000..9fb813e
--- /dev/null
+++ b/brazilfiscalreport/dacte/__init__.py
@@ -0,0 +1,17 @@
+from .config import (
+ DacteConfig,
+ DecimalConfig,
+ FontType,
+ Margins,
+ ReceiptPosition,
+)
+from .dacte import Dacte
+
+__all__ = [
+ "Dacte",
+ "DacteConfig",
+ "DecimalConfig",
+ "FontType",
+ "Margins",
+ "ReceiptPosition",
+]
diff --git a/brazilfiscalreport/dacte/config.py b/brazilfiscalreport/dacte/config.py
new file mode 100644
index 0000000..076295d
--- /dev/null
+++ b/brazilfiscalreport/dacte/config.py
@@ -0,0 +1,39 @@
+from dataclasses import dataclass, field
+from enum import Enum
+from io import BytesIO
+from numbers import Number
+from typing import Union
+
+
+class FontType(Enum):
+ COURIER = "Courier"
+ TIMES = "Times"
+
+
+@dataclass
+class Margins:
+ top: Number = 2
+ right: Number = 2
+ bottom: Number = 2
+ left: Number = 2
+
+
+@dataclass
+class DecimalConfig:
+ price_precision: int = 4
+ quantity_precision: int = 4
+
+
+class ReceiptPosition(Enum):
+ TOP = "top"
+ BOTTOM = "bottom"
+ LEFT = "left"
+
+
+@dataclass
+class DacteConfig:
+ logo: Union[str, BytesIO, bytes] = None
+ margins: Margins = field(default_factory=Margins)
+ receipt_pos: ReceiptPosition = ReceiptPosition.TOP
+ decimal_config: DecimalConfig = field(default_factory=DecimalConfig)
+ font_type: FontType = FontType.TIMES
diff --git a/brazilfiscalreport/dacte/dacte.py b/brazilfiscalreport/dacte/dacte.py
new file mode 100644
index 0000000..2f2dec9
--- /dev/null
+++ b/brazilfiscalreport/dacte/dacte.py
@@ -0,0 +1,1627 @@
+# Copyright (C) 2024 Engenere - Cristiano Mafra Junior
+
+import re
+import xml.etree.ElementTree as ET
+from io import BytesIO
+from xml.etree.ElementTree import Element
+
+import qrcode
+from barcode import Code128
+from barcode.writer import SVGWriter
+
+from ..utils import (
+ format_cep,
+ format_cpf_cnpj,
+ format_number,
+ format_phone,
+ get_date_utc,
+ get_tag_text,
+)
+from ..xfpdf import xFPDF
+from .config import DacteConfig, ReceiptPosition
+from .dacte_conf import URL
+
+tp_modal = {
+ "01": "RODOVIÁRIO",
+ "02": "AÉREO",
+ "03": "AQUAVIÁRIO",
+ "04": "FERROVIÁRIO",
+ "05": "DUTOVIÁRIO",
+ "06": "MULTIMODAL",
+}
+tp_tomador = {
+ "0": "REMETENTE",
+ "1": "EXPEDIDOR",
+ "2": "RECEBEDOR",
+ "3": "DESTINATÁRIO",
+ "4": "OUTRO",
+}
+
+tp_cte = {
+ "0": "NORMAL",
+ "1": "COMPLEMENTAR",
+ "2": "ANULADO",
+ "3": "SUSBTITUTO",
+}
+
+tp_servico = {
+ "0": "NORMAL",
+ "1": "SUBCONTRATAÇÃO",
+ "2": "REDESPACHO",
+ "3": "REDESPACHO INTERMEDIÁRIO",
+}
+
+tp_codigo_medida = {
+ "00": "M3",
+ "01": "KG",
+ "02": "TON",
+ "03": "UNIDADE",
+ "04": "LITROS",
+ "05": "MMBTU",
+}
+
+
+def extract_text(node: Element, tag: str) -> str:
+ return get_tag_text(node, URL, tag)
+
+
+class Dacte(xFPDF):
+ def __init__(self, xml, config: DacteConfig = None):
+ super().__init__(unit="mm", format="A4")
+ config = config if config is not None else DacteConfig()
+ self.set_margins(
+ left=config.margins.left, top=config.margins.top, right=config.margins.right
+ )
+ self.set_auto_page_break(auto=False, margin=config.margins.bottom)
+ self.set_title("DACTE")
+ self.logo_image = config.logo
+ self.receipt_pos = config.receipt_pos
+ self.default_font = config.font_type.value
+ self.price_precision = config.decimal_config.price_precision
+ self.quantity_precision = config.decimal_config.quantity_precision
+
+ root = ET.fromstring(xml)
+ self.inf_cte = root.find(f"{URL}infCte")
+ self.prot_cte = root.find(f"{URL}protCTe")
+ self.emit = root.find(f"{URL}emit")
+ self.ide = root.find(f"{URL}ide")
+ self.dest = root.find(f"{URL}dest")
+ self.exped = root.find(f"{URL}exped")
+ self.receb = root.find(f"{URL}receb")
+ self.rem = root.find(f"{URL}rem")
+ self.inf_prot = root.find(f"{URL}infProt")
+ self.inf_cte_supl = root.find(f"{URL}infCTeSupl")
+ self.tomador = root.find(f"{URL}toma3")
+ self.inf_carga = root.find(f"{URL}infCarga")
+ self.inf_doc = root.find(f"{URL}infDoc")
+ self.v_prest = root.find(f"{URL}vPrest")
+ self.inf_modal = root.find(f"{URL}infModal")
+ self.imp = root.find(f"{URL}imp")
+ self.compl = root.find(f"{URL}compl")
+
+ self.obs_dacte_list = []
+ for obs in self.compl:
+ self.x_texto = extract_text(obs, "xTexto")
+ self.x_texto = " ".join(
+ re.split(r"\s+", self.x_texto.strip(), flags=re.UNICODE)
+ )
+ self.obs_dacte_list.append(self.x_texto)
+
+ self.inf_carga_list = []
+ for infQ in self.inf_carga:
+ self.c_unid = extract_text(infQ, "cUnid")
+ self.tp_media = extract_text(infQ, "tpMed")
+ self.q_carga = extract_text(infQ, "qCarga")
+ self.inf_carga_list.append((self.c_unid, self.tp_media, self.q_carga))
+
+ self.inf_doc_list = []
+ for chave in self.inf_doc:
+ self.chave = extract_text(chave, "chave")
+ self.inf_doc_list.append(self.chave)
+
+ self.comp_list = []
+ for comp in self.v_prest:
+ self.xNome = extract_text(comp, "xNome")
+ self.vComp = extract_text(comp, "vComp")
+ self.comp_list.append((self.xNome, self.vComp))
+
+ self.total_receipt_height = 19 # TODO need compute
+
+ # extract orientation
+ tpImp = extract_text(self.ide, "tpImp")
+ if tpImp == "1":
+ self.orientation = "P"
+ else:
+ self.orientation = "L"
+ # force receipt position
+ # landscape support only left receipt
+ self.receipt_pos = ReceiptPosition.LEFT
+
+ # Emit. CNPJ/CPF
+ self.emit_cnpj_cpf = extract_text(self.emit, "CNPJ")
+ self.emit_id_label = "CNPJ"
+
+ # Dest. CNPJ/CPF
+ self.dest_cnpj_cpf = extract_text(self.dest, "CNPJ")
+ self.dest_id_label = "CNPJ"
+
+ self.recibo_text = self._get_receipt_text()
+ self.nr_dacte = extract_text(self.ide, "nCT")
+ self.serie_cte = extract_text(self.ide, "serie")
+ self.key_cte = self.inf_cte.attrib.get("Id")[3:]
+ self.tp_cte = tp_cte[extract_text(self.ide, "tpCTe")]
+ self.tp_serv = tp_servico[extract_text(self.ide, "tpServ")]
+ self.prot_uso = self._get_usage_protocol()
+ self.mod = extract_text(self.ide, "mod")
+ self.nct = extract_text(self.ide, "nCT")
+ self.toma = tp_tomador[extract_text(self.tomador, "toma")]
+ self.cfop = extract_text(self.ide, "CFOP")
+ self.nat_op = extract_text(self.ide, "natOp")
+
+ self.add_page(orientation=self.orientation)
+ self._draw_receipt()
+ self._draw_header()
+ self._draw_recipient_sender(config)
+ self._draw_service_recipient(config)
+ self._draw_service_fee_value()
+ self._draw_documents_obs()
+ self._draw_specific_data(config)
+ self._draw_void_watermark()
+ self._add_new_page()
+
+ def _get_usage_protocol(self):
+ dt, hr = get_date_utc(extract_text(self.prot_cte, "dhRecbto"))
+ protocol = extract_text(self.prot_cte, "nProt")
+ prot_text = f"{protocol} - {dt} {hr}"
+ return prot_text
+
+ def _get_receipt_text(self):
+ return (
+ "DECLARO QUE RECEBI OS VOLUMES DESTE CONHECIMENTO "
+ "EM PERFEITO ESTADO PELO QUE DOU POR "
+ "CUMPRIDO O PRESENTE CONTRATO DE TRANSPORTE"
+ )
+
+ def _draw_void_watermark(self):
+ if extract_text(self.ide, "tpAmb") == "2":
+ self.set_font(self.default_font, "B", 60)
+ watermark_text = "SEM VALOR FISCAL"
+ width = self.get_string_width(watermark_text)
+ self.set_text_color(r=220, g=150, b=150)
+ height = 15
+ page_width = self.w
+ page_height = self.h
+ x_center = (page_width - width) / 2
+ y_center = (page_height + height) / 2
+ with self.rotation(55, x_center + (width / 2), y_center - (height / 2)):
+ self.text(x_center, y_center, watermark_text)
+ self.set_text_color(r=0, g=0, b=0)
+
+ def _draw_dashed_line(self, distance):
+ self.set_dash_pattern(dash=0.2, gap=0.8)
+ if self.orientation == "P":
+ self.line(
+ x1=self.l_margin,
+ y1=distance,
+ x2=self.w - self.r_margin,
+ y2=distance,
+ )
+ else:
+ self.line(
+ x1=distance,
+ y1=self.t_margin,
+ x2=distance,
+ y2=self.h - self.b_margin,
+ )
+ self.set_dash_pattern(dash=0, gap=0)
+
+ def _draw_receipt(self):
+ x_margin = self.l_margin
+ y_margin = self.y
+ page_width = self.epw
+ w_date_field = 40
+ line_height = 8
+ cell_height = -5
+
+ def draw_vertical_lines(start_y, end_y):
+ col_width = page_width / 4
+ x_line1 = x_margin + col_width
+ x_line2 = x_margin + 2 * col_width
+ x_line3 = x_margin + 3 * col_width
+
+ self.line(x1=x_line1, x2=x_line1, y1=start_y, y2=end_y)
+ self.line(x1=x_line2, x2=x_line2, y1=start_y, y2=end_y)
+ self.line(x1=x_line3, x2=x_line3, y1=start_y, y2=end_y)
+
+ return x_line1, x_line2, x_line3
+
+ self._draw_dashed_line(distance=y_margin + 21)
+ self.set_dash_pattern(dash=0, gap=0)
+
+ self.rect(x=x_margin, y=y_margin, w=page_width - 0.5, h=3, style="")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x=x_margin, y=y_margin)
+ self.cell(
+ w=page_width - 2 * x_margin, h=3, text=self.recibo_text, border=0, align="L"
+ )
+
+ h_recibo = 17
+ self.rect(
+ x=x_margin, y=y_margin + 3.5, w=page_width - 0.5, h=h_recibo, style=""
+ )
+
+ x_line1, x_line2, x_line3 = draw_vertical_lines(
+ y_margin + 3.5, y_margin + h_recibo + 3.5
+ )
+
+ y_start = y_margin + 10
+ self.line(x1=x_margin, x2=x_line1, y1=y_start + 2, y2=y_start + 2)
+
+ self.set_font(self.default_font, "B", 8)
+ self.set_xy(x=x_margin + 2, y=y_start)
+ self.cell(w=w_date_field, h=cell_height, text="NOME", border=0, align="L")
+
+ self.set_xy(x=x_margin + 2, y=y_start + line_height)
+ self.cell(w=w_date_field, h=cell_height, text="RG", border=0, align="L")
+
+ self.set_xy(x=x_line1 + 7.5, y=y_start + 11)
+ self.cell(
+ w=w_date_field,
+ h=cell_height,
+ text="ASSINATURA / CARIMBO",
+ border=0,
+ align="L",
+ )
+
+ self.set_xy(x=x_line2 + 10, y=y_start)
+ self.cell(
+ w=w_date_field, h=cell_height, text="CHEGADA DATA/HORA", border=0, align="L"
+ )
+
+ self.set_xy(x=x_line2 + 10, y=y_start + line_height)
+ self.cell(
+ w=w_date_field, h=cell_height, text="SAÍDA DATA/HORA", border=0, align="L"
+ )
+
+ self.set_xy(x=x_line3 + 23, y=y_start - 2)
+ self.set_font(self.default_font, "B", 10)
+ self.cell(w=w_date_field, h=cell_height, text="CT-E", border=0, align="L")
+
+ self.set_xy(x=x_line3 + 5, y=y_start + 2)
+ self.set_font(self.default_font, "", 7)
+ self.cell(
+ w=w_date_field, h=cell_height, text="NRO. DOCUMENTO", border=0, align="L"
+ )
+
+ self.set_xy(x=x_line3 + 5, y=y_start + line_height)
+ self.cell(w=w_date_field, h=cell_height, text="SÉRIE", border=0, align="L")
+
+ self.set_xy(x=x_line3 + 35, y=y_start + 2)
+ self.set_font(self.default_font, "B", 7)
+ self.cell(
+ w=w_date_field, h=cell_height, text=self.nr_dacte, border=0, align="L"
+ )
+
+ self.set_xy(x=x_line3 + 38, y=y_start + line_height)
+ self.cell(
+ w=w_date_field, h=cell_height, text=self.serie_cte, border=0, align="L"
+ )
+
+ def draw_qr_code(self, y_margin_ret):
+ self.qr_code = extract_text(self.inf_cte_supl, "qrCodCTe")
+ qr = qrcode.QRCode(
+ version=1,
+ error_correction=qrcode.constants.ERROR_CORRECT_L,
+ box_size=10,
+ border=1,
+ )
+ qr.add_data(self.qr_code)
+ qr.make(fit=True)
+
+ qr_img = qr.make_image(fill_color="black", back_color="white")
+ qr_img_bytes = qr_img.get_image()
+
+ num_x = y_margin_ret + 88
+ num_y = self.t_margin + 32
+ num_w = 40
+ num_h = 40
+ self.image(qr_img_bytes, x=num_x + 1, y=num_y + 1, w=num_w - 2, h=num_h - 2)
+
+ def _draw_header(self):
+ x_margin = self.l_margin
+ y_margin = self.y
+ section_start_y = y_margin + 4
+ w_rect = (self.epw / 2) - 33
+ h_rect = 27
+ emit_name = extract_text(self.emit, "xNome")
+ self.cep = format_cep(extract_text(self.emit, "CEP"))
+ self.fone = format_phone(extract_text(self.emit, "fone"))
+ self.modal = tp_modal[extract_text(self.ide, "modal")]
+ self.mod = extract_text(self.ide, "mod")
+ self.serie = extract_text(self.ide, "serie")
+ self.nct = extract_text(self.ide, "nCT")
+ self.dt, self.hr = get_date_utc(extract_text(self.ide, "dhEmi"))
+ self.protocol = extract_text(self.prot_cte, "nProt")
+ self.dh_recebto, hr_recebto = get_date_utc(
+ extract_text(self.prot_cte, "dhRecbto")
+ )
+ address = (
+ f"{extract_text(self.emit, 'xLgr')}, "
+ f"{extract_text(self.emit, 'nro')}\n"
+ f"{extract_text(self.emit, 'xBairro')}\n"
+ f"{extract_text(self.emit, 'xMun')} - "
+ f"{extract_text(self.emit, 'UF')}\n"
+ f"{self.cep}\nFone: {self.fone}"
+ )
+ self.rect(x=x_margin, y=section_start_y, w=(self.epw / 2) - 33, h=h_rect)
+ h_logo = 8
+ w_logo = 8
+ y_logo = y_margin
+ if self.logo_image:
+ self.image(
+ name=self.logo_image,
+ x=x_margin,
+ y=y_logo + 10,
+ w=w_logo + 10,
+ h=h_logo + 10,
+ keep_aspect_ratio=True,
+ )
+ x_text = x_margin + 4
+ y_text = y_logo + 6
+ w_text = w_rect
+ else:
+ x_text = x_margin + 2
+ y_text = y_margin + 6
+ w_text = w_rect - 4
+ self.set_font(self.default_font, "B", 9)
+ self.set_xy(x=x_text, y=y_text)
+ self.multi_cell(w=w_text, h=5, text=emit_name, border=0, align="C")
+ self.set_font(self.default_font, "", 8)
+ self.set_xy(x=x_text - 3, y=y_text + 10)
+ self.multi_cell(w=w_text + 10, h=3, text=address, border=0, align="C")
+
+ y_margin = self.l_margin + 22
+ y_start = self.y + 4
+ y_margin_ret = self.l_margin + (self.epw / 2) - 33
+ w_rect = 53
+ h_rect = 11
+
+ self.rect(x=y_margin_ret, y=section_start_y, w=w_rect, h=h_rect)
+ self.set_font(self.default_font, "B", 10)
+ self.set_xy(x=y_margin_ret - 9, y=y_start - 29)
+ self.multi_cell(w=y_margin_ret, h=4, text="DACTE", align="C", border=0)
+ self.set_font(self.default_font, "", 6)
+ self.set_xy(x=y_margin_ret - 9, y=y_start - 25)
+ self.multi_cell(
+ w=y_margin_ret,
+ h=2,
+ text="DOCUMENTO AUXILIAR DO CONHECIMENTO\nDE TRANSPORTE ELETRÔNICO",
+ align="C",
+ )
+
+ self.rect(x=y_margin_ret + w_rect, y=section_start_y, w=31, h=11, style="")
+
+ self.set_font(self.default_font, "", 8)
+ self.set_xy(y_margin_ret + 55, section_start_y + 2)
+ self.multi_cell(w=31 - 4, h=1, text="MODAL", align="C")
+ self.set_xy(y_margin_ret + 55, section_start_y + 2)
+ self.set_font(self.default_font, "B", 8)
+ self.multi_cell(w=31 - 4, h=11, text=self.modal, align="C")
+
+ section_start_y += 11
+
+ self.rect(x=y_margin_ret, y=section_start_y, w=84, h=11, style="")
+
+ col_width = (206 - (x_margin + 112)) / 5
+ x_line_1 = x_margin + 70 + col_width
+ x_line_2 = x_margin + 70 + 2 * col_width
+ x_line_3 = x_margin + 70 + 3 * col_width
+ x_line_4 = x_margin + 70 + 4 * col_width
+ x_line_5 = x_margin + 70 + 4 * col_width
+ self.line(
+ x1=x_line_1 - 5,
+ x2=x_line_1 - 5,
+ y1=section_start_y,
+ y2=section_start_y + 11,
+ )
+ self.line(
+ x1=x_line_2 - 5,
+ x2=x_line_2 - 5,
+ y1=section_start_y,
+ y2=section_start_y + 11,
+ )
+ self.line(
+ x1=x_line_3 - 8,
+ x2=x_line_3 - 8,
+ y1=section_start_y,
+ y2=section_start_y + 11,
+ )
+ self.line(x1=x_line_4, x2=x_line_4, y1=section_start_y, y2=section_start_y + 11)
+ self.line(x1=x_line_5, x2=x_line_5, y1=section_start_y, y2=section_start_y + 11)
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_1 - 25, section_start_y + 2)
+ self.multi_cell(w=31 - 4, h=1, text="MODELO", align="C")
+ self.set_xy(x_line_1 - 25, section_start_y + 2)
+ self.set_font(self.default_font, "B", 7)
+ self.multi_cell(w=31 - 4, h=11, text=self.mod, align="C")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_2 - 28, section_start_y + 2)
+ self.multi_cell(w=31 - 4, h=1, text="SÉRIE", align="C")
+ self.set_xy(x_line_2 - 28, section_start_y + 2)
+ self.set_font(self.default_font, "B", 7)
+ self.multi_cell(w=31 - 4, h=11, text=self.serie, align="C")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_3 - 29, section_start_y + 2)
+ self.multi_cell(w=31 - 4, h=1, text="NÚMERO", align="C")
+ self.set_xy(x_line_3 - 29, section_start_y + 2)
+ self.set_font(self.default_font, "B", 7)
+ self.multi_cell(w=31 - 4, h=11, text=self.nct, align="C")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_4 - 27, section_start_y + 2)
+ self.multi_cell(w=31 - 4, h=2.5, text="DATA E HORA\nDE EMISSÃO", align="C")
+ self.set_xy(x_line_4 - 27, section_start_y + 2)
+ self.set_font(self.default_font, "B", 7)
+ self.multi_cell(w=31 - 4, h=13, text=f"{self.dt} {self.hr}", align="C")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_5 - 9, section_start_y + 2)
+ self.multi_cell(w=31 - 4, h=2, text="FL", align="C")
+ self.set_xy(x_line_5 - 9, section_start_y + 2)
+ self.set_font(self.default_font, "B", 7)
+ self.multi_cell(w=31 - 1, h=11, text=f"{self.page_no()}/{{nb}}", align="C")
+
+ section_start_y += 11
+ y = section_start_y + 0.5
+ w = 82
+ h = 11.5
+ self.rect(x=y_margin_ret, y=section_start_y, w=84, h=10, style="")
+ svg_img_bytes = BytesIO()
+ Code128(self.key_cte, writer=SVGWriter()).write(svg_img_bytes)
+ self.image(svg_img_bytes, x=y_margin_ret + 1, y=y, w=w, h=h)
+
+ section_start_y += 10
+
+ self.set_font(self.default_font, "", 8)
+ self.rect(x=y_margin_ret, y=section_start_y, w=84, h=10, style="")
+ self.set_xy(x_line_5 - 55, section_start_y + 2)
+ self.multi_cell(w=45, h=0, text="CHAVE DE ACESSO", align="C")
+ self.set_xy(x_line_5 - 70, section_start_y + 2)
+ self.set_font(self.default_font, "B", 8)
+ self.multi_cell(w=75, h=11, text=self.key_cte, align="C")
+ section_start_y += 10
+
+ self.rect(x=y_margin_ret, y=section_start_y, w=84, h=9, style="")
+ self.set_xy(x=y_margin_ret, y=section_start_y)
+ self.multi_cell(
+ w=85, h=10, text="CONSULTA EM http://www.cte.fazenda.gov.br", align="C"
+ )
+ section_start_y += 9
+
+ self.set_font(self.default_font, "", 8)
+ self.rect(x=y_margin_ret, y=section_start_y, w=84, h=10, style="")
+ self.set_xy(x=y_margin_ret, y=section_start_y)
+ self.multi_cell(w=85, h=4, text="PROTOCOLO DE AUTORIZAÇÃO DE USO", align="C")
+ self.set_xy(x=y_margin_ret, y=section_start_y)
+ self.set_font(self.default_font, "B", 8)
+ self.multi_cell(
+ w=86,
+ h=14,
+ text=f"{self.protocol} {self.dh_recebto} {hr_recebto}",
+ align="C",
+ )
+
+ section_start_y += 10
+ self.set_font(self.default_font, "", 8)
+ self.rect(
+ x=self.l_margin,
+ y=section_start_y - 34,
+ w=(self.epw / 2) - 33,
+ h=8,
+ style="",
+ )
+ self.set_xy(x=self.l_margin, y=section_start_y - 34)
+ self.multi_cell(w=85, h=4, text="TIPO DO CT-E", align="L")
+ self.set_xy(x=self.l_margin, y=section_start_y - 34)
+ self.set_font(self.default_font, "B", 8)
+ self.multi_cell(w=85, h=10, text=self.tp_cte, align="L")
+
+ section_start_y += 8
+
+ self.set_font(self.default_font, "", 8)
+ self.rect(
+ x=self.l_margin,
+ y=section_start_y - 34,
+ w=(self.epw / 2) - 33,
+ h=8,
+ style="",
+ )
+ self.set_xy(x=self.l_margin, y=section_start_y - 34)
+ self.multi_cell(w=85, h=4, text="TIPO DO SERVIÇO", align="L")
+ self.set_xy(x=self.l_margin, y=section_start_y - 34)
+ self.set_font(self.default_font, "B", 8)
+ self.multi_cell(w=85, h=10, text=self.tp_serv, align="L")
+
+ section_start_y += 8
+
+ self.set_font(self.default_font, "", 8)
+ self.rect(
+ x=self.l_margin,
+ y=section_start_y - 34,
+ w=(self.epw / 2) - 33,
+ h=9,
+ style="",
+ )
+ self.set_xy(x=self.l_margin, y=section_start_y - 34)
+ self.multi_cell(w=85, h=4, text="TOMADOR DO SERVIÇO", align="L")
+ self.set_xy(x=self.l_margin, y=section_start_y - 34)
+ self.set_font(self.default_font, "B", 8)
+ self.multi_cell(w=85, h=10, text=self.toma, align="L")
+
+ section_start_y += 9
+
+ self.set_font(self.default_font, "", 8)
+ self.rect(
+ x=self.l_margin,
+ y=section_start_y - 34,
+ w=(self.epw / 2) - 33,
+ h=9,
+ style="",
+ )
+ self.set_xy(x=self.l_margin, y=section_start_y - 34)
+ self.multi_cell(w=85, h=4, text="CFOP - NATUREZA DA PRESTAÇÃO", align="L")
+ self.set_xy(x=self.l_margin, y=section_start_y - 34)
+ self.set_font(self.default_font, "B", 8)
+ self.multi_cell(w=85, h=10, text=f"{self.cfop} - {self.nat_op}", align="L")
+
+ self.draw_qr_code(y_margin_ret)
+
+ def _draw_recipient_sender(self, config):
+ self.mun_ini = extract_text(self.ide, "xMunIni")
+ self.mun_fim = extract_text(self.ide, "xMunFim")
+ self.est_inico = extract_text(self.ide, "UFIni")
+ self.est_fim = extract_text(self.ide, "UFFim")
+
+ # Inf do Remetente
+ self.rem_nome = extract_text(self.rem, "xNome")
+ self.rem_loga = extract_text(self.rem, "xLgr")
+ self.rem_nro = extract_text(self.rem, "nro")
+ self.rem_bairro = extract_text(self.rem, "xBairro")
+ self.rem_mun = extract_text(self.rem, "xMun")
+ self.rem_cnpj = format_cpf_cnpj(extract_text(self.rem, "CNPJ"))
+ self.rem_pais = extract_text(self.rem, "xPais")
+ self.rem_cep = format_cep(extract_text(self.rem, "CEP"))
+ self.rem_ie = extract_text(self.rem, "IE")
+ self.rem_fone = format_phone(extract_text(self.rem, "fone"))
+ self.rem_uf = extract_text(self.rem, "UF")
+
+ # Inf Destinatario
+ self.dest_nome = extract_text(self.dest, "xNome")
+ self.dest_loga = extract_text(self.dest, "xLgr")
+ self.dest_nro = extract_text(self.dest, "nro")
+ self.dest_bairro = extract_text(self.dest, "xBairro")
+ self.dest_mun = extract_text(self.dest, "xMun")
+ self.dest_cnpj = format_cpf_cnpj(extract_text(self.dest, "CNPJ"))
+ self.dest_pais = extract_text(self.dest, "xPais")
+ self.dest_cep = format_cep(extract_text(self.dest, "CEP"))
+ self.dest_ie = extract_text(self.dest, "IE")
+ self.dest_fone = format_phone(extract_text(self.dest, "fone"))
+
+ # Inf Expedidor
+ self.exped_nome = extract_text(self.exped, "xNome")
+ self.exped_loga = extract_text(self.exped, "xLgr")
+ self.exped_nro = extract_text(self.exped, "nro")
+ self.exped_bairro = extract_text(self.exped, "xBairro")
+ self.exped_mun = extract_text(self.exped, "xMun")
+ self.exped_cnpj = format_cpf_cnpj(extract_text(self.exped, "CNPJ"))
+ self.exped_pais = extract_text(self.exped, "xPais")
+ self.exped_cep = format_cep(extract_text(self.exped, "CEP"))
+ self.exped_ie = extract_text(self.exped, "IE")
+ self.exped_fone = format_phone(extract_text(self.exped, "fone"))
+
+ # Inf Recebedor
+ self.receb_nome = extract_text(self.receb, "xNome")
+ self.receb_loga = extract_text(self.receb, "xLgr")
+ self.receb_nro = extract_text(self.receb, "nro")
+ self.receb_bairro = extract_text(self.receb, "xBairro")
+ self.receb_mun = extract_text(self.receb, "xMun")
+ self.receb_cnpj = format_cpf_cnpj(extract_text(self.receb, "CNPJ"))
+ self.receb_pais = extract_text(self.receb, "xPais")
+ self.receb_cep = format_cep(extract_text(self.receb, "CEP"))
+ self.receb_ie = extract_text(self.receb, "IE")
+ self.receb_fone = format_phone(extract_text(self.receb, "fone"))
+
+ x_margin = self.l_margin
+ y_margin = 75
+ page_width = 155
+
+ # TODO - TESTE
+ self.set_margins(
+ left=config.margins.left, top=config.margins.top, right=config.margins.right
+ )
+ margins_to_y = {
+ 2: y_margin + 10,
+ 3: y_margin + 11,
+ 4: y_margin + 12,
+ 5: y_margin + 13,
+ 6: y_margin + 14,
+ 7: y_margin + 15,
+ 8: y_margin + 16,
+ 9: y_margin + 17,
+ 10: y_margin + 18,
+ }
+ section_start_y = margins_to_y[config.margins.left]
+
+ self.rect(
+ x=x_margin, y=section_start_y, w=self.epw - 0.1 * x_margin, h=7, style=""
+ )
+ col_width = (page_width - x_margin) / 2
+ x_line_middle = x_margin + col_width + 20
+
+ self.line(
+ x1=x_line_middle,
+ x2=x_line_middle,
+ y1=section_start_y + 7,
+ y2=section_start_y,
+ )
+
+ self.set_font(self.default_font, "", 8)
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.multi_cell(w=0, h=0, text="INÍCIO DA PRESTAÇÃO", align="L")
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.set_font(self.default_font, "B", 8)
+ self.multi_cell(w=0, h=6, text=f"{self.mun_ini} - {self.est_inico}", align="L")
+
+ self.set_font(self.default_font, "", 8)
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.multi_cell(w=0, h=0, text="TÉRMINO DA PRESTAÇÃO", align="L")
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.set_font(self.default_font, "B", 8)
+ self.multi_cell(w=0, h=6, text=f"{self.mun_fim} - {self.est_fim}", align="L")
+
+ self.rect(
+ x=x_margin, y=section_start_y, w=self.epw - 0.1 * x_margin, h=24, style=""
+ )
+ col_width = (page_width - x_margin) / 2
+ x_line_middle = x_margin + col_width + 20
+ self.line(
+ x1=x_line_middle,
+ x2=x_line_middle,
+ y1=section_start_y + 48,
+ y2=section_start_y,
+ )
+
+ # Remetente
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.multi_cell(w=0, h=15, text="REMETENTE ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x=self.l_margin + 16, y=section_start_y + 2)
+ self.multi_cell(w=0, h=15, text=f"{self.rem_nome}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.multi_cell(
+ w=0,
+ h=21,
+ text="ENDEREÇO ",
+ align="L",
+ )
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x=self.l_margin + 16, y=section_start_y + 2)
+ self.multi_cell(
+ w=0,
+ h=21,
+ text=f"{self.rem_loga}, {self.rem_bairro}, {self.rem_nro}",
+ align="L",
+ )
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.multi_cell(w=0, h=28, text="MUNICÍPIO ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x=self.l_margin + 16, y=section_start_y + 2)
+ self.multi_cell(w=0, h=28, text=f"{self.rem_mun}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.multi_cell(w=0, h=35, text="CNPJ/CPF ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x=self.l_margin + 16, y=section_start_y + 2)
+ self.multi_cell(w=0, h=35, text=f"{self.rem_cnpj}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.multi_cell(w=0, h=41, text="PAÍS ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x=self.l_margin + 16, y=section_start_y + 2)
+ self.multi_cell(w=0, h=41, text=f"{self.rem_pais}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle - 25, section_start_y + 2)
+ self.multi_cell(w=0, h=25, text="CEP ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle - 18, section_start_y + 2)
+ if len(self.rem_cep.strip()) == 9:
+ self.multi_cell(w=0, h=25, text=f"{self.rem_cep}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle - 25, section_start_y + 2)
+ self.multi_cell(w=0, h=31, text="IE ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle - 20, section_start_y + 2)
+ self.multi_cell(w=0, h=31, text=f"{self.rem_ie}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle - 29, section_start_y + 2)
+ self.multi_cell(w=0, h=38, text="FONE ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle - 20, section_start_y + 2)
+ self.multi_cell(w=0, h=38, text=f"{self.rem_fone}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.multi_cell(w=0, h=15, text="DESTINATÁRIO ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 22, section_start_y + 2)
+ self.multi_cell(w=0, h=15, text=f"{self.dest_nome}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.multi_cell(
+ w=0,
+ h=21,
+ text="ENDEREÇO ",
+ align="L",
+ )
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 22, section_start_y + 2)
+ self.multi_cell(
+ w=0,
+ h=21,
+ text=f"{self.dest_loga}, {self.dest_bairro}, {self.dest_nro}",
+ align="L",
+ )
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.multi_cell(w=0, h=28, text="MUNICÍPIO ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 22, section_start_y + 2)
+ self.multi_cell(w=0, h=28, text=f"{self.dest_mun}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.multi_cell(w=0, h=35, text="CNPJ/CPF ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 22, section_start_y + 2)
+ self.multi_cell(w=0, h=35, text=f"{self.dest_cnpj}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.multi_cell(w=0, h=41, text="PAÍS ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 22, section_start_y + 2)
+ self.multi_cell(w=0, h=41, text=f"{self.dest_pais}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle + 70, section_start_y + 2)
+ self.multi_cell(w=0, h=25, text="CEP ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 77, section_start_y + 2)
+ if len(self.dest_cep.strip()) == 9:
+ self.multi_cell(w=0, h=25, text=f"{self.dest_cep}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle + 70, section_start_y + 2)
+ self.multi_cell(w=0, h=31, text="IE ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 75, section_start_y + 2)
+ self.multi_cell(w=0, h=31, text=f"{self.dest_ie}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle + 60, section_start_y + 2)
+ self.multi_cell(w=0, h=38, text="FONE ", align="L")
+
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 67, section_start_y + 2)
+ self.multi_cell(w=0, h=38, text=f"{self.dest_fone}", align="L")
+
+ section_start_y += 24
+
+ self.rect(
+ x=x_margin, y=section_start_y, w=self.epw - 0.1 * x_margin, h=24, style=""
+ )
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.multi_cell(w=0, h=3, text="RECEBEDOR", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 20, section_start_y + 2)
+ self.multi_cell(w=0, h=3, text=f"{self.receb_nome}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.multi_cell(w=0, h=10, text="ENDEREÇO", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 20, section_start_y + 2)
+ self.multi_cell(
+ w=0,
+ h=10.6,
+ text=f"{self.receb_loga} {self.receb_bairro} {self.receb_nro}",
+ align="L",
+ )
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.multi_cell(w=0, h=17, text="MUNICÍPIO", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 20, section_start_y + 2)
+ self.multi_cell(w=0, h=18.2, text=f"{self.receb_mun}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.multi_cell(w=0, h=25, text="CNPJ/CPF", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 20, section_start_y + 2)
+ self.multi_cell(w=0, h=25.8, text=f"{self.receb_cnpj}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle, section_start_y + 2)
+ self.multi_cell(w=0, h=32, text="PAÍS", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 20, section_start_y + 2)
+ self.multi_cell(w=0, h=33.4, text=f"{self.receb_pais}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle + 70, section_start_y + 2)
+ self.multi_cell(w=0, h=20, text="CEP", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 77, section_start_y + 2)
+ if len(self.receb_cep.strip()) == 9:
+ self.multi_cell(w=0, h=20, text=f"{self.receb_cep}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle + 70, section_start_y + 2)
+ self.multi_cell(w=0, h=27, text="IE", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 75, section_start_y + 2)
+ self.multi_cell(w=0, h=26.6, text=f"{self.receb_ie}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle + 60, section_start_y + 2)
+ self.multi_cell(w=0, h=34, text="FONE", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle + 67, section_start_y + 2)
+ self.multi_cell(w=0, h=34, text=f"{self.receb_fone}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.multi_cell(w=0, h=3, text="EXPEDIDOR", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x=self.l_margin + 16, y=section_start_y + 2)
+ self.multi_cell(w=0, h=3, text=f"{self.exped_nome}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.multi_cell(w=0, h=10, text="ENDEREÇO", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x=self.l_margin + 16, y=section_start_y + 2)
+ self.multi_cell(
+ w=0,
+ h=10.6,
+ text=f"{self.exped_loga} {self.exped_bairro} {self.exped_nro}",
+ align="L",
+ )
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.multi_cell(w=0, h=17, text="MUNICÍPIO", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x=self.l_margin + 16, y=section_start_y + 2)
+ self.multi_cell(w=0, h=18.2, text=f"{self.exped_mun}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.multi_cell(w=0, h=25, text="CNPJ/CPF", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x=self.l_margin + 16, y=section_start_y + 2)
+ self.multi_cell(w=0, h=25.8, text=f"{self.exped_cnpj}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x=self.l_margin, y=section_start_y + 2)
+ self.multi_cell(w=0, h=32, text="PAÍS", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x=self.l_margin + 16, y=section_start_y + 2)
+ self.multi_cell(w=0, h=33.4, text=f"{self.exped_pais}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle - 25, section_start_y + 2)
+ self.multi_cell(w=0, h=20, text="CEP", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle - 18, section_start_y + 2)
+ if len(self.exped_cep.strip()) == 9:
+ self.multi_cell(w=0, h=20, text=f"{self.exped_cep}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle - 25, section_start_y + 2)
+ self.multi_cell(w=0, h=27, text="IE", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle - 20, section_start_y + 2)
+ self.multi_cell(w=0, h=27, text=f"{self.exped_ie}", align="L")
+
+ self.set_font(self.default_font, "", 7)
+ self.set_xy(x_line_middle - 29, section_start_y + 2)
+ self.multi_cell(w=0, h=34, text="FONE", align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_line_middle - 20, section_start_y + 2)
+ self.multi_cell(w=0, h=34, text=f"{self.exped_fone}", align="L")
+
+ def _draw_service_recipient(self, config):
+ self.inf_carga_nome = extract_text(self.inf_carga, "proPred")
+ self.inf_carga_car = extract_text(self.inf_carga, "xOutCat")
+ self.inf_carga_valor = format_number(
+ extract_text(self.inf_carga, "vCarga"), precision=2
+ )
+ self.inf_carga_peso = format_number(
+ extract_text(self.inf_carga, "qCarga"), precision=2
+ )
+
+ self.inf_unid = extract_text(self.inf_carga, "cUnid")
+
+ self.inf_carga_q = extract_text(self.inf_carga, "qCarga")
+
+ x_margin = self.l_margin
+ y_margin = 123
+ page_width = self.epw
+
+ # TODO - TESTE
+ self.set_margins(
+ left=config.margins.left, top=config.margins.top, right=config.margins.right
+ )
+ margins_to_y = {
+ 2: y_margin + 10,
+ 3: y_margin + 11,
+ 4: y_margin + 12,
+ 5: y_margin + 13,
+ 6: y_margin + 14,
+ 7: y_margin + 15,
+ 8: y_margin + 16,
+ 9: y_margin + 17,
+ 10: y_margin + 18,
+ }
+ section_start_y = margins_to_y[config.margins.left]
+
+ self.rect(
+ x=x_margin, y=section_start_y, w=page_width - 0.1 * x_margin, h=10, style=""
+ )
+
+ self.set_font(self.default_font, "", 7.6)
+ self.set_xy(x_margin, section_start_y)
+ self.multi_cell(w=0, h=4, text="TOMADOR DO SERVIÇO ", align="L")
+
+ self.set_font(self.default_font, "B", 7.6)
+ self.set_xy(x_margin + 32, section_start_y)
+ self.multi_cell(w=0, h=4, text=f"{self.rem_nome}", align="L")
+
+ self.set_font(self.default_font, "", 7.6)
+ self.set_xy(x_margin, section_start_y)
+ self.multi_cell(w=0, h=10, text="ENDEREÇO ", align="L")
+
+ self.set_font(self.default_font, "B", 7.6)
+ self.set_xy(x_margin + 16, section_start_y)
+ self.multi_cell(
+ w=0,
+ h=10,
+ text=f"{self.rem_loga} {self.rem_nro} {self.rem_bairro}",
+ align="L",
+ )
+
+ self.set_font(self.default_font, "", 7.6)
+ self.set_xy(x_margin, section_start_y)
+ self.multi_cell(w=0, h=16, text="CNPJ/CPF ", align="L")
+
+ self.set_font(self.default_font, "B", 7.6)
+ self.set_xy(x_margin + 14, section_start_y)
+ self.multi_cell(w=0, h=16, text=f"{self.rem_cnpj}", align="L")
+
+ self.set_font(self.default_font, "", 7.6)
+ self.set_xy(x_margin + 85, section_start_y)
+ self.multi_cell(w=0, h=16, text="IE ", align="L")
+
+ self.set_font(self.default_font, "B", 7.6)
+ self.set_xy(x_margin + 89, section_start_y)
+ self.multi_cell(w=0, h=16, text=f"{self.rem_ie}", align="L")
+
+ self.set_font(self.default_font, "", 7.6)
+ self.set_xy(x_margin + 115, section_start_y)
+ self.multi_cell(w=0, h=16, text="PAÍS ", align="L")
+
+ self.set_font(self.default_font, "B", 7.6)
+ self.set_xy(x_margin + 122, section_start_y)
+ self.multi_cell(w=0, h=16, text=f"{self.rem_pais}", align="L")
+
+ self.set_font(self.default_font, "", 7.6)
+ self.set_xy(x_margin + 150, section_start_y)
+ self.multi_cell(w=0, h=16, text="FONE ", align="L")
+
+ self.set_font(self.default_font, "B", 7.6)
+ self.set_xy(x_margin + 158, section_start_y)
+ self.multi_cell(w=0, h=16, text=f"{self.rem_fone}", align="L")
+
+ self.set_font(self.default_font, "", 7.6)
+ self.set_xy(x_margin + 100, section_start_y)
+ self.multi_cell(w=0, h=4, text="MUNICÍPIO ", align="L")
+
+ self.set_font(self.default_font, "B", 7.6)
+ self.set_xy(x_margin + 116, section_start_y)
+ self.multi_cell(w=0, h=4, text=f"{self.rem_mun}", align="L")
+
+ self.set_font(self.default_font, "", 7.6)
+ self.set_xy(x_margin + 150, section_start_y)
+ self.multi_cell(w=0, h=4, text="UF ", align="L")
+
+ self.set_font(self.default_font, "B", 7.6)
+ self.set_xy(x_margin + 154, section_start_y)
+ self.multi_cell(w=0, h=4, text=f"{self.rem_uf}", align="L")
+
+ self.set_font(self.default_font, "", 7.6)
+ self.set_xy(x_margin + 160, section_start_y)
+ self.multi_cell(w=0, h=4, text="CEP ", align="L")
+
+ self.set_font(self.default_font, "B", 7.6)
+ self.set_xy(x_margin + 166, section_start_y)
+ self.multi_cell(w=0, h=4, text=f"{self.rem_cep}", align="L")
+
+ section_start_y += 10
+
+ self.rect(
+ x=x_margin, y=section_start_y, w=page_width - 0.1 * x_margin, h=10, style=""
+ )
+ col_width = (page_width - (x_margin - 20)) / 2
+ x_line_1 = x_margin - 53 + col_width
+ x_line_2 = (x_margin - 53 + 2 * col_width) - 10
+ x_line_3 = x_margin - 53 + 3 * col_width
+ self.line(
+ x1=x_line_1 + 30,
+ x2=x_line_1 + 30,
+ y1=section_start_y,
+ y2=section_start_y + 10,
+ )
+ self.line(x1=x_line_2, x2=x_line_2, y1=section_start_y, y2=section_start_y + 10)
+ self.line(x1=x_line_3, x2=x_line_3, y1=section_start_y, y2=section_start_y + 10)
+
+ self.set_xy(x_margin, section_start_y)
+ self.set_font(self.default_font, "", 7)
+ self.multi_cell(w=0, h=4, text="PRODUTO PREDOMINANTE", align="L")
+ self.set_xy(x_margin, section_start_y)
+ self.set_font(self.default_font, "B", 7)
+ self.multi_cell(w=0, h=14, text=self.inf_carga_nome, align="L")
+
+ self.set_xy(x_line_1 + 30, section_start_y)
+ self.set_font(self.default_font, "", 7)
+ self.multi_cell(w=0, h=4, text="OUTRAS CARACTERÍSTICAS DA CARGA", align="L")
+ self.set_xy(x_line_1 + 30, section_start_y)
+ self.set_font(self.default_font, "B", 7)
+ self.multi_cell(w=0, h=14, text=self.inf_carga_car, align="L")
+
+ self.set_xy(x_line_2, section_start_y)
+ self.set_font(self.default_font, "", 7)
+ self.multi_cell(w=0, h=4, text="VALOR TOTAL DA MERCADORIA", align="L")
+ self.set_xy(x_line_2, section_start_y)
+ self.set_font(self.default_font, "B", 7)
+ self.multi_cell(w=0, h=14, text=self.inf_carga_valor, align="L")
+
+ section_start_y += 10
+
+ self.rect(
+ x=x_margin, y=section_start_y, w=page_width - 0.1 * x_margin, h=10, style=""
+ )
+ col_width = (page_width - (x_margin + 2)) / 4
+ x_line_1 = x_margin - 55 + col_width
+ x_line_2 = x_margin - 30 + 2 * col_width
+ x_line_3 = x_margin - 40 + 3 * col_width
+ x_line_4 = x_margin - 40 + 4 * col_width
+ self.line(
+ x1=x_line_1 + 34,
+ x2=x_line_1 + 34,
+ y1=section_start_y,
+ y2=section_start_y + 10,
+ )
+ self.line(x1=x_line_2, x2=x_line_2, y1=section_start_y, y2=section_start_y + 10)
+ self.line(x1=x_line_3, x2=x_line_3, y1=section_start_y, y2=section_start_y + 10)
+ self.line(x1=x_line_4, x2=x_line_4, y1=section_start_y, y2=section_start_y + 10)
+
+ # TODO TESTE
+ x_positions = [0, x_line_1 + 34, x_line_2, x_line_3, x_line_4]
+ titles = [
+ "PESO (Kg)",
+ "TP MED /UN. MED",
+ "TP MED /UN. MED",
+ "CUBAGEM (M³)",
+ "QUANTIDADE DE VOLUMES",
+ ]
+
+ for i, title in enumerate(titles):
+ self.set_xy(self.l_margin + x_positions[i], section_start_y)
+ self.set_font(self.default_font, "", 7.6)
+ self.multi_cell(w=0, h=4, text=title, align="L")
+ section_start_y += 5
+ for item in self.inf_carga_list:
+ c_unid, tp_media, q_carga = item
+ if c_unid == "01" and q_carga > "0":
+ value_index = 0
+ elif c_unid == "03" and tp_media.strip().upper() == "PARES":
+ value_index = 1
+ elif c_unid == "00" and q_carga > "0":
+ value_index = 2
+ elif c_unid == "00" and tp_media in ["M3", "m3"]:
+ value_index = 3
+ elif c_unid == "03" and q_carga > "0":
+ value_index = 4
+ else:
+ continue
+
+ self.set_xy(self.l_margin + x_positions[value_index], section_start_y)
+ self.set_font(self.default_font, "B", 7)
+ self.multi_cell(
+ w=0, h=4, text=f"{q_carga} {tp_codigo_medida[c_unid]}", align="L"
+ )
+
+ def draw_section(self, y, height, text, align="C"):
+ self.rect(x=self.l_margin, y=y, w=self.epw - 0.1 * self.l_margin, h=3, style="")
+ self.set_xy(x=self.l_margin, y=y + 3)
+ self.cell(w=self.epw - 2 * self.l_margin, h=-3, text=text, align=align)
+ return y + height
+
+ def _draw_service_fee_value(self):
+ x_margin = self.l_margin
+ y_margin = self.y
+ page_width = self.epw
+ self.cst = extract_text(self.imp, "CST")
+ self.vbc = format_number(extract_text(self.imp, "vBC"), precision=2)
+ self.p_icms = format_number(extract_text(self.imp, "pICMS"), precision=2)
+ self.v_icms = format_number(extract_text(self.imp, "vICMS"), precision=2)
+ self.v_icms_st = format_number(extract_text(self.imp, "vICMS"), precision=2)
+ self.p_red_bc = format_number(extract_text(self.imp, "pRedBC"), precision=2)
+ self.rntrc = extract_text(self.inf_modal, "RNTRC")
+ self.x_obs = extract_text(self.compl, "compl")
+ self.v_tpprest = format_number(
+ extract_text(self.v_prest, "vTPrest"), precision=2
+ )
+ self.v_rec = format_number(extract_text(self.v_prest, "vRec"), precision=2)
+
+ section_start_y = y_margin + 1
+
+ self.set_font(self.default_font, "", 6.5)
+ section_start_y = self.draw_section(
+ section_start_y, 3, "COMPONENTES DO VALOR DA PRESTAÇÃO DO SERVIÇO"
+ )
+ self.rect(
+ x=x_margin, y=section_start_y, w=page_width - 0.1 * x_margin, h=18, style=""
+ )
+
+ col_width = (page_width - 2 * x_margin) / 4
+ for i in range(1, 4):
+ x_line = x_margin + i * col_width
+ self.line(x1=x_line, x2=x_line, y1=section_start_y, y2=section_start_y + 18)
+
+ self.set_font(self.default_font, "", 8)
+ titles = ["NOME", "VALOR"]
+ for col in range(
+ 2
+ ): # TODO Arrumar a sopreposição do title: NOME E VALOR SOBRE o FPESO
+ self.set_xy(x_margin + col * col_width, section_start_y - 6)
+ self.multi_cell(w=col_width / 2, h=16, text=titles[0], align="L")
+ self.set_xy(x_margin + col * col_width + col_width / 2, section_start_y - 6)
+ self.multi_cell(w=col_width / 2, h=16, text=titles[1], align="L")
+
+ for row, (xNome, vComp) in enumerate(self.comp_list):
+ col = row % 3
+ actual_row = row // 3
+ self.set_xy(x_margin + col * col_width, section_start_y + (actual_row * 6))
+ self.multi_cell(w=col_width / 2, h=4, text=xNome, align="L")
+ self.set_xy(
+ x_margin + col * col_width + col_width / 2,
+ section_start_y + (actual_row * 6),
+ )
+ self.set_font(self.default_font, "B", 8)
+ self.multi_cell(w=col_width / 2, h=4, text=vComp, align="L")
+ self.set_font(self.default_font, "", 8)
+
+ self.set_font(self.default_font, "", 8)
+ self.set_xy(x_margin + 3 * col_width, section_start_y)
+ self.multi_cell(w=col_width, h=4, text="VALOR TOTAL DO SERVIÇO", align="L")
+ self.set_font(self.default_font, "B", 8)
+ self.set_xy(x_margin + 3 * col_width, section_start_y + 4)
+ self.multi_cell(w=col_width, h=4, text=self.v_tpprest, align="L")
+
+ self.line(
+ x1=x_margin + 3 * col_width,
+ x2=self.w - self.r_margin - 1,
+ y1=section_start_y + 10,
+ y2=section_start_y + 10,
+ )
+
+ self.set_font(self.default_font, "", 8)
+ self.set_xy(x_margin + 3 * col_width, section_start_y + 9)
+ self.multi_cell(w=col_width, h=8, text="VALOR TOTAL A RECEBER", align="L")
+ self.set_font(self.default_font, "B", 8)
+ self.set_xy(x_margin + 3 * col_width, section_start_y + 13)
+ self.multi_cell(w=col_width, h=7, text=self.v_rec, align="L")
+
+ section_start_y += 18
+
+ self.set_font(self.default_font, "", 6.5)
+ section_start_y = self.draw_section(
+ section_start_y, 18, "INFORMAÇÕES RELATIVAS AO IMPOSTO"
+ )
+ self.rect(
+ x=x_margin,
+ y=section_start_y - 15,
+ w=page_width - 0.1 * x_margin,
+ h=15,
+ style="",
+ )
+
+ col_width = (page_width - 2 * x_margin) / 6
+ for i in range(1, 6):
+ x_line = x_margin + i * col_width
+ self.line(x1=x_line, x2=x_line, y1=section_start_y - 15, y2=section_start_y)
+
+ tax_titles = [
+ "SITUAÇÃO TRIBUTÁRIA",
+ "BASE DE CALCULO",
+ "ALÍQ ICMS",
+ "VALOR ICMS",
+ "% RED. BC ICMS",
+ "ICMS ST",
+ ]
+ tax_values = [
+ f"{self.cst} - TRIBUTAÇÃO NORMAL DO ICMS",
+ f"{self.vbc}",
+ f"{self.p_icms}",
+ f"{self.v_icms}",
+ f"{self.p_red_bc}",
+ f"{self.v_icms_st}",
+ ]
+
+ for i, (title, value) in enumerate(zip(tax_titles, tax_values)):
+ self.set_xy(x_margin + i * col_width, section_start_y - 15)
+ self.multi_cell(w=col_width, h=4, text=title, align="L")
+ self.set_font(self.default_font, "B", 6)
+ self.set_xy(x_margin + i * col_width, section_start_y - 11)
+ self.multi_cell(w=col_width, h=4, text=value, align="L")
+ self.set_font(self.default_font, "", 6)
+
+ def _draw_documents_obs(self):
+ x_margin = self.l_margin
+ page_width = self.epw
+ self.set_font(self.default_font, "", 7)
+ section_start_y = self.get_y() + 7
+ section_start_y = self.draw_section(
+ section_start_y, 43, "DOCUMENTOS ORIGINÁRIOS"
+ )
+ self.rect(
+ x=x_margin,
+ y=section_start_y - 40,
+ w=page_width - 0.1 * x_margin,
+ h=40,
+ style="",
+ )
+ col_width = (page_width - 2 * x_margin) / 2
+ half_col_width = col_width / 3
+ x_line_middle = x_margin + col_width
+
+ self.line(
+ x1=x_line_middle,
+ x2=x_line_middle,
+ y1=section_start_y - 40,
+ y2=section_start_y,
+ )
+
+ self.set_font(self.default_font, "", 6)
+ self.set_xy(x_margin, section_start_y - 37)
+ self.multi_cell(w=half_col_width, h=0, text="TIPO DOC", align="L")
+ self.set_xy(x_margin + half_col_width - 18, section_start_y - 37)
+ self.multi_cell(w=half_col_width, h=0, text="CNPJ/CHAVE", align="L")
+ self.set_xy(x_margin + 2 * half_col_width, section_start_y - 37)
+ self.set_font(self.default_font, "", 5.5)
+ self.multi_cell(w=half_col_width, h=0, text="SÉRIE/NRO. DOCUMENTO", align="L")
+
+ self.set_font(self.default_font, "", 6)
+ self.set_xy(x_line_middle, section_start_y - 37)
+ self.multi_cell(w=half_col_width, h=0, text="TIPO DOC", align="L")
+ self.set_xy(x_line_middle + half_col_width - 20, section_start_y - 37)
+ self.multi_cell(w=half_col_width, h=0, text="CNPJ/CHAVE", align="L")
+ self.set_xy(x_line_middle + 2 * half_col_width, section_start_y - 37)
+ self.set_font(self.default_font, "", 5.5)
+ self.multi_cell(w=half_col_width, h=0, text="SÉRIE/NRO. DOCUMENTO", align="L")
+
+ y_offset_left = section_start_y - 33
+ y_offset_right = section_start_y - 33
+ lines_per_block = 7
+ self.max_lines_per_page = 14
+ current_line_left = 0
+ current_line_right = 0
+ in_right_block = False
+
+ for index, chave in enumerate(self.inf_doc_list):
+ self.page_lines = index
+ if self.page_lines >= self.max_lines_per_page:
+ break
+
+ if current_line_left == lines_per_block:
+ current_line_left = 0
+ in_right_block = True
+ self.set_xy(x_line_middle, y_offset_right)
+
+ if in_right_block:
+ x_start = x_line_middle
+ y_offset = y_offset_right
+ else:
+ x_start = x_margin
+ y_offset = y_offset_left
+
+ self.set_xy(x_start, y_offset)
+ self.set_font(self.default_font, "B", 6)
+ self.multi_cell(w=half_col_width, h=4, text="NFE", align="L")
+
+ self.set_xy(x_start + half_col_width - 20, y_offset)
+ self.multi_cell(w=half_col_width + 23, h=4, text=chave, align="L")
+
+ key_nfe_1 = chave[22:25]
+ key_nfe_2 = chave[25:34]
+ key_nfe_format = f"{key_nfe_1}/{key_nfe_2}"
+
+ self.set_font(self.default_font, "B", 6)
+ self.set_xy(x_start + 2 * half_col_width + 5, y_offset)
+ self.multi_cell(w=half_col_width, h=4, text=key_nfe_format, align="L")
+
+ y_offset += 5
+ if in_right_block:
+ current_line_right += 1
+ y_offset_right = y_offset
+ else:
+ current_line_left += 1
+ y_offset_left = y_offset
+
+ if (
+ not in_right_block
+ and current_line_left == 0
+ and self.page_lines == lines_per_block
+ ):
+ y_offset_right = section_start_y - 33
+
+ self.set_font(self.default_font, "", 7)
+ text_width = page_width - 0.1 * x_margin
+ max_characters = 350
+ combined_obs = " ".join(self.obs_dacte_list)
+ section_start_y = self.draw_section(section_start_y, 18, "OBSERVAÇÕES")
+ initial_y = section_start_y - 15
+
+ self.set_xy(x_margin, initial_y)
+ text_to_draw = combined_obs[:max_characters]
+ self.remaining_text = combined_obs[max_characters:]
+ self.text_exceeds_limit = len(combined_obs) > max_characters
+
+ self.multi_cell(w=text_width, h=3, text=text_to_draw, align="L")
+ calculated_height = self.get_y() - initial_y
+
+ rectangle_height = max(calculated_height, 10)
+ self.set_xy(x_margin, initial_y)
+ self.rect(x=x_margin, y=initial_y, w=text_width, h=rectangle_height)
+
+ def _draw_specific_data(self, config):
+ x_margin = self.l_margin
+ page_width = self.epw
+ section_start_y = self.get_y() + 7
+ section_start_y = self.draw_section(
+ section_start_y,
+ 13,
+ "DADOS ESPECÍFICOS DO MODAL RODOVIÁRIO - CARGA FRACIONADA",
+ )
+ self.rect(
+ x=x_margin,
+ y=section_start_y - 10,
+ w=page_width - 0.1 * x_margin,
+ h=10,
+ style="",
+ )
+
+ col_width = (page_width - 2 * x_margin) / 4
+ for i in range(1, 4):
+ x_line = x_margin + i * col_width
+ self.line(x1=x_line, x2=x_line, y1=section_start_y - 10, y2=section_start_y)
+
+ self.set_font(self.default_font, "", 7)
+ road_titles = [
+ "RNTRC DA EMPRESA",
+ "CIOT",
+ "DATA PREVISTA DE ENTREGA",
+ "ESTE CONHECIMENTO DE TRANSPORTE ATENDE"
+ "À LEGISLAÇÃO DE TRANSPORTE RODOVIÁRIO EM VIGOR",
+ ]
+
+ road_values = [
+ f"{self.rntrc}",
+ "",
+ "",
+ "",
+ ]
+
+ for i, (title, value) in enumerate(zip(road_titles, road_values)):
+ self.set_xy(x_margin + i * col_width, section_start_y - 10)
+ self.multi_cell(w=col_width, h=3, text=title, align="L")
+ self.set_font(self.default_font, "B", 7)
+ self.set_xy(x_margin + i * col_width, section_start_y - 7)
+ self.multi_cell(w=col_width, h=3, text=value, align="L")
+ self.set_font(self.default_font, "", 6)
+
+ self.set_font(self.default_font, "", 7)
+ section_start_y = self.draw_section(
+ section_start_y, 18, "USO EXCLUSIVO DO EMISSOR DO CT-E"
+ )
+ # TODO TESTE
+ self.set_margins(
+ left=config.margins.left, top=config.margins.top, right=config.margins.right
+ )
+ margins_to_height = {
+ 2: 13,
+ 3: 9,
+ 4: 10,
+ 5: 11,
+ 6: 11,
+ 7: 10,
+ 8: 9,
+ 9: 8,
+ 10: 7,
+ }
+ rect_height = margins_to_height[config.margins.left]
+
+ self.rect(
+ x=x_margin,
+ y=section_start_y - 15,
+ w=page_width - 0.1 * x_margin,
+ h=rect_height,
+ style="",
+ )
+
+ # Adicionando outra pag
+ def _add_new_page(self):
+ x_margin = self.l_margin
+ page_width = self.epw
+ line_height = 4
+ if self.page_lines > 0 and self.page_lines % self.max_lines_per_page == 0:
+ self.add_page(orientation=self.orientation)
+ self._draw_receipt()
+ self._draw_header()
+
+ section_start_y = self.get_y() - 1
+ section_start_y = self.draw_section(
+ section_start_y, 43, "DOCUMENTOS ORIGINÁRIOS"
+ )
+ y_offset_left = section_start_y - 33
+ y_offset_right = section_start_y - 33
+ current_line_left = 0
+ current_line_right = 0
+ in_right_block = False
+
+ self.set_font(self.default_font, "", 7)
+ col_width = (page_width - 2 * x_margin) / 2
+ half_col_width = col_width / 3
+ x_line_middle = x_margin + col_width
+
+ total_documents = len(self.inf_doc_list) - self.page_lines
+ lines_per_column = (total_documents + 1) // 2
+ rectangle_height = total_documents * line_height // 2
+ self.rect(
+ x=x_margin,
+ y=section_start_y - 40,
+ w=page_width - 0.1 * x_margin,
+ h=rectangle_height + 8,
+ )
+ self.line(
+ x1=x_line_middle,
+ x2=x_line_middle,
+ y1=section_start_y - 40,
+ y2=section_start_y - 32 + rectangle_height,
+ )
+
+ self.set_font(self.default_font, "", 6)
+ self.set_xy(x_margin, section_start_y - 37)
+ self.multi_cell(w=half_col_width, h=0, text="TIPO DOC", align="L")
+ self.set_xy(x_margin + half_col_width - 18, section_start_y - 37)
+ self.multi_cell(w=half_col_width, h=0, text="CNPJ/CHAVE", align="L")
+ self.set_xy(x_margin + 2 * half_col_width, section_start_y - 37)
+ self.set_font(self.default_font, "", 5.5)
+ self.multi_cell(
+ w=half_col_width, h=0, text="SÉRIE/NRO. DOCUMENTO", align="L"
+ )
+
+ self.set_font(self.default_font, "", 6)
+ self.set_xy(x_line_middle, section_start_y - 37)
+ self.multi_cell(w=half_col_width, h=0, text="TIPO DOC", align="L")
+ self.set_xy(x_line_middle + half_col_width - 20, section_start_y - 37)
+ self.multi_cell(w=half_col_width, h=0, text="CNPJ/CHAVE", align="L")
+ self.set_xy(x_line_middle + 2 * half_col_width, section_start_y - 37)
+ self.set_font(self.default_font, "", 5.5)
+ self.multi_cell(
+ w=half_col_width, h=0, text="SÉRIE/NRO. DOCUMENTO", align="L"
+ )
+
+ for i, chave in enumerate(self.inf_doc_list):
+ if i < self.page_lines:
+ continue
+
+ if current_line_left == lines_per_column:
+ current_line_left = 0
+ in_right_block = True
+ self.set_xy(x_line_middle, y_offset_right)
+
+ if in_right_block:
+ x_start = x_line_middle
+ y_offset = y_offset_right
+ else:
+ x_start = x_margin
+ y_offset = y_offset_left
+
+ self.set_xy(x_start, y_offset)
+ self.set_font(self.default_font, "B", 6)
+ self.multi_cell(w=half_col_width, h=line_height, text="NFE", align="L")
+
+ self.set_xy(x_start + half_col_width - 20, y_offset)
+ self.multi_cell(
+ w=half_col_width + 23, h=line_height, text=chave, align="L"
+ )
+
+ key_nfe_1 = chave[22:25]
+ key_nfe_2 = chave[25:34]
+ key_nfe_format = f"{key_nfe_1}/{key_nfe_2}"
+
+ self.set_font(self.default_font, "B", 6)
+ self.set_xy(x_start + 2 * half_col_width + 5, y_offset)
+ self.multi_cell(
+ w=half_col_width, h=line_height, text=key_nfe_format, align="L"
+ )
+
+ y_offset += line_height
+ if in_right_block:
+ current_line_right += 1
+ y_offset_right = y_offset
+ else:
+ current_line_left += 1
+ y_offset_left = y_offset
+ if self.text_exceeds_limit:
+ section_start_y = self.get_y() + 1
+ self.set_font(self.default_font, "", 7)
+ text_width = page_width - 0.1 * x_margin
+ section_start_y = self.draw_section(section_start_y, 18, "OBSERVAÇÕES")
+ initial_y = section_start_y - 15
+
+ self.set_xy(x_margin, initial_y)
+
+ self.multi_cell(w=text_width, h=3, text=self.remaining_text, align="L")
+
+ self.set_xy(x_margin, initial_y)
+ self.rect(
+ x=x_margin, y=initial_y, w=text_width, h=self.eph - section_start_y
+ )
diff --git a/brazilfiscalreport/dacte/dacte_conf.py b/brazilfiscalreport/dacte/dacte_conf.py
new file mode 100644
index 0000000..085c881
--- /dev/null
+++ b/brazilfiscalreport/dacte/dacte_conf.py
@@ -0,0 +1 @@
+URL = ".//{http://www.portalfiscal.inf.br/cte}"
diff --git a/tests/fixtures/dacte_test_1.xml b/tests/fixtures/dacte_test_1.xml
new file mode 100644
index 0000000..71ffa9e
--- /dev/null
+++ b/tests/fixtures/dacte_test_1.xml
@@ -0,0 +1,204 @@
+
+
+
+
+
+ 01
+ 0111111
+ 6353
+ PRESTACAO DE SERVICO
+ 57
+ 2
+ 99203223
+ 2024-03-27T00:00:00-03:00
+ 1
+ 1
+ 3
+ 1
+ 0
+ 0
+ 4.0
+ 4216305
+ SAO JOAO BATISTA
+ SC
+ 01
+ 0
+ 4216305
+ SAO JOAO BATISTA
+ SC
+ 3556453
+ VARGEM GRANDE PAULISTA
+ SP
+ 1
+ 1
+
+ 0
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+
+ O valor aproximado de tributos incidentes sobre o preco deste servico e de R$ 56,40
+
+
+
+ 92382301841312
+ 232412442
+ FANTASMA TRANSPORTES LTDA
+ FANTASMA TRANSPORTES LTDA
+
+ RUA TESTE FANTASMA
+ 0099
+ FANTASMA
+ 4216305
+ SAO JOAO BATISTA
+ 88240000
+ SC
+ 489999009
+
+ 1
+
+
+ 23924902942209
+ 224242421
+ INDUSTRIA FANTASMA DE TESTE
+ INDUSTRIA FANTASMA DE TESTE
+ 47232332239
+
+ RODOVIA SC FANTASMA
+ 122
+ TESTE
+ 4216305
+ SAO JOAO BATISTA
+ 88240000
+ SC
+ 1058
+ BRASIL
+
+
+
+ 02394820392092
+ 224242421
+ EMPRESA DE TESTE
+ 23424242442
+
+ TESTE L.A
+ 21
+ AO LADO DE EMPRESA FANTASMA
+ TESTE
+ 3556453
+ VARGEM GRANDE PAULISTA
+ 06730000
+ SP
+ 1058
+ BRASIL
+
+
+
+ 221.19
+ 221.19
+
+ FPESO
+ 117.78
+
+
+ FVALOR
+ 4.24
+
+
+ PEDAGIO
+ 13.11
+
+
+ TAXAS
+ 79.64
+
+
+ GRIS
+ 3.15
+
+
+ TAS
+ 3.27
+
+
+
+
+
+ 00
+ 221.19
+ 12.00
+ 26.54
+
+
+ 56.40
+
+
+
+ 3016.92
+ FITA GOMADA SEMI-KRAFT 80MM C/REFORCO CADEADO
+
+ 03
+ UNIDADE
+ 14
+
+
+ 03
+ PARES
+ 0
+
+
+ 00
+ M3
+ 0.0000
+
+
+ 01
+ PESO REAL
+ 209.8000
+
+
+ 01
+ PESO BASE DE CALCULO
+ 209.8000
+
+ 3016.92
+
+
+
+ 02394910232349239234934138491040891884149930
+
+
+
+
+ 23920392
+
+
+
+
+
+
+ https://dfe-portal.svrs.rs.gov.br/cte/qrCode?chCTe=42240302484555000343570020003396861081562222&tpAmb=1
+
+
+
+
+
+ 1
+ RS20240222155212
+ 4224030248244242523254224232424242
+ 2024-03-27T20:17:16-03:00
+ 2324240111249023
+ UCtDCEsja/Ljq5GjpftJHAK3oHI=
+ 100
+ Autorizado o uso do CT-e
+
+
+
diff --git a/tests/fixtures/dacte_test_overload.xml b/tests/fixtures/dacte_test_overload.xml
new file mode 100644
index 0000000..9baa70d
--- /dev/null
+++ b/tests/fixtures/dacte_test_overload.xml
@@ -0,0 +1,311 @@
+
+
+
+
+
+ 01
+ 0111111
+ 6353
+ PRESTACAO DE SERVICO
+ 57
+ 2
+ 99203223
+ 2024-03-27T00:00:00-03:00
+ 1
+ 1
+ 3
+ 1
+ 0
+ 0
+ 4.0
+ 4216305
+ SAO JOAO BATISTA
+ SC
+ 01
+ 0
+ 4216305
+ SAO JOAO BATISTA
+ SC
+ 3556453
+ VARGEM GRANDE PAULISTA
+ SP
+ 1
+ 1
+
+ 0
+
+
+
+
+
+ 2
+ 2024-07-18
+
+
+ 0
+
+
+ Texto fictício para teste: Informação exemplo 1 - Seguradora: 12345678901234
+
+ Texto fictício para teste: Informação exemplo 2 - Seguradora: 12345678901234
+
+
+ Texto fictício para teste: Informação exemplo 3
+
+
+ Texto fictício para teste: Informação exemplo 4 - ROTA: ABCD/EFGH - TARIF: 123 - TIPO
+ MERCAD: EXEMPLO
+
+
+ Texto fictício para teste: Tratamento de dados exemplo (Art. X, Y).
+
+ Texto fictício para teste: Informação exemplo 1 - Seguradora: 12345678901234
+
+ Texto fictício para teste: Informação exemplo 2 - Seguradora: 12345678901234
+
+
+ Texto fictício para teste: Informação exemplo 3
+
+
+ Texto fictício para teste: Informação exemplo 4 - ROTA: ABCD/EFGH - TARIF: 123 - TIPO
+ MERCAD: EXEMPLO
+
+
+ Texto fictício para teste: Tratamento de dados exemplo (Art. X, Y).
+
+
+
+ 92382301841312
+ 232412442
+ FANTASMA TRANSPORTES LTDA
+ FANTASMA TRANSPORTES LTDA
+
+ RUA TESTE FANTASMA
+ 0099
+ FANTASMA
+ 4216305
+ SAO JOAO BATISTA
+ 88240000
+ SC
+ 489999009
+
+ 1
+
+
+ 23924902942209
+ 224242421
+ INDUSTRIA FANTASMA DE TESTE
+ INDUSTRIA FANTASMA DE TESTE
+ 47232332239
+
+ RODOVIA SC FANTASMA
+ 122
+ TESTE
+ 4216305
+ SAO JOAO BATISTA
+ 88240000
+ SC
+ 1058
+ BRASIL
+
+
+
+ 23924902942209
+ 224242421
+ CTE EMITIDO EM AMBIENTE DE TESTE
+ 47232332239
+
+ RODOVIA SC FANTASMA
+ 122
+ Teste
+ 4216305
+ Joinville
+ 89212210
+ SC
+ 1058
+ Brasil
+
+
+
+ 03364555000100
+ 254409377
+ CTE EMITIDO EM AMBIENTE DE TESTE
+ 47232332239
+
+ RODOVIA SC FANTASMA
+ 200
+ Cobre
+ 4216305
+ SAO JOAO BATISTA
+ 88122035
+ SC
+ 1058
+ Brasil
+
+
+
+ 02394820392092
+ 224242421
+ EMPRESA DE TESTE
+ 23424242442
+
+ TESTE L.A
+ 21
+ AO LADO DE EMPRESA FANTASMA
+ TESTE
+ 3556453
+ VARGEM GRANDE PAULISTA
+ 06730000
+ SP
+ 1058
+ BRASIL
+
+
+
+ 221.19
+ 221.19
+
+ FPESO
+ 117.78
+
+
+ FVALOR
+ 4.24
+
+
+ PEDAGIO
+ 13.11
+
+
+ TAXAS
+ 79.64
+
+
+ GRIS
+ 3.15
+
+
+ TAS
+ 3.27
+
+
+
+
+
+ 00
+ 221.19
+ 12.00
+ 26.54
+ 20.00
+ 10.00
+
+
+ 56.40
+
+
+
+ 3016.92
+ FITA GOMADA SEMI-KRAFT 80MM C/REFORCO CADEADO
+
+ 03
+ UNIDADE
+ 14
+
+
+ 03
+ PARES
+ 0
+
+
+ 00
+ M3
+ 0.0000
+
+
+ 01
+ PESO REAL
+ 209.8000
+
+
+ 01
+ PESO BASE DE CALCULO
+ 209.8000
+
+ 3016.92
+
+
+
+ 62367248427294724924247484294724947224924724
+
+
+ 26647257237588237892912038492427378248782474
+
+
+ 26647257237588237892912038492427378248782474
+
+
+ 21421432123787842376346723647623767432762367
+
+
+ 26467762782874893612467784782478278782684286
+
+
+ 27146726719120087166248234671990472672849217
+
+
+ 23432423747432562783672839271638578534746644
+
+
+ 27146726719120087166248234671990472672849217
+
+
+ 23432423747432562783672839271638578534746644
+
+
+ 21421432123787842376346723647623767432762367
+
+
+ 42232387690480348792673297204795735037539474
+
+
+ 21421432123787842376346723647623767432762367
+
+
+ 42232387690480348792673297204795735037539474
+
+
+ 42232387690480348792673297204795735037539474
+
+
+ 21421432123787842376346723647623767432762367
+
+
+ 42232387690480348792673297204795735037539474
+
+
+
+
+ 23920392
+
+
+
+
+
+
+ https://dfe-portal.svrs.rs.gov.br/cte/qrCode?chCTe=42240302484555000343570020003396861081562222&tpAmb=1
+
+
+
+
+
+ 1
+ RS20240222155212
+ 4224030248244242523254224232429098
+ 2024-03-27T20:17:16-03:00
+ 2324240111249023
+ UCtDCEsja/Ljq5GjpftJHAK3oHI=
+ 100
+ Autorizado o uso do CT-e
+
+
+
diff --git a/tests/generated/dacte/dacte_default.pdf b/tests/generated/dacte/dacte_default.pdf
new file mode 100644
index 0000000..d1de559
Binary files /dev/null and b/tests/generated/dacte/dacte_default.pdf differ
diff --git a/tests/generated/dacte/dacte_default_logo.pdf b/tests/generated/dacte/dacte_default_logo.pdf
new file mode 100644
index 0000000..feb9224
Binary files /dev/null and b/tests/generated/dacte/dacte_default_logo.pdf differ
diff --git a/tests/generated/dacte/dacte_overload.pdf b/tests/generated/dacte/dacte_overload.pdf
new file mode 100644
index 0000000..36e1ac0
Binary files /dev/null and b/tests/generated/dacte/dacte_overload.pdf differ
diff --git a/tests/test_dacte.py b/tests/test_dacte.py
new file mode 100644
index 0000000..617e77e
--- /dev/null
+++ b/tests/test_dacte.py
@@ -0,0 +1,50 @@
+import pytest
+
+from brazilfiscalreport.dacte import (
+ Dacte,
+ DacteConfig,
+ Margins,
+ ReceiptPosition,
+)
+from tests.conftest import assert_pdf_equal, get_pdf_output_path
+
+
+@pytest.fixture
+def load_dacte(load_xml):
+ def _load_dacte(filename, config=None):
+ xml_content = load_xml(filename)
+ return Dacte(xml=xml_content, config=config)
+
+ return _load_dacte
+
+
+@pytest.fixture(scope="module")
+def default_dacte_config(logo_path):
+ config = DacteConfig(
+ margins=Margins(top=2, right=2, bottom=2, left=2),
+ logo=logo_path,
+ receipt_pos=ReceiptPosition.TOP,
+ )
+ return config
+
+
+def test_dacte_default(tmp_path, load_dacte):
+ dacte = load_dacte("dacte_test_1.xml")
+ pdf_path = get_pdf_output_path("dacte", "dacte_default")
+ assert_pdf_equal(dacte, pdf_path, tmp_path)
+
+
+def test_dacte_overload(tmp_path, load_dacte):
+ dacte_config = DacteConfig(margins=Margins(top=10, right=10, bottom=10, left=10))
+ dacte = load_dacte("dacte_test_overload.xml", config=dacte_config)
+ pdf_path = get_pdf_output_path("dacte", "dacte_overload")
+ assert_pdf_equal(dacte, pdf_path, tmp_path)
+
+
+def test_dacte_default_logo(tmp_path, load_dacte, logo_path):
+ dacte_config = DacteConfig(
+ logo=logo_path,
+ )
+ dacte = load_dacte("dacte_test_1.xml", config=dacte_config)
+ pdf_path = get_pdf_output_path("dacte", "dacte_default_logo")
+ assert_pdf_equal(dacte, pdf_path, tmp_path)