Paraguay - Electronic Invoicing Base

l10n_py_edi_base
REPOSITORY
REPOSITORYOCA/l10n-paraguay
GIT
GIThttps://github.com/OCA/l10n-paraguay.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/l10n-paraguay/tree/16.0/l10n_py_edi_base
VERSION
VERSION 3.0.0
CATEGORY
CATEGORYAccounting/Localizations/EDI
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSOdoo Community Association (OCA), KMEE
MAINTAINERS
MAINTAINERSOdoo Community Association (OCA), KMEE
COMMITTERS
COMMITTERSmileo, OCA-git-bot, oca-ci
WEBSITE
WEBSITEhttps://github.com/OCA/l10n-paraguay
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 19:12:07
ODOO DEPENDENCIES
ODOO DEPENDENCIES OCA/l10n-paraguay:
    - l10n_py_base
    - l10n_py_account
    - l10n_py
odoo/odoo:
    - account
    - base_setup
    - base
    - web
    - product
    - mail
    - bus
    - web_tour
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
    - resource
    - base_address_extended
    - l10n_latam_base
    - contacts
    - base_vat
    - l10n_latam_invoice_document
    - account_debit_note
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES cryptography
pykude
qrcode
requests
num2words
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
# Paraguay - Electronic Invoicing Base

This module provides the base functionality for electronic invoicing (facturación electrónica) in Paraguay, compliant with SET (Subsecretaría de Estado de Tributación) requirements.

## Features

### Document Types Support
- **Factura Electrónica**: Electronic invoice
- **Nota de Crédito Electrónica**: Electronic credit note
- **Nota de Débito Electrónica**: Electronic debit note
- **Nota de Remisión Electrónica**: Electronic delivery note
- **Autofactura Electrónica**: Electronic self-invoice

### Core Functionality
- **Data Models**: Complete models for electronic documents
- **Field Extensions**: Enhanced account.move, res.partner, and res.company
- **Fiscal Validation**: RUC and DV validation
- **JSON Builder**: Automatic generation of JSON for SIFEN
- **QR Code Generation**: Ready for KUDE (Código Único de Documento Electrónico)
- **Log System**: Complete audit trail of EDI operations

### Compliance
- **SIFEN Compatible**: Sistema Integrado de Facturación Electrónica Nacional
- **SET Requirements**: Meets all SET regulatory requirements
- **Contingency Mode**: Support for offline operation
- **KUDE Support**: Ready for electronic document codes

### Integration Ready
This is a base module that requires a connector:
- `l10n_py_edi_factpy`: FactPy integration
- `l10n_py_edi_facturasend`: FacturaSend integration

## Technical Architecture

- **Provider-agnostic**: Works with multiple EDI providers
- **Extensible**: Easy to add new document types
- **Robust**: Error handling and retry mechanisms
- **Auditable**: Complete logging of all operations

## Dependencies

- `account`: Core accounting
- `l10n_py_base`: Base Paraguayan localization
- `l10n_py_account`: Accounting extensions (timbrado management)
- `product`: Product management
- `sale`: Sales management

Code Analysis

Views touched (15)
XML IDNameModelTypeStatus
kude_report_template kude_report_template ir.ui.view qweb New
view_account_move_send_edi_wizard account.move.send.edi.wizard.form account.move.send.edi.wizard form New
view_company_form_edi res.company.form.edi res.company xpath Inherits base.view_company_form
view_edi_connector_form l10n_py.edi.connector.form l10n_py.edi.connector form New
view_edi_connector_tree l10n_py.edi.connector.tree l10n_py.edi.connector tree New
view_l10n_py_associated_document_tree l10n_py.associated.document.tree l10n_py.associated.document tree New
view_l10n_py_edi_cancel_wizard l10n_py.edi.cancel.wizard.form l10n_py.edi.cancel.wizard form New
view_l10n_py_edi_log_form l10n_py.edi.log.form l10n_py.edi.log form New
view_l10n_py_edi_log_tree l10n_py.edi.log.tree l10n_py.edi.log tree New
view_l10n_py_number_inutilization_form l10n_py.number.inutilization.form l10n_py.number.inutilization form New
view_l10n_py_number_inutilization_tree l10n_py.number.inutilization.tree l10n_py.number.inutilization tree New
view_l10n_py_transport_form l10n_py.transport.form l10n_py.transport form New
view_l10n_py_transport_tree l10n_py.transport.tree l10n_py.transport tree New
view_move_form_edi account.move.form.edi account.move xpath Inherits account.view_move_form
view_product_template_form_edi product.template.form.edi product.template xpath Inherits product.product_template_form_view
Models touched (14)

New fields (39)
  • l10n_py_afe_constancia_control Char
    size=20 string='Número de Control (EA005)'
  • l10n_py_afe_constancia_number Char
    size=20 string='Número de Constancia (EA004)'
  • l10n_py_afe_constancia_type Selection
    string='Tipo de Constancia (EA002)' args: [('1', 'No contribuyente'), ('2', 'Microproductor')]
  • l10n_py_afe_provision_address Char
    string='Dirección Provisión (EA018)'
  • l10n_py_afe_provision_city Integer
    string='Ciudad Provisión (EA023)'
  • l10n_py_afe_provision_department Integer
    string='Departamento Provisión (EA019)'
  • l10n_py_afe_provision_district Integer
    string='Distrito Provisión (EA021)'
  • l10n_py_afe_vendor_address Char
    string='Dirección Vendedor (EA010)'
  • l10n_py_afe_vendor_city Integer
    string='Ciudad Vendedor (EA016)'
  • l10n_py_afe_vendor_department Integer
    string='Departamento Vendedor (EA012)'
  • l10n_py_afe_vendor_district Integer
    string='Distrito Vendedor (EA014)'
  • l10n_py_afe_vendor_doc_number Char
    size=20 string='Nro. Doc. Vendedor (EA008)'
  • l10n_py_afe_vendor_doc_type Selection
    string='Tipo Doc. Vendedor (EA006)' args: [('1', 'Cédula paraguaya'), ('2', 'Pasaporte'), ('3', 'Cédula extranjera'), ('4', 'Carnet de residencia')]
  • l10n_py_afe_vendor_house Integer
    string='Nro. Casa Vendedor (EA011)'
  • l10n_py_afe_vendor_name Char
    string='Nombre Vendedor (EA009)'
  • l10n_py_associated_document_ids One2many → l10n_py.associated.document
    help='Documentos asociados al DTE (Grupo H del SIFEN)' string='Documentos Asociados' args: 'l10n_py.associated.document', 'move_id'
  • l10n_py_cdc Char
    copy=False help='Código de Control del documento electrónico' readonly=True args: 'CDC'
  • l10n_py_contingency_motive Char
    args: 'Motivo de Contingencia'
  • l10n_py_doc_type_code Char
    compute='_compute_l10n_py_doc_type_code'
  • l10n_py_edi_batch_id Char
    copy=False readonly=True args: 'ID de Lote'
  • l10n_py_edi_message Text
    copy=False readonly=True args: 'Mensaje EDI'
  • l10n_py_edi_status Selection
    copy=False default='draft' readonly=True string='Estado EDI' args: [('draft', 'Borrador'), ('to_send', 'Para Enviar'), ('sent', 'Enviado'), ('processing', 'Procesando'), ('accepted', 'Aceptado'), ('rejected', 'Rechazado'), ('cancelled', 'Cancelado'), ('error', 'Error')]
  • l10n_py_edi_xml Binary
    copy=False readonly=True args: 'XML Firmado'
  • l10n_py_edi_xml_filename Char
    readonly=True args: 'XML Filename'
  • l10n_py_emission_type Selection
    default='1' required=True string='Tipo de Emisión' args: [('1', 'Normal'), ('2', 'Contingencia')]
  • l10n_py_exchange_rate_condition Selection
    default='1' help='Condición del tipo de cambio (D015)' string='Condición Tipo de Cambio' args: [('1', 'Global'), ('2', 'Por Ítem')]
  • l10n_py_kude_filename Char
    readonly=True args: 'KUDE Filename'
  • l10n_py_kude_pdf Binary
    copy=False readonly=True args: 'KUDE (PDF)'
  • l10n_py_nre_estimated_invoice_date Date
    help='Fecha estimada de facturación para NRE sin factura asociada' string='Fecha Estimada de Facturación (E506)'
  • l10n_py_nre_motive Selection
    string='Motivo de Remisión (E501)' args: [('1', 'Traslado por venta'), ('2', 'Traslado por consignación'), ('3', 'Traslado por exportación'), ('4', 'Traslado por importación'), ('5', 'Traslado entre locales'), ('6', 'Otros')]
  • l10n_py_payment_type Selection
    default='1' help='Tipo de pago para condición contado (E606)' string='Tipo de Pago' args: [('1', 'Efectivo'), ('2', 'Cheque'), ('3', 'Tarjeta de crédito'), ('4', 'Tarjeta de débito'), ('5', 'Transferencia'), ('6', 'Giro'), ('7', 'Billetera electrónica'), ('8', 'Tarjeta empresarial'), ('9', 'Vale'), ('10', 'Retención'), ('11', 'Anticipo'), ('12', 'Valor fiscal'), ('13', 'Valor comercial'), ('14', 'Compensación'), ('15', 'Permuta'), ('16', 'Pago bancario')]
  • l10n_py_presence_type Selection
    default='1' string='Tipo de Presencia' args: [('1', 'Operación presencial'), ('2', 'Operación electrónica'), ('3', 'Operación telemarketing'), ('4', 'Venta a domicilio'), ('5', 'Operación bancaria')]
  • l10n_py_qr_code Binary
    copy=False readonly=True args: 'Código QR'
  • l10n_py_qr_string Char
    copy=False readonly=True args: 'String QR'
  • l10n_py_receipt_id Char
    help='ID único del sistema cliente' args: 'Receipt ID'
  • l10n_py_security_code Char
    copy=False readonly=True size=9 args: 'Código de Seguridad'
  • l10n_py_transaction_type Selection
    default='1' required=True string='Tipo de Transacción' args: [('1', 'Venta de mercadería'), ('2', 'Prestación de servicios'), ('3', 'Mixto (Venta de mercadería y servicios)'), ('4', 'Venta de activo fijo'), ('5', 'Venta de divisas'), ('6', 'Compra de divisas'), ('7', 'Promoción o entrega de muestras'), ('8', 'Donación'), ('9', 'Anticipo'), ('10', 'Compra de productos'), ('11', 'Compra de servicios'), ('12', 'Venta de crédito fiscal'), ('13', 'Compra de crédito fiscal')]
  • l10n_py_transmission_deadline Datetime
    compute='_compute_transmission_deadline' help='Plazo máximo para transmitir el DTE (72 horas desde emisión)' store=True string='Plazo de Transmisión'
  • l10n_py_transport_id Many2one → l10n_py.transport
    help='Datos de transporte para Nota de Remisión (Grupo G SIFEN)' string='Datos de Transporte' args: 'l10n_py.transport'
Public methods (8)
  • action_cancel_edi(self)
    Cancelar documento electrónico
  • action_download_kude(self)
    Descargar KUDE (PDF)
  • action_download_xml(self)
    Descargar XML del documento
  • action_post(self)
    Override para configurar estado EDI al confirmar factura.
  • action_preview_kude(self)
    Generar KuDE (PDF) a partir del XML preview via pykude.
  • action_preview_xml(self)
    Generar y mostrar XML sin firmar ni enviar (preview).
  • action_retry_edi(self)
    Reintentar envío de documento
  • action_send_edi(self)
    Enviar documento a sistema EDI

New fields (1)
  • invoice_id Many2one → account.move
    required=True string='Factura' args: 'account.move'
Public methods (2)
  • action_send(self)
    Enviar factura a EDI
  • default_get(self, fields_list)
    @api.model

New fields (11)
  • association_type Selection
    required=True string='Tipo de Asociación (H002)' args: [('1', 'Electrónico'), ('2', 'Impreso'), ('3', 'Constancia Electrónica')]
  • cdc Char
    help='Código de Control del Documento Electrónico (44 dígitos)' size=44 string='CDC'
  • constancia_number Char
    size=20 string='Número de Constancia (H012)'
  • constancia_type Selection
    string='Tipo de Constancia (H011)' args: [('1', 'Constancia de no ser contribuyente'), ('2', 'Constancia de microproductor')]
  • doc_date Date
    string='Fecha del Documento (H010)'
  • doc_number Char
    help='Número del documento (H008)' size=7 string='Número de Documento'
  • doc_type_code Selection
    string='Tipo de Documento Impreso (H009)' args: [('1', 'Factura'), ('2', 'Nota de crédito'), ('3', 'Nota de débito'), ('4', 'Nota de remisión'), ('5', 'Comprobante de retención')]
  • establishment Char
    help='Código de establecimiento (H006)' size=3 string='Establecimiento'
  • expedition_point Char
    help='Punto de expedición (H007)' size=3 string='Punto de Expedición'
  • move_id Many2one → account.move
    index=True ondelete='cascade' required=True string='Factura' args: 'account.move'
  • timbrado Char
    help='Número de timbrado del documento impreso (H005)' size=8
Public methods (0)

No public methods.

New fields (2)
  • invoice_id Many2one → account.move
    required=True string='Factura' args: 'account.move'
  • motive Text
    required=True string='Motivo de Cancelación'
Public methods (2)
  • action_cancel(self)
    Cancelar documento EDI con verificación de plazos.
  • default_get(self, fields_list)
    @api.model

New fields (6)
  • active Boolean
    default=True
  • company_id Many2one → res.company
    default=<expr> required=True args: 'res.company'
  • environment Selection
    default='test' required=True args: [('test', 'Pruebas'), ('prod', 'Producción')]
  • name Char
    required=True
  • provider_type Selection
    required=True selection=[] string='Proveedor'
  • timeout Integer
    default=30
Public methods (6)
  • cancel_document(self, document_id, reason='')
    Cancel an approved document.
  • check_status(self, document_id)
    Check document status.
  • inutilize_range(self, data)
    Inutilize a range of document numbers. Returns dict.
  • preview_document(self, invoice_data)
    Build XML without signing or sending. Returns XML string.
  • send_document(self, invoice_data)
    Send document. Returns dict with success, result, error keys.
  • test_connection(self)
    Test connectivity. Returns notification action.

New fields (3)
  • code Char
    required=True size=2 string='Código'
  • description Text
    string='Descripción' translate=True
  • name Char
    required=True string='Nombre' translate=True
Public methods (0)

No public methods.

New fields (19)
  • batch_id Char
    help='Identificador de lote do provedor' string='ID do Lote'
  • cdc Char
    help='Código de Control del documento' index=True string='CDC'
  • document_id Many2one → account.move
    help='Documento fiscal relacionado' index=True ondelete='cascade' string='Documento' args: 'account.move'
  • duration_human Char
    compute='_compute_duration_human' help='Duração em formato legível' string='Duração'
  • endpoint Char
    help='URL ou endpoint da API'
  • error Boolean
    compute='_compute_error' help='Indica se houve erro na operação' store=True string='É Erro'
  • error_code Char
    help='Código de erro do provedor' string='Código de Erro'
  • error_message Text
    help='Descrição do erro se houver' string='Mensagem de Erro'
  • execution_time Float
    digits=(10, 2) help='Tempo de execução em milissegundos' string='Tempo de Execução (ms)'
  • method Selection
    string='Método HTTP' args: [('GET', 'GET'), ('POST', 'POST'), ('PUT', 'PUT'), ('DELETE', 'DELETE'), ('PATCH', 'PATCH')]
  • operation_type Selection
    index=True required=True string='Tipo de Operação' args: [('send', 'Envio de Documento'), ('status', 'Consulta de Status'), ('cancel', 'Cancelación'), ('event', 'Evento'), ('download_pdf', 'Download PDF'), ('download_xml', 'Download XML'), ('validate', 'Validação')]
  • provider Selection
    index=True required=True string='Provedor' args: [('factpy', 'FactPy'), ('facturasend', 'FacturaSend'), ('sifen', 'SIFEN Directo'), ('local', 'Processamento Local')]
  • request_data Text
    help='Payload da requisição' string='Dados Enviados'
  • request_headers Text
    help='Headers HTTP enviados' string='Headers da Requisição'
  • response_data Text
    help='Payload da resposta' string='Resposta Recebida'
  • response_headers Text
    help='Headers HTTP recebidos' string='Headers da Resposta'
  • retry_count Integer
    default=0 help='Número de tentativas realizadas' string='Tentativas'
  • status_code Integer
    help='Código de status HTTP' string='Código de Status'
  • success Boolean
    default=True help='Indica se a operação foi bem-sucedida' index=True string='Sucesso'
Public methods (5)
  • action_retry_operation(self)
    Repetir operação (se aplicável)
  • action_view_document(self)
    Abrir documento relacionado
  • action_view_request_data(self)
    Exibir dados da requisição em formato legível
  • action_view_response_data(self)
    Exibir dados da resposta em formato legível
  • log_operation(self, operation_type, provider, document=None, request_data=None, response_data=None, execution_time=0, success=True, error_message=None, **kwargs)
    @api.model
    Registrar operação EDI Args: operation_type (str): Tipo de operação provider (str): Provedor EDI document (account.move): Documento relacionado request_data (dict): Dados da requisição response_data (dict): Dados da resposta execution_time (float): Tempo de execução em ms success (bool): Se a operação foi bem-sucedida error_message (str): Mensagem de erro (se houver) **kwargs: Campos adicionais Returns: l10n_py.edi.log: Registro de log criado

New fields (0)

No new fields.

Public methods (1)
  • log_edi_operation(self, operation_type, provider, document=None, **kwargs)
    @contextmanager
    Context manager para logging automático de operações EDI Usage: with self.log_edi_operation('send', 'factpy', document) as log_data: result = self.send_to_provider(data) log_data['response_data'] = result Args: operation_type (str): Tipo de operação provider (str): Provedor EDI document (account.move): Documento relacionado **kwargs: Dados adicionais para o log Yields: dict: Dicionário para adicionar dados durante a operação

New fields (7)
  • authorization_id Many2one → account.authorization
    ondelete='restrict' required=True string='Timbrado' args: 'account.authorization'
  • company_id Many2one
    related='authorization_id.company_id' store=True
  • motive Text
    required=True string='Motivo'
  • number_from Integer
    required=True string='Número Desde'
  • number_to Integer
    required=True string='Número Hasta'
  • quantity Integer
    compute='_compute_quantity' string='Cantidad'
  • state Selection
    default='draft' readonly=True string='Estado' args: [('draft', 'Borrador'), ('sent', 'Enviado'), ('accepted', 'Aceptado'), ('rejected', 'Rechazado')]
Public methods (1)
  • action_send(self)
    Enviar inutilización al SIFEN a través del conector EDI.

New fields (22)
  • company_id Many2one
    related='move_id.company_id' store=True
  • delivery_ids One2many → l10n_py.transport.delivery
    string='Entregas' args: 'l10n_py.transport.delivery', 'transport_id'
  • departure_address Char
    string='Dirección de Salida (E920)'
  • departure_city Integer
    string='Ciudad Salida (E928)'
  • departure_department Integer
    string='Departamento Salida (E924)'
  • departure_district Integer
    string='Distrito Salida (E926)'
  • departure_house Integer
    string='Número de Casa Salida (E921)'
  • driver_doc_number Char
    string='Doc. del Chofer (E950)'
  • driver_name Char
    string='Nombre del Chofer (E951)'
  • freight_responsibility Selection
    string='Responsable del Flete (E905)' args: [('1', 'Emisor de la Factura Electrónica'), ('2', 'Receptor de la Factura Electrónica'), ('3', 'Tercero'), ('4', 'Agente intermediario del transporte')]
  • incoterm Selection
    string='Condición de Negociación (E906)' args: [('CFR', 'CFR'), ('CIF', 'CIF'), ('CIP', 'CIP'), ('CPT', 'CPT'), ('DAP', 'DAP'), ('DAT', 'DAT'), ('DDP', 'DDP'), ('EXW', 'EXW'), ('FAS', 'FAS'), ('FCA', 'FCA'), ('FOB', 'FOB')]
  • manifest_number Char
    string='Número de Manifiesto / Conocimiento (E907)'
  • move_id Many2one → account.move
    ondelete='cascade' required=True string='Documento' args: 'account.move'
  • transport_end_date Date
    string='Fecha Fin Transporte (E910)'
  • transport_mode Selection
    default='1' required=True string='Modalidad de Transporte (E901)' args: [('1', 'Terrestre'), ('2', 'Fluvial'), ('3', 'Aéreo'), ('4', 'Multimodal')]
  • transport_start_date Date
    string='Fecha Inicio Transporte (E909)'
  • transport_type Selection
    string='Tipo de Transporte (E903)' args: [('1', 'Propio'), ('2', 'Tercero')]
  • transporter_dv Char
    size=1 string='DV del Transportista (E943)'
  • transporter_name Char
    string='Nombre del Transportista (E941)'
  • transporter_nature Selection
    string='Naturaleza del Transportista (E940)' args: [('1', 'Contribuyente'), ('2', 'No contribuyente')]
  • transporter_ruc Char
    string='RUC del Transportista (E942)'
  • vehicle_ids One2many → l10n_py.transport.vehicle
    string='Vehículos' args: 'l10n_py.transport.vehicle', 'transport_id'
Public methods (0)

No public methods.

New fields (6)
  • address Char
    required=True string='Dirección de Entrega (E930)'
  • city Integer
    required=True string='Ciudad (E938)'
  • department Integer
    required=True string='Departamento (E934)'
  • district Integer
    string='Distrito (E936)'
  • house_number Integer
    string='Número de Casa (E931)'
  • transport_id Many2one → l10n_py.transport
    ondelete='cascade' required=True string='Transporte' args: 'l10n_py.transport'
Public methods (0)

No public methods.

New fields (4)
  • brand Char
    required=True string='Marca (E961)'
  • plate_number Char
    required=True string='Número de Identificación (E962)'
  • transport_id Many2one → l10n_py.transport
    ondelete='cascade' required=True string='Transporte' args: 'l10n_py.transport'
  • vehicle_type Char
    required=True string='Tipo de Vehículo (E960)'
Public methods (0)

No public methods.

New fields (2)
  • l10n_py_ncm_code Char
    help='Nomenclatura Común del Mercosur (8 dígitos)' size=8 string='Código NCM'
  • l10n_py_unit_code Integer
    default=77 help='Código de unidad de medida según SET (77 = UNI - Unidad)' string='Código Unidad de Medida'
Public methods (0)

No public methods.

New fields (9)
  • l10n_py_city_code Char
    help='Código de la ciudad según SET' readonly=True related='partner_id.l10n_py_city_code' store=True string='Código Ciudad SET'
  • l10n_py_department_code Integer
    help='Código del departamento según SET' readonly=True related='partner_id.l10n_py_department_code' store=True string='Código Departamento SET'
  • l10n_py_district_code Integer
    help='Código del distrito según SET' string='Código Distrito SET'
  • l10n_py_dv Char
    compute='_compute_dv' help='Dígito Verificador del RUC' size=1 store=True string='DV'
  • l10n_py_economic_activity Char
    help='Descripción de la actividad económica principal' string='Descripción Actividad Económica'
  • l10n_py_economic_activity_code Char
    help='Código de actividad económica principal según SET' size=8 string='Código Actividad Económica'
  • l10n_py_ruc Char
    help='Registro Único del Contribuyente sin dígito verificador' size=8 string='RUC'
  • l10n_py_ruc_full Char
    compute='_compute_ruc_full' help='RUC con dígito verificador' store=True string='RUC Completo'
  • l10n_py_trade_name Char
    help='Nombre comercial o de fantasía de la empresa' string='Nombre Fantasía'
Public methods (0)

No public methods.