Copy of all modules in original repo

This commit is contained in:
Jorge Enrique Gómez Gómez 2022-10-08 16:33:21 -05:00
parent b9263b9119
commit 79491e0d69
369 changed files with 33148 additions and 0 deletions

View File

@ -0,0 +1,18 @@
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3
===============
Tax Group Types
===============
Types for Tax Groups
Credits
=======
Contributors
------------
* Joan Marín <https://github.com/JoanMarin>

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Tax Group Types",
"summary": "Types for Tax Groups",
"version": "14.0.1.0.0",
"license": "AGPL-3",
"website": "https://github.com/JoanMarin",
"author": "Joan Marín Github@JoanMarin",
"category": "Localization",
"depends": ["account_menu"],
"data": [
'security/ir.model.access.csv',
"views/account_tax_group_views.xml",
],
"installable": True,
}

View File

@ -0,0 +1,110 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_tax_group_type
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-08-20 13:59+0000\n"
"PO-Revision-Date: 2021-08-20 13:59+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__code
msgid "Code"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__create_uid
msgid "Created by"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__create_date
msgid "Created on"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__description
msgid "Description"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group__display_name
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__display_name
msgid "Display Name"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group__id
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__id
msgid "ID"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group____last_update
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type____last_update
msgid "Last Modified on"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__write_uid
msgid "Last Updated by"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__write_date
msgid "Last Updated on"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__name
msgid "Name"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields.selection,name:account_tax_group_type.selection__account_tax_group_type__type__tax
msgid "Tax"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model,name:account_tax_group_type.model_account_tax_group
msgid "Tax Group"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group__tax_group_type_id
msgid "Tax Group Type"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model,name:account_tax_group_type.model_account_tax_group_type
msgid "Tax Group Types"
msgstr ""
#. module: account_tax_group_type
#: code:addons/account_tax_group_type/models/account_tax_group_type.py:0
#, python-format
msgid "The code of Tax Group Type must be unique!"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__type
msgid "Type"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields.selection,name:account_tax_group_type.selection__account_tax_group_type__type__withholding_tax
msgid "Withholding Tax"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.constraint,message:account_tax_group_type.constraint_account_tax_group_type_code_uniq
msgid "¡El código del Tipo de Grupo Impuesto debe ser único!"
msgstr ""

View File

@ -0,0 +1,106 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_tax_group_type
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-08-20 14:04+0000\n"
"PO-Revision-Date: 2021-08-20 14:04+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__code
msgid "Code"
msgstr "Código"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__create_uid
msgid "Created by"
msgstr "Creado por"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__create_date
msgid "Created on"
msgstr "Creado el"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__description
msgid "Description"
msgstr "Descripción"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group__display_name
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__display_name
msgid "Display Name"
msgstr "Nombre Público"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group__id
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__id
msgid "ID"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group____last_update
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type____last_update
msgid "Last Modified on"
msgstr "Última Modificación el"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__write_uid
msgid "Last Updated by"
msgstr "Última Actualización por"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__write_date
msgid "Last Updated on"
msgstr "Última Actualización el"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__name
msgid "Name"
msgstr "Nombre"
#. module: account_tax_group_type
#: model:ir.model.fields.selection,name:account_tax_group_type.selection__account_tax_group_type__type__tax
msgid "Tax"
msgstr "Impuesto"
#. module: account_tax_group_type
#: model:ir.model,name:account_tax_group_type.model_account_tax_group
msgid "Tax Group"
msgstr "Grupo de impuestos"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group__tax_group_type_id
msgid "Tax Group Type"
msgstr "Tipo de Grupo de Impuesto"
#. module: account_tax_group_type
#: model:ir.model,name:account_tax_group_type.model_account_tax_group_type
msgid "Tax Group Types"
msgstr "Tipos de Grupos de Impuestos"
#. module: account_tax_group_type
#: code:addons/account_tax_group_type/models/account_tax_group_type.py:0
#: model:ir.model.constraint,message:account_tax_group_type.constraint_account_tax_group_type_code_uniq
#, python-format
msgid "The code of Tax Group Type must be unique!"
msgstr "¡El código del Tipo de Grupo Impuesto debe ser único!"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__type
msgid "Type"
msgstr "Tipo"
#. module: account_tax_group_type
#: model:ir.model.fields.selection,name:account_tax_group_type.selection__account_tax_group_type__type__withholding_tax
msgid "Withholding Tax"
msgstr "Impuesto de Retención"

View File

@ -0,0 +1,106 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_tax_group_type
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-08-31 20:46+0000\n"
"PO-Revision-Date: 2021-08-31 20:46+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__code
msgid "Code"
msgstr "Código"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__create_uid
msgid "Created by"
msgstr "Creado por"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__create_date
msgid "Created on"
msgstr "Creado el"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__description
msgid "Description"
msgstr "Descripción"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group__display_name
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__display_name
msgid "Display Name"
msgstr "Nombre Público"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group__id
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__id
msgid "ID"
msgstr ""
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group____last_update
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type____last_update
msgid "Last Modified on"
msgstr "Última Modificación el"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__write_uid
msgid "Last Updated by"
msgstr "Última Actualización por"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__write_date
msgid "Last Updated on"
msgstr "Última Actualización el"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__name
msgid "Name"
msgstr "Nombre"
#. module: account_tax_group_type
#: model:ir.model.fields.selection,name:account_tax_group_type.selection__account_tax_group_type__type__tax
msgid "Tax"
msgstr "Impuesto"
#. module: account_tax_group_type
#: model:ir.model,name:account_tax_group_type.model_account_tax_group
msgid "Tax Group"
msgstr "Grupo de impuestos"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group__tax_group_type_id
msgid "Tax Group Type"
msgstr "Tipo de Grupo de Impuesto"
#. module: account_tax_group_type
#: model:ir.model,name:account_tax_group_type.model_account_tax_group_type
msgid "Tax Group Types"
msgstr "Tipos de Grupos de Impuestos"
#. module: account_tax_group_type
#: code:addons/account_tax_group_type/models/account_tax_group_type.py:0
#: model:ir.model.constraint,message:account_tax_group_type.constraint_account_tax_group_type_code_uniq
#, python-format
msgid "The code of Tax Group Type must be unique!"
msgstr "¡El código del Tipo de Grupo Impuesto debe ser único!"
#. module: account_tax_group_type
#: model:ir.model.fields,field_description:account_tax_group_type.field_account_tax_group_type__type
msgid "Type"
msgstr "Tipo"
#. module: account_tax_group_type
#: model:ir.model.fields.selection,name:account_tax_group_type.selection__account_tax_group_type__type__withholding_tax
msgid "Withholding Tax"
msgstr "Impuesto de Retención"

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import account_tax_group_type
from . import account_tax_group

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class AccountTaxGroup(models.Model):
_inherit = "account.tax.group"
tax_group_type_id = fields.Many2one(
comodel_name="account.tax.group.type",
string="Tax Group Type")

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models, _
class AccountTaxGroupType(models.Model):
_name = 'account.tax.group.type'
_description = 'Tax Group Types'
code = fields.Char(string='Code', required=True)
name = fields.Char(string='Name', required=True)
type = fields.Selection(
selection=[('tax', 'Tax'), ('withholding_tax', 'Withholding Tax')],
string='Type',
required=True,
default=False)
description = fields.Char(string='Description')
_sql_constraints = [
('code_uniq', 'unique (code)',
_('The code of Tax Group Type must be unique!'))]

View File

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_tax_group_type_user,access_account_tax_group_type_user,model_account_tax_group_type,,1,0,0,0
access_account_tax_group_type_manager,access_account_tax_group_type_manager,model_account_tax_group_type,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_tax_group_type_user access_account_tax_group_type_user model_account_tax_group_type 1 0 0 0
3 access_account_tax_group_type_manager access_account_tax_group_type_manager model_account_tax_group_type base.group_system 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_account_tax_group_tree" model="ir.ui.view">
<field name="model">account.tax.group</field>
<field name="inherit_id" ref="account.view_tax_group_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='sequence']" position="after">
<field name="tax_group_type_id"/>
</xpath>
</field>
</record>
<record id="view_account_tax_group_form" model="ir.ui.view">
<field name="model">account.tax.group</field>
<field name="inherit_id" ref="account_menu.view_account_tax_group_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='sequence']" position="after">
<group>
<field name="tax_group_type_id"/>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
"""
Module models
"""
from . import models
from . import wizard
from . import controllers

View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# pylint: disable=missing-docstring
{
'name': "Facturación Electrónica DIAN",
'summary': """
Facturación Electrónica DIAN""",
'description': """
Envio de facturas y notas electrónicas, por Avancys SAS como
proveedor tecnológico o por software propio.
""",
'author': "Avancys SAS",
'website': "http://www.avancys.com",
'category': 'Accounting/Accounting',
'version': '14.0.0.0',
'depends': ['account', 'sale'],
'license': 'OEEL-1',
'installable': True,
'data': [
'data/data.xml',
# 'data/res.country.state.csv',
'security/security.xml',
'security/ir.model.access.csv',
'wizard/ei_multi_process.xml',
'wizard/ei_state_reset.xml',
'views/res_company.xml',
'views/electronic_invoice_resolution.xml',
'views/account_journal.xml',
'views/account_move.xml',
'views/account_tax.xml',
'views/uom_uom.xml',
'views/sale_order.xml',
'views/res_partner.xml',
'templates/customer_acknowledge_email.xml',
'templates/customer_acknowledge_response.xml',
'templates/electronic_invoice.xml',
'report/electronic_invoice_report.xml',
],
}

View File

View File

@ -0,0 +1 @@
from . import main

View File

@ -0,0 +1,43 @@
import odoo
from odoo import api, SUPERUSER_ID
import odoo.http as http
from odoo.http import request
import odoo.addons.web.controllers.main as webmain
import json
import os
class AccountInvoiceAck(http.Controller):
@http.route('/invoice/dian/accept', type='http', auth="public")
def accept(self, db, token, id):
registry = odoo.modules.registry.Registry(db)
state_change = None
with api.Environment.manage(), registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
invoice_id = env['account.move'].search([
('access_token', '=', token),
('ei_state', '!=', 'customer_accept'),
('id', '=', id)
])
if invoice_id:
state_change = invoice_id.do_accept()
if not state_change:
return
return request.render('electronic_invoice_dian.customer_accept_invoice', {})
@http.route('/invoice/dian/reject', type='http', auth="public")
def reject(self, db, token, id):
registry = odoo.modules.registry.Registry(db)
state_change = None
with api.Environment.manage(), registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
invoice_id = env['account.move'].search([
('access_token', '=', token),
('ei_state', '!=', 'customer_accept'),
('id', '=', id)
])
if invoice_id:
state_change = invoice_id.do_accept()
if not state_change:
return
return request.render('electronic_invoice_dian.customer_reject_invoice', {})

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<menuitem id="electronic_invoice_menu" name="Facturación Electrónica" parent="account.menu_finance_configuration" sequence="2"/>
</odoo>

View File

@ -0,0 +1,34 @@
"id","code","name"
base.state_co_01,"05","ANTIOQUIA"
base.state_co_02,"08","ATLANTICO"
base.state_co_03,"11","BOGOTA"
base.state_co_04,"13","BOLIVAR"
base.state_co_05,"15","BOYACA"
base.state_co_06,"17","CALDAS"
base.state_co_07,"18","CAQUETA"
base.state_co_08,"19","CAUCA"
base.state_co_09,"20","CESAR"
base.state_co_10,"23","CORDOBA"
base.state_co_11,"25","CUNDINAMARCA"
base.state_co_12,"27","CHOCO"
base.state_co_13,"41","HUILA"
base.state_co_14,"44","LA GUAJIRA"
base.state_co_15,"47","MAGDALENA"
base.state_co_16,"50","META"
base.state_co_17,"52","NARIÑO"
base.state_co_18,"54","N. DE SANTANDER"
base.state_co_19,"63","QUINDIO"
base.state_co_20,"66","RISARALDA"
base.state_co_21,"68","SANTANDER"
base.state_co_22,"70","SUCRE"
base.state_co_23,"73","TOLIMA"
base.state_co_24,"76","VALLE DEL CAUCA"
base.state_co_25,"81","ARAUCA"
base.state_co_26,"85","CASANARE"
base.state_co_27,"86","PUTUMAYO"
base.state_co_28,"88","SAN ANDRES"
base.state_co_29,"91","AMAZONAS"
base.state_co_30,"94","GUAINIA"
base.state_co_31,"95","GUAVIARE"
base.state_co_32,"97","VAUPES"
base.state_co_33,"99","VICHADA"
1 id code name
2 base.state_co_01 05 ANTIOQUIA
3 base.state_co_02 08 ATLANTICO
4 base.state_co_03 11 BOGOTA
5 base.state_co_04 13 BOLIVAR
6 base.state_co_05 15 BOYACA
7 base.state_co_06 17 CALDAS
8 base.state_co_07 18 CAQUETA
9 base.state_co_08 19 CAUCA
10 base.state_co_09 20 CESAR
11 base.state_co_10 23 CORDOBA
12 base.state_co_11 25 CUNDINAMARCA
13 base.state_co_12 27 CHOCO
14 base.state_co_13 41 HUILA
15 base.state_co_14 44 LA GUAJIRA
16 base.state_co_15 47 MAGDALENA
17 base.state_co_16 50 META
18 base.state_co_17 52 NARIÑO
19 base.state_co_18 54 N. DE SANTANDER
20 base.state_co_19 63 QUINDIO
21 base.state_co_20 66 RISARALDA
22 base.state_co_21 68 SANTANDER
23 base.state_co_22 70 SUCRE
24 base.state_co_23 73 TOLIMA
25 base.state_co_24 76 VALLE DEL CAUCA
26 base.state_co_25 81 ARAUCA
27 base.state_co_26 85 CASANARE
28 base.state_co_27 86 PUTUMAYO
29 base.state_co_28 88 SAN ANDRES
30 base.state_co_29 91 AMAZONAS
31 base.state_co_30 94 GUAINIA
32 base.state_co_31 95 GUAVIARE
33 base.state_co_32 97 VAUPES
34 base.state_co_33 99 VICHADA

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
"""
Models
"""
from . import res_company
from . import electronic_invoice_resolution
from . import account_journal
from . import account_move
from . import account_tax
from . import ei_transaction_log
from . import uom_uom
from . import sale_order
from . import res_partner

View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.exceptions import ValidationError
INVALID_TYPE_WARN = """Tipo de diario no válido para facturación electrónica"""
class AccountJournal(models.Model):
_inherit = 'account.journal'
resolution_id = fields.Many2one(
string='Resolución de Numeración',
comodel_name='electronic.invoice.resolution')
@api.constrains('resolution_id')
def check_document_type(self):
if self.type != 'sale' and self.resolution_id:
raise ValidationError(INVALID_TYPE_WARN)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
EI_CODE_SELECTION = [
('01', 'IVA'),
('02', 'IC'),
('03', 'ICA'),
('04', 'INC'),
('05', 'ReteIVA'),
('06', 'ReteFuente'),
('07', 'ReteICA')
]
class AccountTax(models.Model):
_inherit = 'account.tax'
ei_code = fields.Selection(
string='Código Facturación Electrónica',
selection=EI_CODE_SELECTION)

View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
DOCUMENT_STATE_SELECTION = [
('sent', 'Emitido'),
('dian_reject', 'Rechazo Dian'),
('dian_accept', 'Aceptación Dian'),
('email_sent', 'Email Enviado'),
('email_fail', 'Email Fallido'),
('customer_accept', 'Aceptación Cliente'),
('customer_reject', 'Rechazo Cliente'),
('provider_error', 'Error de Procesamiento'),
('exception', 'Excepción de Envio'),
]
DOCUMENT_TYPE_SELECTION = [
('json', 'Json'),
('app_response', 'Application Response'),
('xml_fe', 'Factura XML'),
('none', 'Ninguno'),
]
class EITransactionLog(models.Model):
_name = 'ei.transaction.log'
_description = 'Log de Facturacion Electronica'
_order = 'date desc'
invoice_id = fields.Many2one(
string='Factura', comodel_name='account.move', readonly=True)
date = fields.Datetime(string='Fecha', readonly=True)
document_state = fields.Selection(
string='Estado', selection=DOCUMENT_STATE_SELECTION, readonly=True)
document_type = fields.Selection(
string='Tipo de documento', selection=DOCUMENT_TYPE_SELECTION,
readonly=True)
content = fields.Text(string='Contenido', readonly=True)

View File

@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.exceptions import ValidationError
from lxml import etree
import requests
import re
import json
import base64
import operator
from functools import reduce
from .http_helper import URL_NUMBERING_XML, AVANCYS_NIT, HEADERS
ID_PARAM_HELP = """Provisto por el Proveedor Tecnológico"""
DOCUMENT_TYPE_HELP = """
Para Notas Crédito y Débito los datos de resolución no aplican"""
DOCUMENT_TYPE_SELECTION = [
('01', 'Factura Electrónica'),
('91', 'Nota Crédito'),
('92', 'Nota Débito'),
('03', 'Contingencia')
]
# Warnings
NOT_ENOUGH_DATA_WARN = """
No hay suficientes datos para obtener la información, Por favor ingrese
al menos los siguientes:
- Número de Resolución
- Prefijo
- Numeración Desde
- Numeración Hasta
"""
MULTI_RESOLUTION_WARN = """
Se encontro mas de una resolución con los mismos parametros.
"""
NO_RESOLUTION_WARN = """
No se encontro ninguna resolución que coincida con los datos."""
RESOLUTION_SAMPLE = """
Ejemplo: Resolución DIAN N° 00000000 desde PREFIJO 1 hasta PREFIJO 1000
"""
class ElectronicInvoiceResolution(models.Model):
_name = 'electronic.invoice.resolution'
_description = 'Resolucion de Facturacion Electronica'
name = fields.Char(string='Nombre', required=True)
number = fields.Char(string='Número de Resolución')
prefix = fields.Char('Prefijo', required=True)
from_number = fields.Integer(string='Numeración Desde', copy=False)
to_number = fields.Integer(string='Numeración Hasta', copy=False)
valid_date_from = fields.Date(string='Fecha Inicio Resolución', copy=False)
valid_date_to = fields.Date(string='Fecha Fin Resolución', copy=False)
id_param = fields.Integer(
string='ID Resolución', help=ID_PARAM_HELP, required=True, default=0)
technical_key = fields.Char(string='Clave Técnica', copy=False)
document_type = fields.Selection(
string='Tipo de Documento', selection=DOCUMENT_TYPE_SELECTION,
required=True, copy=False, help=DOCUMENT_TYPE_HELP)
description = fields.Text(
string='Texto de Resolución', help=RESOLUTION_SAMPLE)
company_id = fields.Many2one(
'res.company', 'Company',
default=lambda self: self.env.company,
index=True, required=True)
def get_resolution_xml(self):
self.ensure_one()
company = self.company_id
body_dict = {
"datos_conexion":
{
"token": company.software_token,
"documento": company.partner_id.ref_num,
"documentoT": (AVANCYS_NIT
if company.ei_software_operation == 'provider'
else company.partner_id.ref_num)
},
"Datos_software":
{
"codigo": company.software_code_dian,
"ambiente": "1"
}
}
data = 'json_data=' + \
base64.b64encode(json.dumps(body_dict).encode(
'utf-8')).decode('utf-8')
response = requests.post(URL_NUMBERING_XML, data=data,
headers=HEADERS, verify=False)
return str(response.content, 'utf-8')
def parse_resolution(self, resolution):
return {
'ResolutionNumber': re.search(
r'<c:ResolutionNumber>(.*?)</c:ResolutionNumber>', resolution).group(1),
'ResolutionDate': re.search(
r'<c:ResolutionDate>(.*?)</c:ResolutionDate>', resolution).group(1),
'Prefix': re.search(r'<c:Prefix>(.*?)</c:Prefix>', resolution).group(1),
'FromNumber': re.search(r'<c:FromNumber>(.*?)</c:FromNumber>', resolution).group(1),
'ToNumber': re.search(r'<c:ToNumber>(.*?)</c:ToNumber>', resolution).group(1),
'ValidDateFrom': re.search(
r'<c:ValidDateFrom>(.*?)</c:ValidDateFrom>', resolution).group(1),
'ValidDateTo': re.search(r'<c:ValidDateTo>(.*?)</c:ValidDateTo>', resolution).group(1),
'TechnicalKey': re.search(r'<c:TechnicalKey>(.*?)</c:TechnicalKey>',
resolution).group(1)
}
def resolucion_match(self, resolucion_match):
return reduce(operator.and_, [
self.number == resolucion_match.get('ResolutionNumber', False),
self.prefix == resolucion_match.get('Prefix', False),
str(self.from_number) == resolucion_match.get('FromNumber', False),
str(self.to_number) == resolucion_match.get('ToNumber', False),
])
def get_technical_key(self):
if not self.prefix or not self.number or not self.from_number or not self.to_number:
raise ValidationError(NOT_ENOUGH_DATA_WARN)
self.technical_key = ''
resolution_xml = self.get_resolution_xml()
resolutions = re.findall(
r'<c:NumberRangeResponse>([\s\S.]*?)</c:NumberRangeResponse>', resolution_xml)
if not resolutions:
return
resolutions_list = list(map(self.parse_resolution, resolutions))
matching_resolution = list(
filter(self.resolucion_match, resolutions_list))
if not matching_resolution:
raise ValidationError(NO_RESOLUTION_WARN)
if len(matching_resolution) > 1:
raise ValidationError(MULTI_RESOLUTION_WARN)
self.technical_key = matching_resolution[0]['TechnicalKey']

View File

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
from collections import OrderedDict
import base64
import json
import re
import requests
HEADERS = {
'Host': 'terabyte.com.co',
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Referer': 'https://www.avancyserp.com/',
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': '5580',
'Origin': 'https://www.avancyserp.com',
'Connection': 'close',
'Upgrade-Insecure-Requests': '1',
}
INVOICE = {
"datos_conexion": {},
"tipo_documento": {},
"basicos_factura": {},
"respuesta": {},
"param_basico": {},
"facturador": {},
"autorizacion_descarga": {},
"WithholdingTaxTotal": {},
"datos_empresa": {},
"datos_cliente": {},
"datos_transportadora": {},
"QR": {},
"Periodo_pago": {},
"Metodo_pago": {},
"Referencia_factura": {},
"Referencia_factura2": {},
"respuesta_discrepancia": {},
"order_de_referencia": {},
"Referencia_envio": {},
"Referencia_recibido": {},
"Terminos_de_entrega": {},
"Tasa_cambio": {},
"AdditionalDocumentReference": {},
"Anticipos": [],
"Productos_servicios": []
}
URL_XML = 'https://facturaelectronica.avancyserp.com/facturacion_electronica/v5.2/xml/formatos/GetXmlByDocumentKey.php'
URL_NUMBERING_XML = 'https://facturaelectronica.avancyserp.com/facturacion_electronica/v5.2/xml/formatos/GetNumberingRange.php'
DEFAULT_SERVICE_POST = 'https://www.avancyserp.com/response_ajax.php'
DEFAULT_SERVICE_URL = 'https://facturaelectronica.avancyserp.com/facturacion_electronica/v5.2/send.php'
DEFAULT_SERVICE_URL_GET = 'https://facturaelectronica.avancyserp.com/facturacion_electronica/v5.2/view_response.php'
AVANCYS_NIT = 900297700
DIAN_INVOICE_URL_BASE = 'https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey='

View File

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
from .http_helper import DEFAULT_SERVICE_URL
from .http_helper import DEFAULT_SERVICE_URL_GET
from .http_helper import DEFAULT_SERVICE_POST
EI_DATABASE_HELP = """
Base de Datos en la cual funcionará la Facturación Electrónica"""
EI_ENVIRONMENT_HELP = """
Indicador del tipo de ambiente de Facturación Electrónica que se usará en
esta Base de Datos"""
SERVICE_URL_HELP = """URL de envio de Facturación Electrónica del Proveedor"""
SERVICE_URL_GET_HELP = """URL de Respuesta de Proveedor"""
SERVICE_URL_POST_HELP = """
URL de envio de Facturación Electrónica del Facturador"""
SOFTWARE_TOKEN_HELP = """Token de Proveedor Tecnológico"""
SOFTWARE_CODE_DIAN_HELP = """Codigo de software dado por la DIAN"""
EI_TMP_PATH_HELP = """Directorio local para archivos temporales"""
EI_AUTOMATIC_GENERATION_HELP = """
Enviar factura electrónica automáticamente al validar la factura"""
ATTACH_CUSTOMER_ORDER_HELP = """
Adjuntar en el correo la orden de compra del cliente"""
ATTACH_DELIVERY_NOTE_HELP = """Adjuntar documento de remisión de pedido"""
ATTACH_INVOICE_DOCS_HELP = """
Adjuntar documentos adicionales al pdf y xml adjuntos en la factura"""
AUTO_ACCEPTANCE_EMAIL_HELP = """Enviar correo de aceptación al cliente
una vez la factura haya quedado aceptada por la DIAN"""
EI_ID_CUSTOMIZATION_HELP = """
Tipo de Operación principal de facturación electrónica de la compañía"""
TRIBUTARY_OBLIGATIONS_HELP = """
Usadas en la construcción del XML AttachedDocument"""
class ResCompany(models.Model):
_inherit = 'res.company'
# Basic config
electronic_invoice = fields.Boolean(
string='Activar Facturación Electrónica')
ei_database = fields.Char(
string='Base de Datos', default=lambda self: self.env.cr.dbname,
required=True, help=EI_DATABASE_HELP)
ei_environment = fields.Selection(
string='Ambiente',
selection=[
('production', u'Producción'), ('test', u'Habilitación')],
default='test', required=True, help=EI_ENVIRONMENT_HELP)
ei_software_operation = fields.Selection(
string='Operación de Software',
selection=[
('own', u'Software Propio'),
('provider', u'Proveedor Tecnológico')],
default='provider', required=True)
# Server
service_url = fields.Char(
string='URL Servicio', help=SERVICE_URL_HELP,
default=DEFAULT_SERVICE_URL)
service_url_get = fields.Char(
string='URL Respuesta', help=SERVICE_URL_GET_HELP,
default=DEFAULT_SERVICE_URL_GET)
service_url_post = fields.Char(
string='URL Peticion', help=SERVICE_URL_POST_HELP,
default=DEFAULT_SERVICE_POST)
software_token = fields.Char(
string='Token', help=SOFTWARE_TOKEN_HELP)
software_code_dian = fields.Char(
string='Codigo Software DIAN', help=SOFTWARE_CODE_DIAN_HELP)
ei_tmp_path = fields.Char(
string='Ruta de Archivos Temporales',
help=EI_TMP_PATH_HELP, default='/tmp')
# Policies
ei_automatic_generation = fields.Boolean(
string='Enviar Factura al Validar',
help=EI_AUTOMATIC_GENERATION_HELP)
attach_customer_order = fields.Boolean(
string='Adjuntar OC Cliente', help=ATTACH_CUSTOMER_ORDER_HELP)
attach_delivery_note = fields.Boolean(
string='Adjuntar Remisión', help=ATTACH_DELIVERY_NOTE_HELP)
attach_invoice_docs = fields.Boolean(
string='Envíar Adjuntos de Factura',
help=ATTACH_INVOICE_DOCS_HELP)
auto_acceptance_email = fields.Boolean(
string='Email de Aceptación Automático',
help=AUTO_ACCEPTANCE_EMAIL_HELP)
# Settings
ei_id_customization = fields.Selection(
string='Operación Principal',
selection=[('09', 'AIU'), ('10', 'Estandar'), ('11', 'Mandatos')],
required=True, default='10',
help=EI_ID_CUSTOMIZATION_HELP)
mail_server_id = fields.Many2one(
string='Servidor Email Preferido',
comodel_name='ir.mail_server')
ei_report_id = fields.Many2one(
string='Formato PDF', comodel_name='ir.actions.report',
domain=[('model', '=', 'account.move')])
tributary_obligations = fields.Char(
string='Obligaciones Tributarias',
help=TRIBUTARY_OBLIGATIONS_HELP,
default='R-99-PN')
invoice_batch_process = fields.Boolean(string='Procesamiento Masivo')

View File

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class ResPartner(models.Model):
_inherit = 'res.partner'
ei_email = fields.Char(string='E-mail Facturación Electrónica')

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.exceptions import ValidationError
DATASIZE_RATIO = 3.0 / 4.0 # converts from len to actual filesize
MAX_FILE_SIZE = 2000000
MAX_SIZE_WARN = """
El tamaño del archivo de la Orden de Compra Cliente supera los 2Mb permitidos"""
class SaleOrder(models.Model):
_inherit = 'sale.order'
customer_po_file = fields.Binary(
string='Orden de Compra Cliente', copy=False)
customer_po_name = fields.Char('Nombre OC Cliente', copy=False)
customer_po_policy = fields.Boolean(
string='Política Envío OC', related='company_id.attach_customer_order',
readonly=True)
@api.constrains('customer_po_file')
def _check_file_size(self):
if self.customer_po_file:
if len(self.customer_po_file) * DATASIZE_RATIO > MAX_FILE_SIZE:
raise ValidationError(MAX_SIZE_WARN)

View File

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class UomUom(models.Model):
_inherit = 'uom.uom'
code_dian = fields.Char(string='Código DIAN')

View File

@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
# pylint: disable=invalid-name, bare-except, eval-used, cell-var-from-loop
# pylint: disable=unused-wildcard-import,wildcard-import,too-many-locals
"""
XML Helper to build invoice
"""
from xml.etree.ElementTree import SubElement as ETSE
from xml.etree.ElementTree import Element as ETE
import xml.etree.ElementTree as ET
from .xml_schemas import ATTDOC_ATTRS
from .xml_schemas import Prefix as pfx
from .xml_schemas import NAMESPACES
for ns in NAMESPACES:
ET.register_namespace(ns, NAMESPACES[ns])
def set_document_root(ET, doc_type):
"""Attachem Document Creation"""
AttachedDocument = ET.Element(
doc_type,
attrib=ATTDOC_ATTRS
)
return AttachedDocument
def set_header(AttachedDocument, tags, tag_attributes):
UBLVersionID = ET.SubElement(AttachedDocument, pfx.cbc + 'UBLVersionID')
CustomizationID = ET.SubElement(
AttachedDocument, pfx.cbc + 'CustomizationID')
ProfileID = ET.SubElement(
AttachedDocument, pfx.cbc + 'ProfileID')
ProfileExecutionID = ET.SubElement(
AttachedDocument, pfx.cbc + 'ProfileExecutionID')
ID = ET.SubElement(
AttachedDocument, pfx.cbc + 'ID')
IssueDate = ET.SubElement(
AttachedDocument, pfx.cbc + 'IssueDate')
IssueTime = ET.SubElement(
AttachedDocument, pfx.cbc + 'IssueTime')
DocumentType = ET.SubElement(
AttachedDocument, pfx.cbc + 'DocumentType')
ParentDocumentID = ET.SubElement(
AttachedDocument, pfx.cbc + 'ParentDocumentID')
for tag in tags:
try:
eval(tag).text = tags[tag]
except:
pass
return AttachedDocument
def set_party(Party, tags, tag_attributes):
PartyTaxScheme = ET.SubElement(
Party, pfx.cac + 'PartyTaxScheme')
RegistrationName = ET.SubElement(
PartyTaxScheme, pfx.cbc + 'RegistrationName')
CompanyID = ET.SubElement(
PartyTaxScheme, pfx.cbc + 'CompanyID',
attrib=tag_attributes.get('CompanyID', {}))
TaxLevelCode = ET.SubElement(
PartyTaxScheme, pfx.cbc + 'TaxLevelCode',
attrib=tag_attributes.get('TaxLevelCode', {}))
TaxScheme = ET.SubElement(
PartyTaxScheme, pfx.cac + 'TaxScheme')
ID = ET.SubElement(
TaxScheme, pfx.cbc + 'ID')
Name = ET.SubElement(
TaxScheme, pfx.cbc + 'Name')
for tag in tags:
try:
eval(tag).text = tags[tag]
except:
pass
return Party
def set_sender_party(AttachedDocument, values, tag_attributes):
SenderParty = ET.SubElement(
AttachedDocument, pfx.cac + 'SenderParty')
set_party(SenderParty, values, tag_attributes)
return AttachedDocument
def set_receiver_party(AttachedDocument, values, tag_attributes):
ReceiverParty = ET.SubElement(
AttachedDocument, pfx.cac + 'ReceiverParty')
set_party(ReceiverParty, values, tag_attributes)
return AttachedDocument
def set_attchment(Document, tags, tag_attributes):
Attachment = ET.SubElement(
Document, pfx.cac + 'Attachment')
ExternalReference = ET.SubElement(
Attachment, pfx.cac + 'ExternalReference')
MimeCode = ET.SubElement(
ExternalReference, pfx.cbc + 'MimeCode')
EncodingCode = ET.SubElement(
ExternalReference, pfx.cbc + 'EncodingCode')
Description = ET.SubElement(
ExternalReference, pfx.cbc + 'Description')
for tag in tags:
try:
eval(tag).text = tags[tag]
except:
pass
return Document
def set_parent_document_line_reference(AttachedDocument, tags, tag_attributes):
ParentDocumentLineReference = ET.SubElement(
AttachedDocument, pfx.cac + 'ParentDocumentLineReference')
LineID = ET.SubElement(
ParentDocumentLineReference, pfx.cbc + 'LineID')
DocumentReference = ET.SubElement(
ParentDocumentLineReference, pfx.cac + 'DocumentReference')
ID = ET.SubElement(DocumentReference, pfx.cbc + 'ID')
UUID = ET.SubElement(DocumentReference, pfx.cbc + 'UUID',
attrib=tag_attributes.get('UUID', {}))
IssueDate = ET.SubElement(DocumentReference, pfx.cbc + 'IssueDate')
DocumentType = ET.SubElement(DocumentReference, pfx.cbc + 'DocumentType')
set_attchment(DocumentReference, tags, tag_attributes)
ResultOfVerification = ET.SubElement(
DocumentReference, pfx.cac + 'ResultOfVerification')
ValidatorID = ET.SubElement(
ResultOfVerification, pfx.cbc + 'ValidatorID')
ValidationResultCode = ET.SubElement(
ResultOfVerification, pfx.cbc + 'ValidationResultCode')
ValidationDate = ET.SubElement(
ResultOfVerification, pfx.cbc + 'ValidationDate')
ValidationTime = ET.SubElement(
ResultOfVerification, pfx.cbc + 'ValidationTime')
for tag in tags:
try:
eval(tag).text = tags[tag]
except:
pass
return AttachedDocument
def sanitize_utf_es(text):
chars = {
'&#241;': u'ñ', '&#225;': u'á', '&#233;': u'é', '&#237;': u'í',
'&#243;': u'ó', '&#250;': u'ú', '&#193;': u'Á', '&#201;': u'É',
'&#205;': u'Í', '&#211;': u'Ó', '&#218;': u'Ú'}
sanitized_text = text
for es_char, utf_char in chars.items():
sanitized_text = sanitized_text.replace(es_char, utf_char)
return sanitized_text
def build_xml_attached_document(xml_fe, app_response, values):
xml_declaration = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
document = set_document_root(ET, doc_type='AttachedDocument')
document = set_header(
document, values['header']['tags'], values['header']['attrs'])
document = set_sender_party(
document, values['sender']['tags'], values['sender']['attrs'])
document = set_receiver_party(
document, values['receiver']['tags'], values['receiver']['attrs'])
document = set_attchment(
document, values['attachment']['tags'], values['attachment']['attrs'])
document = set_parent_document_line_reference(
document, values['doc_line']['tags'], values['doc_line']['attrs'])
# return document
doc_string = xml_declaration + ET.tostring(document).decode('utf-8').replace(
'ei_xml_content', '<![CDATA[%s]]>' % xml_fe).replace(
'ei_app_response', '<![CDATA[%s]]>' % app_response)
return sanitize_utf_es(doc_string).encode('utf-8')

View File

@ -0,0 +1,40 @@
"""
Namespaces declarations
"""
NAMESPACES = {
'ds': "http://www.w3.org/2000/09/xmldsig#",
'cac': "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2",
'cbc': "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2",
'ccts': "urn:un:unece:uncefact:data:specification:CoreComponentTypeSchemaModule:2",
'ext': "urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2",
'xades': "http://uri.etsi.org/01903/v1.3.2#",
'xades141': "http://uri.etsi.org/01903/v1.4.1#"
}
ATTDOC_ATTRS = {
"xmlns:ext": "urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2",
"xmlns": "urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2",
}
cac = '{%s}' % NAMESPACES['cac']
cbc = '{%s}' % NAMESPACES['cbc']
ccts = '{%s}' % NAMESPACES['ccts']
ext = '{%s}' % NAMESPACES['ext']
xades = '{%s}' % NAMESPACES['xades']
xades141 = '{%s}' % NAMESPACES['xades141']
ds = 'ds:'
class DictMap(dict):
__getattr__ = dict.__getitem__
PREFIXMAP = {
'cac': cac,
'cbc': cbc,
'ccts': ccts,
'ext': ext,
'xades': xades,
'xades141': xades141,
}
Prefix = DictMap(PREFIXMAP)

View File

@ -0,0 +1,17 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="0">
<report
id="electronic_invoice_report"
model="account.move"
string="Factura Electrónica DIAN"
report_type="qweb-pdf"
name="electronic_invoice_dian.report_electronic_invoice"
file="electronic_invoice_dian.report_electronic_invoice"
attachment="(object.state == 'posted') and ((object.name or 'INV').replace('/','_')+'.pdf')"
print_report_name="(object._get_report_base_filename())"
groups="electronic_invoice_dian.group_user"
/>
</data>
</odoo>

View File

@ -0,0 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_electronic_invoice_resolution_manager,access_electronic_invoice_resolution_manager,model_electronic_invoice_resolution,electronic_invoice_dian.group_manager,1,1,1,1
access_electronic_invoice_resolution_user,access_electronic_invoice_resolution_user,model_electronic_invoice_resolution,electronic_invoice_dian.group_user,1,1,0,0
access_electronic_invoice_resolution_invoice_user,access_electronic_invoice_resolution_user,model_electronic_invoice_resolution,account.group_account_invoice,1,1,0,0
access_ei_transaction_log_manager,access_ei_transaction_log_manager,model_ei_transaction_log,electronic_invoice_dian.group_manager,1,1,1,1
access_ei_transaction_log_user,access_ei_transaction_log_user,model_ei_transaction_log,electronic_invoice_dian.group_user,1,1,0,0
access_ei_transaction_log_invoice_user,access_ei_transaction_log_user,model_ei_transaction_log,account.group_account_invoice,1,1,0,0
access_ei_multi_process_manager,access_ei_multi_process_manager,model_ei_multi_process,electronic_invoice_dian.group_manager,1,1,1,1
access_ei_multi_process_user,access_ei_multi_process_user,model_ei_multi_process,electronic_invoice_dian.group_user,1,1,1,0
access_ei_multi_process_invoice_user,access_ei_multi_process_user,model_ei_multi_process,account.group_account_invoice,1,1,1,0
access_ei_state_reset_manager,access_ei_state_Reset_manager,model_ei_state_reset,electronic_invoice_dian.group_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_electronic_invoice_resolution_manager access_electronic_invoice_resolution_manager model_electronic_invoice_resolution electronic_invoice_dian.group_manager 1 1 1 1
3 access_electronic_invoice_resolution_user access_electronic_invoice_resolution_user model_electronic_invoice_resolution electronic_invoice_dian.group_user 1 1 0 0
4 access_electronic_invoice_resolution_invoice_user access_electronic_invoice_resolution_user model_electronic_invoice_resolution account.group_account_invoice 1 1 0 0
5 access_ei_transaction_log_manager access_ei_transaction_log_manager model_ei_transaction_log electronic_invoice_dian.group_manager 1 1 1 1
6 access_ei_transaction_log_user access_ei_transaction_log_user model_ei_transaction_log electronic_invoice_dian.group_user 1 1 0 0
7 access_ei_transaction_log_invoice_user access_ei_transaction_log_user model_ei_transaction_log account.group_account_invoice 1 1 0 0
8 access_ei_multi_process_manager access_ei_multi_process_manager model_ei_multi_process electronic_invoice_dian.group_manager 1 1 1 1
9 access_ei_multi_process_user access_ei_multi_process_user model_ei_multi_process electronic_invoice_dian.group_user 1 1 1 0
10 access_ei_multi_process_invoice_user access_ei_multi_process_user model_ei_multi_process account.group_account_invoice 1 1 1 0
11 access_ei_state_reset_manager access_ei_state_Reset_manager model_ei_state_reset electronic_invoice_dian.group_manager 1 1 1 1

View File

@ -0,0 +1,24 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="0">
<record model="ir.module.category" id="module_category_electronic_invoice_dian">
<field name="name">Facturación Electrónica DIAN</field>
</record>
<record model="res.groups" id="group_manager">
<field name="name">Administrador</field>
<field name="category_id" ref="module_category_electronic_invoice_dian" />
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]" />
</record>
<record model="res.groups" id="group_user">
<field name="name">Usuario</field>
<field name="category_id" ref="module_category_electronic_invoice_dian" />
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]" />
</record>
<record model="ir.rule" id="electronic_invoice_resolution_rule">
<field name="name">Resolución de Facturación Electronica: multi-company</field>
<field name="model_id" ref="model_electronic_invoice_resolution" />
<field name="global" eval="True" />
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,60 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="0">
<record id="electronic_invoice_customer_acknowlegement" model="mail.template">
<field name="name">Envio de factura electrónica</field>
<field name="email_from">${object.company_id.email or ''}</field>
<field name="subject">${object.company_id.partner_id.ref_num};${object.company_id.partner_id.name};${object.name};${object.journal_id.resolution_id.document_type};${object.company_id.partner_id.name}</field>
<field name="model_id" ref="account.model_account_move" />
<field name="email_to">${(object.partner_id.ei_email or object.partner_id.email)}</field>
<field name="partner_to">${False}</field>
<field name="auto_delete" eval="True" />
<field name="body_html">
<![CDATA[
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>${object.name}</title>
<style>
span.oe_mail_footer_access {
display: block;
text-align: center;
color: grey;
}
</style>
</head>
<body>
<div
style="border-radius: 2px; max-width: 1200px; height: auto;margin-left: auto;margin-right: auto;background-color:#f9f9f9;">
<div style="height:auto;text-align: center;font-size : 30px;color: #8A89BA;">
Factura electronica <strong>${object.name}</strong>
</div>
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
<strong style="margin-left:12px">Estimado: ${object.partner_id.name}</strong>,<br />
<p style="margin-left:12px">Se ha generado un factura electrónica a su nombre</p>
<strong style="margin-left:12px">Emisor: ${object.company_id.partner_id.name}</strong>
<p style="margin-left:12px">Adjunto encontrará los archivos de la factura, por favor indique la
aceptación o rechazo de la misma</p>
</div>
<br><br><br><br>
<div style="height: auto;width:450px; margin:0 auto;padding-top:20px;padding-bottom:40px;">
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#3b9c17;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="/invoice/dian/accept?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&id=${object.id}">Aceptar</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#d66058;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="/invoice/dian/reject?db=${'dbname' in ctx and ctx['dbname'] or '' }&token=${object.access_token}&id=${object.id}">Rechazar</a>
</div>
</div>
<div style="height: 50px;text-align: center;font-size : 10px;border-collapse: separate;margin-top:10px">
<p style="margin-left:12px">
Por Avancys SAS <a href="https://www.avancys.com">https://www.avancys.com</a> Proveedor tecnologico.
</p>
</div>
</body>
</html>
]]>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="customer_accept_invoice">
<title>Factura Aceptada</title>
<div style="border-radius: 2px; max-width: 1200px; height: auto;margin-left: auto;margin-right: auto;background-color:#f9f9f9;">
<div style="height:auto;text-align: center;font-size : 30px;color: #3b9c17;">
<strong>Factura Aceptada</strong>
</div>
<div style="height: 50px;text-align: center;font-size : 10px;border-collapse: separate;margin-top:10px">
<p style="margin-left:12px">
Por Avancys SAS <a href="https://www.avancys.com">https://www.avancys.com</a> Proveedor tecnologico.
</p>
</div>
</div>
</template>
<template id="customer_reject_invoice">
<title>Factura Rechazada</title>
<div style="border-radius: 2px; max-width: 1200px; height: auto;margin-left: auto;margin-right: auto;background-color:#f9f9f9;">
<div style="height:auto;text-align: center;font-size : 30px;color: #d66058;">
<strong>Factura Rechazada</strong>
</div>
<div style="height: 50px;text-align: center;font-size : 10px;border-collapse: separate;margin-top:10px">
<p style="margin-left:12px">
Por Avancys SAS <a href="https://www.avancys.com">https://www.avancys.com</a> Proveedor tecnologico.
</p>
</div>
</div>
</template>
</data>
</odoo>

View File

@ -0,0 +1,396 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="0">
<template id="report_electronic_invoice_document">
<t t-call="web.basic_layout">
<style>
.infolabel {
font-size: 11px;
text-align: left;
font-weight: bold;
border-bottom: 0px
}
div.infolabel {
font-size: 11px;
text-align: center;
font-weight: bold;
border-bottom: 0px
}
div.infolabelbig {
font-size: 14px;
text-align: center;
font-weight: bold;
border-bottom: 0px
}
span.infodata{
font-size: 11px;
text-align: left;
border-bottom: 0px
}
span.infodatabottom{
font-size: 11px;
text-align: left;
border-bottom: 0px
}
strong.infodatabottom{
font-size: 11px;
text-align: left;
border-bottom: 0px
}
tr.inforow {
line-height: 25px;
min-height: 25px;
height: 25px;
}
.borderless td, .borderless tr {
border: none;
line-height: 15px;
min-height: 15px;
padding: 1px;
}
</style>
<div class="header">
<div class="row">
<div class="col-3">
<img t-if="o.company_id.logo" t-att-src="image_data_uri(o.company_id.logo)" width="130" height="auto" />
</div>
<div class="col-3">
<span style="font-size:10px" t-field="o.company_id.partner_id.name" />
<span style="font-size:10px">NIT </span><span style="font-size:10px" t-field="o.company_id.partner_id.ref_num" />
<br/>
<span style="font-size:10px" t-field="o.company_id.street" />
<span style="font-size:10px" t-field="o.company_id.street2" />
<br/>
<span style="font-size:10px" t-field="o.company_id.partner_id.city_id.name" />
,
<span style="font-size:10px" t-field="o.company_id.partner_id.country_id.name" />
<br/>
<span style="font-size:10px">TELEFONO </span><span style="font-size:10px" t-field="o.company_id.phone" />
<span style="font-size:10px" t-if="o.company_id.partner_id.mobile">/</span>
<span style="font-size:10px" t-field="o.company_id.partner_id.mobile" />
<br/>
<span style="font-size:10px" t-field="o.company_id.email" />
</div>
<div class="col-3">
</div>
<div t-if="o.ei_qr" class="col-3">
<img t-att-src="'/report/barcode/?type=QR&amp;value=%s&amp;width=%s&amp;height=%s' % (o.ei_qr, 110, 110)" />
</div>
</div>
</div>
<div class="row">
<div class="col-6 card card-body">
<table class="table borderless">
<tr class="inforow">
<td>
<span class="w-15 infolabel">
CLIENTE
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.name" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
NIT
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.ref_num" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
DIRECCIÓN
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.street" />
<span class="infodata" t-field="o.partner_id.street2" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
CIUDAD
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.city_id.name" />
-
<span class="infodata" t-field="o.partner_id.country_id.name" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
TELEFONO
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.phone" />
<span class="infodata" t-if="o.partner_id.mobile">/</span>
<span class="infodata" t-field="o.partner_id.mobile" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
EMAIL
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.email" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
REFERENCIA
</span>
</td>
<td>
<span class="infodata" t-field="o.ref" />
</td>
</tr>
</table>
</div>
<div class="col-6 card">
<div class="infolabelbig card-header">
<span t-if="o.move_type == 'out_invoice' and o.ei_cufe">FACTURA ELECTRÓNICA DE VENTA</span>
<span t-if="o.move_type == 'out_invoice' and not o.ei_cufe">FACTURA DE VENTA</span>
<span t-if="o.move_type == 'out_invoice' and o.ei_cude">NOTA DÉBITO ELECTRONICA</span>
<span t-if="o.move_type == 'out_refund' and o.ei_cude">NOTA CRÉDITO ELECTRONICA</span>
<span t-if="o.move_type == 'out_refund' and not o.ei_cude">NOTA CRÉDITO</span>
<span t-if="o.move_type == 'out_invoice' and not o.ei_cude and o.debit_origin_id">NOTA DÉBITO</span>
<span t-if="o.move_type == 'out_invoice' and o.state == 'cancel'">CANCELADA</span>
<span t-if="o.move_type == 'in_invoice'">DOCUMENTO DE SOPORTE</span>
<span t-if="o.name != '/'" t-field="o.name"/>
</div>
<div class="card-body">
<table class="table borderless">
<tr class="inforow">
<td>
<span class="w-15 infolabel">
FECHA DE EMISION
</span>
</td>
<td>
<span class="infodata" t-field="o.ei_generation_date" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
FECHA DE VENCIMIENTO
</span>
</td>
<td>
<span class="infodata" t-field="o.invoice_date_due" />
</td>
</tr>
<tr t-if="o.invoice_payment_term_id">
<td>
<span class="w-15 infolabel">
TERMINO DE PAGO
</span>
</td>
<td>
<span class="infodata" t-field="o.invoice_payment_term_id.name" />
</td>
</tr>
</table>
<div class="row">
<t t-if="o.ei_cufe">
<div class="col-12" style="font-size:8px; float:left; text-align:left; white-space: pre;"><strong>CUFE : </strong><span t-field="o.ei_cufe" /></div>
</t>
<t t-if="o.ei_cude">
<div class="col-12" style="font-size:8px; float:left; text-align:left; white-space: pre;"><strong>CUDE : </strong><span t-field="o.ei_cude" /></div>
</t>
</div>
</div>
</div>
</div>
<div class="page">
<t t-set="display_discount" t-value="any(l.discount for l in o.invoice_line_ids)"/>
<table class="table table-sm o_main_table" name="invoice_line_table">
<thead>
<tr>
<th name="th_defaultcode" class="text-left"><span class="infolabel"></span></th>
<th name="th_defaultcode" class="text-left"><span class="infolabel">REFERENCIA</span></th>
<th name="th_description" class="text-left"><span class="infolabel">DESCRIPCIÓN</span></th>
<th name="th_quantity" class="text-right"><span class="infolabel">CANTIDAD</span></th>
<th name="th_priceunit" t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}"><span class="infolabel">PRECIO UNIT.</span></th>
<th name="th_price_unit" t-if="display_discount" t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span class="infolabel">DESC.%</span>
</th>
<th name="th_taxes" t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}"><span class="infolabel">IMPUESTOS</span></th>
<th name="th_subtotal" class="text-right">
<span groups="account.group_show_line_subtotals_tax_excluded" class="infolabel">VALOR</span>
<span groups="account.group_show_line_subtotals_tax_included" class="infolabel">PRECIO TOTAL</span>
</th>
</tr>
</thead>
<tbody class="invoice_tbody">
<t t-set="current_subtotal" t-value="0"/>
<t t-set="lines" t-value="o.invoice_line_ids.sorted(key=lambda l: (-l.sequence, l.date, l.move_name, -l.id), reverse=True)"/>
<t t-set="n_item" t-value="0"/>
<t t-foreach="lines" t-as="line">
<t t-set="n_item" t-value="n_item+1"/>
<t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
<t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''">
<t t-if="not line.display_type" name="account_invoice_line_accountable">
<td name="account_invoice_line_number"><span class="infodatabottom" t-esc="n_item"/></td>
<td name="account_invoice_default_code"><span class="infodatabottom" t-field="line.product_id.default_code" t-options="{'widget': 'text'}"/></td>
<td name="account_invoice_line_name"><span class="infodatabottom" t-field="line.name" t-options="{'widget': 'text'}"/></td>
<td class="text-right">
<span class="infodatabottom" t-field="line.quantity"/>
<span class="infodatabottom" t-field="line.product_uom_id" groups="uom.group_uom"/>
</td>
<td t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span class="text-nowrap infodatabottom" t-field="line.price_unit"/>
</td>
<td t-if="display_discount" t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span class="text-nowrap infodatabottom" t-field="line.discount"/>
</td>
<td t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span class="infodatabottom" t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_ids))" id="line_tax_ids"/>
</td>
<td class="text-right o_price_total">
<span class="text-nowrap infodatabottom" t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
<span class="text-nowrap infodatabottom" t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
</td>
</t>
<t t-if="line.display_type == 'line_section'">
<td colspan="99">
<span t-field="line.name" t-options="{'widget': 'text'}"/>
</td>
<t t-set="current_section" t-value="line"/>
<t t-set="current_subtotal" t-value="0"/>
</t>
<t t-if="line.display_type == 'line_note'">
<td colspan="99">
<span t-field="line.name" t-options="{'widget': 'text'}"/>
</td>
</t>
</tr>
<t t-if="current_section and (line_last or lines[line_index+1].display_type == 'line_section')">
<tr class="is-subtotal text-right">
<td colspan="99">
<strong class="mr16">SUBTOTAL</strong>
<span t-esc="current_subtotal" t-options="{&quot;widget&quot;: &quot;monetary&quot;, &quot;display_currency&quot;: o.currency_id}"/>
</td>
</tr>
</t>
</t>
</tbody>
</table>
<div>
<span class="infodata" t-esc="n_item"></span> <span class="infodata" >Items</span>
</div>
<br/>
<div class="clearfix">
<div id="total" class="row">
<div class="col-6">
<t t-if="o.journal_id.resolution_id.description">
<div class="col-12" style="font-size:10px; float:left; text-align:left;"><span t-field="o.journal_id.resolution_id.description" /></div>
</t>
</div>
<div class="col-6">
<table class="table table-sm" style="page-break-inside: avoid;">
<tr class="border-black o_subtotal" style="">
<td><strong class="infodatabottom">SUBTOTAL</strong></td>
<td class="text-right">
<span class="infodatabottom" t-field="o.amount_untaxed"/>
</td>
</tr>
<t t-foreach="o.amount_by_group" t-as="amount_by_group">
<tr style="">
<t t-if="len(o.line_ids.filtered(lambda line: line.tax_line_id)) in [0, 1] and o.amount_untaxed == amount_by_group[2]">
<td><span class="text-nowrap infodatabottom" t-esc="amount_by_group[0]"/></td>
<td class="text-right o_price_total">
<span class="text-nowrap infodatabottom" t-esc="amount_by_group[3]"/>
</td>
</t>
<t t-else="">
<td>
<span class="infodatabottom" t-esc="amount_by_group[0]"/>
<span class="text-nowrap infodatabottom"> sobre
<t t-esc="amount_by_group[4]"/>
</span>
</td>
<td class="text-right o_price_total">
<span class="text-nowrap infodatabottom" t-esc="amount_by_group[3]"/>
</td>
</t>
</tr>
</t>
<tr class="border-black o_total">
<td><strong class="infodatabottom">TOTAL</strong></td>
<td class="text-right">
<span class="text-nowrap infodatabottom" t-field="o.amount_total"/>
</td>
</tr>
</table>
</div>
</div>
</div>
<p t-if="o.narration" name="comment">
<span class="infodata" t-field="o.narration"/>
</p>
<p t-if="o.fiscal_position_id.note" name="note">
<span class="infodata" t-field="o.fiscal_position_id.note"/>
</p>
<p t-if="o.invoice_incoterm_id" name="incoterm">
<strong class="infodatabottom">Incoterm: </strong><span class="infodata" t-field="o.invoice_incoterm_id.code"/> - <span class="infodata" t-field="o.invoice_incoterm_id.name"/>
</p>
</div>
<div class="footer">
<t t-if="bool(o.ei_cufe or o.ei_cude)">
<span style="font-size:9px"><b>FECHA DE VALIDACION : </b></span><span style="font-size:9px" t-esc="o.ei_validation_date"/><br/>
<span style="font-size:9px"><b>INFORMACIÓN : </b></span><a style="font-size:9px">https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=<span t-esc="o.ei_cufe or o.ei_cude" /></a>
</t>
<div class="text-center border-top" >
<small>
<strong style="font-size:10px">
<span style="font-size:10px" t-field="o.company_id.website" />
</strong>
</small>
</div>
<div class="text-left">
<p style="font-size:9px; margin-bottom: 10px">AvancysERP, por Avancys SAS, NIT 900297700</p>
</div>
</div>
</t>
</template>
<template id="report_electronic_invoice">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-set="lang" t-value="o.invoice_user_id.sudo().lang if o.move_type in ('in_invoice', 'in_refund') else o.partner_id.lang"/>
<t t-call="electronic_invoice_dian.report_electronic_invoice_document" t-lang="lang"/>
</t>
</t>
</template>
</data>
</odoo>

View File

@ -0,0 +1,22 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data>
<record id="view_account_journal_form" model="ir.ui.view">
<field name="name">account.journal.ei.view.form</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form" />
<field name="arch" type="xml">
<xpath expr="//page[@name='advanced_settings']" position="after">
<page string="Facturación Electrónica" groups="electronic_invoice_dian.group_manager">
<group>
<group>
<field name="resolution_id" />
</group>
<group></group>
</group>
</page>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,66 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="0">
<record id="view_move_form" model="ir.ui.view">
<field name="name">account.move.ei.view.form</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form" />
<field name="arch" type="xml">
<xpath expr="//page[@name='other_info']" position="after">
<page string="Factura Electrónica" attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}" groups="electronic_invoice_dian.group_manager,electronic_invoice_dian.group_user">
<button name="generate_electronic_invoice" type="object" string="Enviar Factura" class="oe_stat_button" icon="fa-upload" attrs="{'invisible': ['|', ('ei_state', 'not in', ('pending','dian_reject','supplier_reject','exception')), ('state','not in',('open','paid','posted'))]}" />
<button name="get_invoice_attachments" type="object" string="Recargar Adjuntos" class="oe_stat_button" icon="fa-refresh" attrs="{'invisible': ['|', ('ei_state', 'in', ('customer_reject','customer_accept')), ('state','not in',('open','paid','posted'))]}" help="Cargar archivos XML-PDF para esta factura" />
<button name="resend_acknowlegement_email" type="object" string="Enviar FE por Email" class="oe_stat_button" icon="fa-envelope-o" attrs="{'invisible': ['|', ('ei_state', '!=', 'dian_accept'), ('state', 'not in', ('open','paid','posted'))]}" style="float: right;" />
<group>
<field name="ei_cufe" attrs="{'invisible':['|', '|', ('reversed_entry_id', '!=', False), ('debit_origin_id', '!=', False), ('out_refund_id', '!=', False)]}" />
<field name="ei_cude" attrs="{'invisible':[('reversed_entry_id', '=', False), ('debit_origin_id', '=', False), ('out_refund_id', '=', False)]}" />
<field name="ei_qr" />
</group>
<group>
<group>
<field name="ei_generation_date" />
<!-- <field name="ei_validation_date" /> -->
</group>
<group>
<label for="ei_state" />
<div class="o_row">
<field name="ei_state" />
<button name="%(electronic_invoice_dian.action_ei_state_reset)d" type="action" class="oe_link" attrs="{'invisible': [('ei_state', '=', 'pending')]}" groups="electronic_invoice_dian.group_manager" icon="fa-rotate-left" help="Revertir Estado" />
</div>
</group>
</group>
<group string="Logs">
<field name="transaction_log_ids" readonly="1" nolabel="1">
<tree>
<field name="date" />
<field name="document_type" />
<field name="document_state" />
</tree>
</field>
</group>
</page>
</xpath>
<xpath expr="//field[@name='invoice_vendor_bill_id']" position="after">
<field name="out_refund_id" domain="[('partner_id', '=', partner_id)]" attrs="{'invisible':[('move_type', '!=', 'out_refund')]}" />
<field name="invoice_reference" />
</xpath>
</field>
</record>
<record id="view_account_invoice_filter" model="ir.ui.view">
<field name="name">account.move.ei.search.form</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_account_invoice_filter" />
<field name="arch" type="xml">
<xpath expr="//group/filter[@name='duedate']" position="after">
<filter name="group_by_ei_state" string="Estado FE" context="{'group_by':'ei_state'}" />
</xpath>
<xpath expr="//filter[@name='due_date']" position="after">
<separator />
<filter name="eifailed" string="FE Fallidas" domain="[('ei_state', 'in', ('exception', 'dian_deject'))]" />
<filter name="eirejected" string="FE Rechazadas" domain="[('ei_state', '=', 'customer_reject')]" />
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data>
<record id="view_tax_form" model="ir.ui.view">
<field name="name">account.tax.ei.view.form</field>
<field name="model">account.tax</field>
<field name="inherit_id" ref="account.view_tax_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='company_id']" position="before">
<field name="ei_code" groups="electronic_invoice_dian.group_user,electronic_invoice_dian.group_manager" />
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,30 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="0">
<record id="ei_transaction_log_form" model="ir.ui.view">
<field name="name">ei.transaction.log.form</field>
<field name="model">ei.transaction.log</field>
<field name='type'>form</field>
<field name="arch" type="xml">
<form string="Log de Factura Electrónica" create="false" edit="false">
<sheet>
<group>
<group>
<field name="date" />
</group>
<group>
<field name="document_state" />
</group>
</group>
<notebook>
<page string="Contenido">
<field name="data" />
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,71 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="0">
<record id="electronic_invoice_resolution_view_form" model="ir.ui.view">
<field name="name">electronic.invoice.resolution.view.form</field>
<field name="model">electronic.invoice.resolution</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<group>
<field name="name" />
<field name="document_type" />
<field name="number" attrs="{'invisible':[('document_type', 'in', ('91', '92'))]}" />
<field name="prefix" />
<field name="from_number" attrs="{'invisible':[('document_type', 'in', ('91', '92'))]}" />
<field name="to_number" attrs="{'invisible':[('document_type', 'in', ('91', '92'))]}" />
</group>
<group>
<field name="id_param" />
<field name="valid_date_from" attrs="{'invisible':[('document_type', 'in', ('91', '92'))]}" />
<field name="valid_date_to" attrs="{'invisible':[('document_type', 'in', ('91', '92'))]}" />
</group>
</group>
<label for="technical_key" attrs="{'invisible':[('document_type', 'in', ('91', '92'))]}" />
<div class="o_row" attrs="{'invisible':[('document_type', 'in', ('91', '92'))]}">
<field name="technical_key" />
<button name="get_technical_key" type="object" class="oe_link" groups="electronic_invoice_dian.group_manager" icon="fa-key" help="Consultar Clave Técnica" />
</div>
<br />
<label for="description"></label>
<field name="description" />
</sheet>
</form>
</field>
</record>
<record id="electronic_invoice_resolution_view_tree" model="ir.ui.view">
<field name="name">electronic.invoice.resolution.view.tree</field>
<field name="model">electronic.invoice.resolution</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="number" />
<field name="prefix" />
<field name="from_number" />
<field name="to_number" />
<field name="valid_date_from" />
<field name="valid_date_to" />
<field name="document_type" />
</tree>
</field>
</record>
<record id="electronic_invoice_resolution_action" model="ir.actions.act_window">
<field name="name">Resolución de Numeración</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">electronic.invoice.resolution</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Crear Resolución de Facturación Electŕonica
</p>
<p>
Las Resoluciones de numeración son expedidas por la DIAN
y son usadas por cada diario de facturación electrónica
</p>
</field>
</record>
<menuitem id="electronic_invoice_resolution_menu_act" name="Resoluciónes" parent="electronic_invoice_dian.electronic_invoice_menu" action="electronic_invoice_dian.electronic_invoice_resolution_action" sequence="1" />
</data>
</odoo>

View File

@ -0,0 +1,63 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="0">
<record id="view_company_form" model="ir.ui.view">
<field name="name">res.company.ei.view.form</field>
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form" />
<field name="arch" type="xml">
<xpath expr="//page[@name='general_info']" position="after">
<page string="Facturación Electrónica" groups="electronic_invoice_dian.group_manager">
<group>
<group>
<field name="electronic_invoice" />
<field name="ei_database" attrs="{'required': [('electronic_invoice', '=', True)]}" />
</group>
<group>
<field name="ei_environment" attrs="{'required': [('electronic_invoice', '=', True)]}" />
<field name="ei_software_operation" attrs="{'required': [('electronic_invoice', '=', True)]}" />
</group>
</group>
<notebook>
<page string="Servicio WEB" name="web_service">
<group>
<group>
<field name="service_url" attrs="{'required': [('electronic_invoice', '=', True)]}" invisible="1" />
<field name="service_url_get" attrs="{'required': [('electronic_invoice', '=', True)]}" invisible="1" />
<field name="service_url_post" attrs="{'required': [('electronic_invoice', '=', True)]}" />
<field name="software_token" attrs="{'required': [('electronic_invoice', '=', True)]}" />
<field name="software_code_dian" attrs="{'required': [('electronic_invoice', '=', True)]}" />
<field name="ei_tmp_path" attrs="{'required': [('electronic_invoice', '=', True)]}" />
</group>
<group></group>
</group>
</page>
<page string="Políticas" name="policies">
<group>
<group>
<field name="ei_automatic_generation" />
<field name="auto_acceptance_email" />
<field name="invoice_batch_process" invisible="1" />
</group>
<group>
<field name="attach_customer_order" />
<field name="attach_delivery_note" />
<field name="attach_invoice_docs" />
</group>
</group>
</page>
<page string="Configuraciónes" name="settings">
<group string="Tipo de Operación">
<field name="ei_id_customization" />
<field name="tributary_obligations" />
<field name="ei_report_id" />
<field name="mail_server_id" />
</group>
</page>
</notebook>
</page>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,19 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data>
<record id="view_partner_form" model="ir.ui.view">
<field name="name">res.partner.ei.view.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="arch" type="xml">
<xpath expr="//sheet/notebook/page[@name='internal_notes']" position="after">
<page name="electronic_invoice" string="Facturación Electrónica" groups="electronic_invoice_dian.group_user,electronic_invoice_dian.group_manager">
<group name="group_ei_email">
<field name="ei_email" />
</group>
</page>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,17 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="1">
<record id="view_order_form" model="ir.ui.view">
<field name="name">sale.order.ei.view.form</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<xpath expr="//page[@name='other_information']/group/group/field[@name='invoice_status']" position="after">
<field name="customer_po_policy" invisible="1" />
<field name="customer_po_name" invisible="1" />
<field name="customer_po_file" attrs="{'readonly': [('state', 'not in', ['draft', 'sent'])], 'invisible': [('customer_po_policy', '=', False)]}" filename="customer_po_name" />
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data>
<record id="product_uom_form_view" model="ir.ui.view">
<field name="name">uom.uom.ei.view.form</field>
<field name="model">uom.uom</field>
<field name="inherit_id" ref="uom.product_uom_form_view" />
<field name="arch" type="xml">
<xpath expr="//field[@name='rounding']" position="after">
<field name="code_dian" groups="electronic_invoice_dian.group_manager,electronic_invoice_dian.group_user" />
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
"""
Multi Invoice Process
"""
from . import ei_multi_process
from . import ei_state_reset

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class EIMultiProcess(models.TransientModel):
_name = 'ei.multi.process'
_description = 'Wizard para enviar multiples facturas electrónica'
@api.onchange('invoices')
def populate_invoices(self):
invoices = self.env['account.move'].browse(
self._context['active_ids'])
txt = ''
for invoice in invoices.filtered(
lambda invoice: invoice.move_type in (
'out_invoice', 'out_refund')
and invoice.state == 'posted'
and invoice.ei_state in ('pending', 'dian_reject')):
txt += invoice.name + '\n'
self.invoices = txt
invoices = fields.Text(string='Facturas por Procesar', readonly=True,
help="Facturas con Estado 'No Transferido'")
def send_multiple_invoices(self):
active_invoices = self.env['account.move'].browse(
self._context['active_ids'])
to_send_invoices = active_invoices.filtered(
lambda invoice: invoice.move_type in ('out_invoice', 'out_refund')
and invoice.state == 'posted'
and invoice.ei_state in ('pending', 'dian_reject'))
if not to_send_invoices:
return
if self.env.user.company_id.invoice_batch_process:
ei_batch_process = self.env['ei.batch.process']
ei_batch_process.process_batch(to_send_invoices)
else:
for invoice in to_send_invoices:
invoice.generate_electronic_invoice()

View File

@ -0,0 +1,30 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data noupdate="1">
<record id="ei_multi_process_view_form" model="ir.ui.view">
<field name="name">ei.multi.process.view.form</field>
<field name="model">ei.multi.process</field>
<field name="arch" type="xml">
<form string="">
<group string="Facturas por Procesar">
<field name="invoices" nolabel="1" />
</group>
<h4 class="oe_grey">Facturas con Estado No Transferido o Rechazado DIAN</h4>
<footer>
<button name="send_multiple_invoices" string="Confirmar" type="object" class="oe_stat_button" icon="fa-upload" />
<button string="Cancel" class="oe_stat_button" icon="fa-times" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_ei_multi_process" model="ir.actions.act_window">
<field name="name">Enviar Factura Electrónica</field>
<field name="res_model">ei.multi.process</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="electronic_invoice_dian.ei_multi_process_view_form" />
<field name="target">new</field>
<field name="binding_model_id" ref="account.model_account_move" />
<field name="binding_view_types">list</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class EIStateReset(models.TransientModel):
_name = 'ei.state.reset'
_description = 'Wizard para devolver el estado de la factura electrónica'
def reset_ei_state(self):
self.env['account.move'].browse(
self._context['active_id']).ei_state = 'pending'

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<data>
<data>
<record id="ei_state_reset_view" model="ir.ui.view">
<field name="name">Revertir Estado de Factura Electrónica</field>
<field name="model">ei.state.reset</field>
<field name="arch" type="xml">
<form string="Revertir Estado de Factura Electrónica">
<h4>El estado sera revertido a No Transferido, ¿Desea Continuar?</h4>
<h5>El Log generado previamente, no será eliminado."</h5>
<footer>
<button name="reset_ei_state" string="Confirmar" type="object" class="oe_stat_button" icon="fa-check-circle" />
<button string="Cancel" class="oe_stat_button" icon="fa-times-circle" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_ei_state_reset" model="ir.actions.act_window">
<field name="name">Resetear Estado de Factura Electrónica</field>
<field name="res_model">ei.state.reset</field>
<field name="view_mode">form</field>
<field name="view_id" ref="ei_state_reset_view" />
<field name="target">new</field>
</record>
</data>
</data>

View File

@ -0,0 +1,40 @@
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3
=============================
Colombian Account E-Invoicing
=============================
Configuacion del Server:
Configurar "server_wide_modules" en el archivo de configuracion para que el modulo funcione correctamente.
Ejemplo:
server_wide_modules = web,web_kanban,l10n_co_account_e_invoicing
Configuacion:
1) Configurar Secuencias DIAN, principalmente la clave técnica:
.. image:: https://raw.githubusercontent.com/odooloco/l10n-colombia/10.0/l10n_co_account_e_invoicing/static/images/ir_sequence.png
2) Configurar datos de la compañía:
.. image:: https://raw.githubusercontent.com/odooloco/l10n-colombia/10.0/l10n_co_account_e_invoicing/static/images/res_company.png
3) Hacer pruebas con las facturas:
.. image:: https://raw.githubusercontent.com/odooloco/l10n-colombia/10.0/l10n_co_account_e_invoicing/static/images/account_invoice.png
4) Resultados:
.. image:: https://raw.githubusercontent.com/odooloco/l10n-colombia/10.0/l10n_co_account_e_invoicing/static/images/dian_document.png
Credits
=======
Contributors
------------
* Joan Marín <https://github.com/JoanMarin>

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models
from . import controllers

View File

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Colombian Account E-Invoicing",
"category": "E-Invoicing",
"version": "14.0.1.0.0",
"author": "Avancys SAS",
"website": "https://www.avancys.com/",
"license": "AGPL-3",
"summary": "Colombian Account E-Invoicing",
"depends": [
"l10n_co_account_move_discrepancy_response",
"l10n_co_account_move_payment_mean",
"l10n_co_account_tax_group_type",
"l10n_co_base_location",
"l10n_co_partner_person_type",
"l10n_co_partner_vat",
"l10n_co_uom",
"l10n_co_sequence_resolution",
"account_fiscal_year",
"partner_coc",
"partner_commercial_name",
"partner_fax",
"product_brand",
"product_manufacturer"
],
"external_dependencies": {
"python": [
"pyopenssl",
"xades",
],
},
"data": [
"security/ir.model.access.csv",
"data/account_fiscal_position_party_tax_scheme_data.xml",
"data/account_fiscal_position_tax_level_code_data.xml",
"data/ir_cron_data.xml",
"data/ir_module_category_data.xml",
"data/mail_template_data.xml",
"data/product_scheme_data.xml",
"data/res_groups_data.xml",
"views/account_fiscal_position_party_tax_scheme_views.xml",
"views/account_fiscal_position_tax_level_code_views.xml",
"views/account_fiscal_position_views.xml",
"views/account_fiscal_year_views.xml",
"views/account_move_views.xml",
"views/account_move_dian_document_views.xml",
"views/account_tax_group_views.xml",
"views/ir_sequence_views.xml",
"views/product_template_views.xml",
"views/res_company_views.xml",
"views/res_partner_views.xml",
"report/template_report.xml",
"report/report.xml",
],
"installable": True,
}

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import main

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from datetime import datetime
from pytz import timezone
from odoo import http
from odoo.http import request
class DianDocumentController(http.Controller):
@http.route('/dian/document/accept', type='http', auth="none")
def accept(self, db, token, id, **kwargs):
request.session.db = db
move_id = request.env['account.move'].sudo().search([
('access_token', '=', token), ('id', '=', id)])
if move_id and not move_id.accepted_rejected_datetime:
move_id.write({
'is_accepted_rejected': True,
'accepted_rejected_datetime': datetime.now().replace(
tzinfo=None), #timezone('UTC')),
'dian_document_state': 'customer_accept'})
return request.render('l10n_co_account_e_invoicing.customer_accept_invoice', {})
return
@http.route('/dian/document/reject', type='http', auth="none")
def reject(self, db, token, id, **kwargs):
request.session.db = db
move_id = request.env['account.move'].sudo().search([
('access_token', '=', token), ('id', '=', id)])
if move_id and not move_id.accepted_rejected_datetime:
move_id.write({
'is_accepted_rejected': False,
'accepted_rejected_datetime': datetime.now().replace(
tzinfo=None), #timezone('UTC')),
'dian_document_state': 'customer_reject'})
return request.render('l10n_co_account_e_invoicing.customer_reject_invoice', {})
return

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_fiscal_position_party_tax_scheme_01" model="account.fiscal.position.party.tax.scheme">
<field name="name">IVA</field>
<field name="code">01</field>
</record>
<record id="account_fiscal_position_party_tax_scheme_04" model="account.fiscal.position.party.tax.scheme">
<field name="name">INC</field>
<field name="code">04</field>
</record>
<record id="account_fiscal_position_party_tax_scheme_za" model="account.fiscal.position.party.tax.scheme">
<field name="name">IVA e INC</field>
<field name="code">ZA</field>
</record>
<record id="account_fiscal_position_party_tax_scheme_zz" model="account.fiscal.position.party.tax.scheme">
<field name="name">No aplica</field>
<field name="code">ZZ</field>
</record>
</odoo>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_fiscal_position_tax_level_code_o_13" model="account.fiscal.position.tax.level.code">
<field name="name">Gran contribuyente</field>
<field name="code">O-13</field>
</record>
<record id="account_fiscal_position_tax_level_code_o_15" model="account.fiscal.position.tax.level.code">
<field name="name">Autorretenedor</field>
<field name="code">O-15</field>
</record>
<record id="account_fiscal_position_tax_level_code_o_23" model="account.fiscal.position.tax.level.code">
<field name="name">Agente de retención IVA</field>
<field name="code">O-23</field>
</record>
<record id="account_fiscal_position_tax_level_code_o_47" model="account.fiscal.position.tax.level.code">
<field name="name">Régimen simple de tributación</field>
<field name="code">O-47</field>
</record>
<record id="account_fiscal_position_tax_level_code_r_99_pn" model="account.fiscal.position.tax.level.code">
<field name="name">No aplica - Otros</field>
<field name="code">R-99-PN</field>
</record>
</odoo>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="ir_cron_process_dian_documents" model="ir.cron">
<field name="name">Process DIAN Documents</field>
<field name="active" eval="False"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="nextcall" eval="(DateTime.today()).strftime('%Y-%m-%d 06:00:00')" />
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="model_id" ref="model_res_company"/>
<field name="state">code</field>
<field name="code">model.cron_process_dian_documents()</field>
</record>
</odoo>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="0">
<record id="category_einvoicing" model="ir.module.category">
<field name="name">E-Invoicing</field>
</record>
</odoo>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="email_template_einvoice" model="mail.template">
<field name="name">E-Invoice: Send by Email</field>
<field name="email_from">${object.company_id.einvoicing_email}</field>
<field name="reply_to">${object.company_id.einvoicing_email}</field>
<field name="subject">${object.company_id.partner_id.ref_num};${object.company_id.name};${object.name};${object.invoice_type_code if not object.refund_type else ('91' if object.refund_type == 'credit' else '92')};${object.company_id.name};</field>
<field name="email_to">${(object.partner_id.einvoicing_email or object.partner_id.email) or object.company_id.einvoicing_partner_no_email}${',' + object.company_id.einvoicing_receives_all_emails if object.company_id.einvoicing_receives_all_emails else ''}</field>
<field name="model_id" ref="account.model_account_move"/>
<field name="auto_delete" eval="True"/>
<field name="lang">${object.partner_id.lang}</field>
<field name="body_html" type="html">
<div >
<p>Señor(es), ${object.partner_id.name}</p>
<br/>
<p>Le informamos que ha recibido una factura/nota electrónica de ${object.company_id.name}:</p>
<br/>
<p>Número de factura: <strong>${object.number}</strong></p>
<p>Valor Total: <strong>${object.amount_total} ${object.currency_id.name}</strong></p>
<br/>
<p>Si tiene inquietud respecto a la información contenida en la factura/nota electrónica, responda este correo electrónico.</p>
<br/>
<p>Si pasados tres (3) días hábiles siguientes a la recepción de la factura/nota electrónica, no se ha recibido rechazo de la factura/nota electrónica, el sistema la dará por aceptada.</p>
<br/><br/><br/>
<div style="height: auto;width:450px; margin:0 auto;padding-top:20px;padding-bottom:40px;">
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#3b9c17;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;"
href="/dian/document/accept?db=${object.dbname}&amp;token=${object.access_token}&amp;id=${object.id}">Aceptar</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#d66058;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;"
href="/dian/document/reject?db=${object.dbname}&amp;token=${object.access_token}&amp;id=${object.id}">Rechazar</a>
</div>
<br/>
<p>Cordialmente, <strong>${object.company_id.name}</strong></p>
</div>
</field>
</record>
</odoo>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_scheme_001" model="product.scheme">
<field name="name">UNSPSC</field>
<field name="code">001</field>
<field name="scheme_agency_id">10</field>
</record>
<record id="product_scheme_010" model="product.scheme">
<field name="name">GTIN</field>
<field name="code">010</field>
<field name="scheme_agency_id">9</field>
</record>
<record id="product_scheme_020" model="product.scheme">
<field name="name">Partida Arancelarias</field>
<field name="code">020</field>
<field name="scheme_agency_id">195</field>
</record>
<record id="product_scheme_999" model="product.scheme">
<field name="name">Estándar de adopción del contribuyente</field>
<field name="code">999</field>
</record>
</odoo>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="res.groups" id="group_view_einvoicing_email_fields">
<field name="name">View 'E-Invoicing Email' Fields</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="category_id" ref="l10n_co_account_e_invoicing.category_einvoicing"/>
</record>
<record model="res.groups" id="group_edit_is_einvoicing_agent_field">
<field name="name">Edit 'Is an E-Invoicing Agent?' Field</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="category_id" ref="l10n_co_account_e_invoicing.category_einvoicing"/>
</record>
<record model="res.groups" id="group_view_einvoicing_setting_product">
<field name="name">View E-Invoicing Setting of the Product</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="category_id" ref="l10n_co_account_e_invoicing.category_einvoicing"/>
</record>
<record model="res.groups" id="group_view_dian_documents">
<field name="name">View DIAN Documents</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="category_id" ref="l10n_co_account_e_invoicing.category_einvoicing"/>
</record>
<record model="res.groups" id="group_view_invoice_refund_buttons">
<field name="name">View Invoice Refund Buttons</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="category_id" ref="l10n_co_account_e_invoicing.category_einvoicing"/>
</record>
<record model="res.groups" id="group_view_send_invoice_dian_field">
<field name="name">View 'Send Invoice to DIAN?' Field</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="category_id" ref="l10n_co_account_e_invoicing.category_einvoicing"/>
</record>
<record model="res.groups" id="group_view_operation_type_field">
<field name="name">View 'Operation Type' Field</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="category_id" ref="l10n_co_account_e_invoicing.category_einvoicing"/>
</record>
<record model="res.groups" id="group_view_invoice_type_field">
<field name="name">View 'Invoice Type' Field</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="category_id" ref="l10n_co_account_e_invoicing.category_einvoicing"/>
</record>
<record model="res.groups" id="group_view_accepted_rejected_fields">
<field name="name">View 'Accepted/Rejected' Fields</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="category_id" ref="l10n_co_account_e_invoicing.category_einvoicing"/>
</record>
<record model="res.groups" id="group_view_summary_fields">
<field name="name">View 'Summary of Invoice Lines' Fields</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="category_id" ref="l10n_co_account_e_invoicing.category_einvoicing"/>
</record>
<record model="res.groups" id="group_allow_cancel_invoice_dian_document_done">
<field name="name">Allow to Cancel Invoice With DIAN Document in Done</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
<field name="category_id" ref="l10n_co_account_e_invoicing.category_einvoicing"/>
</record>
</odoo>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import res_company
from . import res_partner
from . import product_scheme
from . import product_template
from . import ir_sequence
from . import ir_sequence_date_range
from . import account_fiscal_position_party_tax_scheme
from . import account_fiscal_position_tax_level_code
from . import account_fiscal_position
from . import account_fiscal_year
from . import account_tax_group
from . import account_move_summary_line
from . import account_move_line
from . import account_move
from . import account_move_dian_document_line
from . import account_move_dian_document
from . import einvoice_notification_group

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models, _
class AccountFiscalPosition(models.Model):
_inherit = 'account.fiscal.position'
listname = fields.Selection(
selection=[
('48', 'Responsable del impuesto sobre las ventas - IVA'),
('49', 'No responsable de IVA')],
string='Fiscal Regime',
default=False)
tax_level_code_ids = fields.Many2many(
comodel_name='account.fiscal.position.tax.level.code',
relation='account_fiscal_position_tax_level_code_rel',
column1='account_fiscal_position_id',
column2='tax_level_code_id',
string='Fiscal Responsibilities (TaxLevelCode)')
party_tax_scheme_id = fields.Many2one(
comodel_name='account.fiscal.position.party.tax.scheme',
string="Fiscal Responsibilities (PartyTaxScheme)")

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class AccountFiscalPositionPartyTaxScheme(models.Model):
_name = 'account.fiscal.position.party.tax.scheme'
_description = 'Fiscal Responsibilities (PartyTaxScheme)'
name = fields.Char(string='Name')
code = fields.Char(string='Code')

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class AccountFiscalPositionTaxLevelCode(models.Model):
_name = 'account.fiscal.position.tax.level.code'
_description = 'Fiscal Responsibilities (TaxLevelCode)'
name = fields.Char(string='Name')
code = fields.Char(string='Code')

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields
class AccountFiscalYear(models.Model):
_inherit = 'account.fiscal.year'
out_invoice_sent = fields.Integer(string='Invoices Sent', default=0)
out_refund_credit_sent = fields.Integer(string='Credit Notes Sent', default=0)
out_refund_debit_sent = fields.Integer(string='Debit Notes Sent', default=0)
in_invoice_sent = fields.Integer(string='Support Documents Sent', default=0)

View File

@ -0,0 +1,809 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import pytz
from dateutil import tz
from datetime import datetime, timedelta
from uuid import uuid4
from odoo import api, models, fields, SUPERUSER_ID, _
from odoo.exceptions import UserError
DIAN_TYPES = ('e-support_document', 'e-invoicing', 'e-credit_note', 'e-debit_note')
class AccountMove(models.Model):
_inherit = "account.move"
@api.model
def _default_operation_type(self):
user = self.env['res.users'].search([('id', '=', self.env.user.id)])
view_operation_type_field = False
if (user.has_group('l10n_co_account_e_invoicing.group_view_operation_type_field')
and self.env.user.id != SUPERUSER_ID):
view_operation_type_field = True
if 'move_type' in self._context.keys():
if self._context['move_type'] == 'out_invoice' and not view_operation_type_field:
return '10'
elif self._context['move_type'] == 'in_invoice':
return '10'
else:
return False
elif not view_operation_type_field:
return '10'
else:
return False
@api.model
def _default_invoice_type_code(self):
user = self.env['res.users'].search([('id', '=', self.env.user.id)])
view_invoice_type_field = False
if (user.has_group('l10n_co_account_e_invoicing.group_view_invoice_type_field')
and self.env.user.id != SUPERUSER_ID):
view_invoice_type_field = True
if 'move_type' in self._context.keys():
if self._context['move_type'] == 'out_invoice' and not view_invoice_type_field:
return '01'
elif self._context['move_type'] == 'in_invoice':
return '05'
else:
return False
elif not view_invoice_type_field:
return '01'
else:
return False
@api.model
def _default_send_invoice_to_dian(self):
return self.env.user.company_id.send_invoice_to_dian or '0'
def _compute_warn_certificate(self):
warn_remaining_certificate = False
warn_inactive_certificate = False
if self.company_id.einvoicing_enabled:
warn_inactive_certificate = True
if (self.company_id.certificate_file
and self.company_id.certificate_password
and self.company_id.certificate_date):
remaining_days = self.company_id.certificate_remaining_days or 0
today = fields.Date.context_today(self)
date_to = self.company_id.certificate_date
days = (date_to - today).days
warn_inactive_certificate = False
if days < remaining_days:
if days < 0:
warn_inactive_certificate = True
else:
warn_remaining_certificate = True
self.warn_inactive_certificate = warn_inactive_certificate
self.warn_remaining_certificate = warn_remaining_certificate
def _compute_sequence_resolution_id(self):
for invoice_id in self:
sequence_resolution = False
if (invoice_id.move_type == "out_invoice"
or (invoice_id.journal_id.sequence_id.dian_type == "e-support_document"
and invoice_id.move_type == "in_invoice")):
sequence_resolution_ids = self.env['ir.sequence.date_range'].search([
('sequence_id', '=', invoice_id.journal_id.sequence_id.id)])
for sequence_resolution_id in sequence_resolution_ids:
move_name = invoice_id.move_name or ''
number = move_name.replace(sequence_resolution_id.prefix or '', '')
if sequence_resolution_id.active_resolution:
sequence_resolution = sequence_resolution_id
if (number.isnumeric()
and sequence_resolution_id.number_from <= int(number)
and int(number) <= sequence_resolution_id.number_to):
sequence_resolution = sequence_resolution_id
invoice_id.sequence_resolution_id = sequence_resolution
def _compute_dbname(self):
self.dbname = self._cr.dbname
warn_remaining_certificate = fields.Boolean(
string="Warn About Remainings?",
compute="_compute_warn_certificate",
store=False)
warn_inactive_certificate = fields.Boolean(
string="Warn About Inactive Certificate?",
compute="_compute_warn_certificate",
store=False)
sequence_resolution_id = fields.Many2one(
comodel_name='ir.sequence.date_range',
string='Sequence Resolution',
compute='_compute_sequence_resolution_id',
store=False)
dbname = fields.Char(string="DB Name", compute="_compute_dbname", store=False)
invoice_datetime = fields.Datetime(string='Invoice Datetime', copy=False)
delivery_datetime = fields.Datetime(string='Delivery Datetime', copy=False)
operation_type = fields.Selection(
selection=[
('09', 'AIU'),
('10', 'Standard'),
('20', 'Credit note that references an e-invoice'),
('22', 'Credit note without reference to invoices *'),
('30', 'Debit note that references an e-invoice'),
('32', 'Debit note without reference to invoices *')],
string='Operation Type',
default=_default_operation_type,
copy=False)
invoice_type_code = fields.Selection(
selection=[
('01', 'E-invoice of sale'),
('03', 'E-document of transmission - type 03'),
('04', 'E-invoice of sale - type 04'),
('05', 'E-Support Document')],
string='Invoice Type',
default=_default_invoice_type_code,
copy=False)
send_invoice_to_dian = fields.Selection(
selection=[('0', 'Immediately'), ('1', 'After 1 Day'), ('2', 'After 2 Days')],
string='Send Invoice to DIAN?',
default=_default_send_invoice_to_dian,
copy=False)
access_token = fields.Char(string='Access Token', copy=False)
is_accepted_rejected = fields.Boolean(string='Is Accepted/Rejected?', copy=False)
accepted_rejected_datetime = fields.Datetime(
string='Datetime of Accepted/Rejected', copy=False)
receipt_document_reference = fields.Char(
string='Merchandise / Service Receipt Document',
copy=False)
dian_document_state = fields.Selection(
selection=[
('pending', 'No Transferido'),
('done', 'Emitido'),
('exception', 'Excepción de Envio'),
('dian_reject', 'Rechazado DIAN'),
('dian_accept', 'Aceptado DIAN'),
('customer_reject', 'Rechazado Cliente'),
('customer_accept', 'Aceptado Cliente')],
string='DIAN Document State',
default='pending',
copy=False,
readonly=True)
sale_order_id = fields.Many2one(
comodel_name='sale.order',
string='Sale Order',
copy=False,
readonly=True,
states={'draft': [('readonly', False)]})
merge_with_sale_order = fields.Boolean(
string='Merge With Sale Order?',
copy=False,
readonly=True,
states={'draft': [('readonly', False)]})
summary_line_ids = fields.One2many(
comodel_name='account.move.summary.line',
inverse_name='move_id',
string='Summary of Invoice Lines',
copy=False,
readonly=True,
states={'draft': [('readonly', False)]})
summary_amount_untaxed = fields.Monetary(
string='Untaxed Amount',
readonly=True,
copy=False)
summary_amount_tax = fields.Monetary(string='Tax', readonly=True, copy=False)
summary_amount_total = fields.Monetary(string='Total', readonly=True, copy=False)
dian_document_ids = fields.One2many(
comodel_name='account.move.dian.document',
inverse_name='invoice_id',
string='DIAN Documents',
copy=False,
readonly=True,
states={'draft': [('readonly', False)]})
def _create_token(self):
self.access_token = self.access_token or uuid4().hex
def _get_active_dian_resolution(self):
msg = _("You do not have an active dian resolution, contact with your administrator.")
resolution_number = False
if self.sequence_resolution_id:
resolution_number = self.sequence_resolution_id.resolution_number
date_from = self.sequence_resolution_id.date_from
date_to = self.sequence_resolution_id.date_to_resolution
prefix = self.sequence_resolution_id.prefix
number_from = self.sequence_resolution_id.number_from
number_to = self.sequence_resolution_id.number_to
technical_key = self.sequence_resolution_id.technical_key
if not resolution_number:
raise UserError(msg)
return {
'InvoiceAuthorization': resolution_number,
'StartDate': date_from,
'EndDate': date_to,
'Prefix': prefix,
'From': number_from,
'To': number_to,
'technical_key': technical_key
}
def _get_billing_reference(self):
msg1 = _("You can not make a refund invoice of an invoice with state different "
"to 'Posted'.")
msg2 = _("You can not make a refund invoice of an invoice with DIAN documents "
"with state 'Draft', 'Sent' or 'Cancelled'.")
billing_reference = {}
refund_invoice_id = self.reversed_entry_id or self.debit_origin_id
if refund_invoice_id:
if refund_invoice_id.state != 'posted':
raise UserError(msg1)
if refund_invoice_id.state == 'posted':
dian_document_state_done = False
dian_document_state_cancel = False
dian_document_state_sent = False
dian_document_state_draft = False
for dian_document_id in refund_invoice_id.dian_document_ids:
if dian_document_id.state == 'done':
dian_document_state_done = True
billing_reference['ID'] = refund_invoice_id.name
billing_reference['UUID'] = dian_document_id.cufe_cude
billing_reference[
'IssueDate'] = refund_invoice_id.invoice_date
billing_reference[
'CustomizationID'] = refund_invoice_id.operation_type
if dian_document_id.state == 'cancel':
dian_document_state_cancel = True
if dian_document_id.state == 'draft':
dian_document_state_draft = True
if dian_document_id.state == 'sent':
dian_document_state_sent = True
if ((not dian_document_state_done
and dian_document_state_cancel)
or dian_document_state_draft
or dian_document_state_sent):
raise UserError(msg2)
return billing_reference
def _get_payment_exchange_rate(self):
rate = 1
if self.currency_id != self.company_id.currency_id:
rate = self.currency_id._convert(
rate,
self.company_id.currency_id,
self.company_id,
self.date)
return {
'SourceCurrencyCode': self.currency_id.name,
'TargetCurrencyCode': self.company_id.currency_id.name,
'CalculationRate': rate,
'Date': self.date
}
def _get_einvoicing_taxes(self):
msg1 = _("Your tax: '%s', has no e-invoicing tax group type, contact with your "
"administrator.")
msg2 = _("Your withholding tax: '%s', has amount equal to zero (0), the "
"withholding taxes must have amount different to zero (0), contact with "
"your administrator.")
msg3 = _("Your tax: '%s', has negative amount or an amount equal to zero (0), "
"the taxes must have an amount greater than zero (0), contact with your "
"administrator.")
taxes = {}
tax_total_base = 0
withholding_taxes = {}
currency_rate = self._get_payment_exchange_rate()
for move_line in self.line_ids.filtered(lambda l: l.tax_line_id):
if move_line.tax_line_id.tax_group_id.is_einvoicing:
if not move_line.tax_line_id.tax_group_id.tax_group_type_id:
raise UserError(msg1 % move_line.name)
tax_code = move_line.tax_line_id.tax_group_id.tax_group_type_id.code
tax_name = move_line.tax_line_id.tax_group_id.tax_group_type_id.name
tax_type = move_line.tax_line_id.tax_group_id.tax_group_type_id.type
tax_percent = '{:.2f}'.format(move_line.tax_line_id.amount)
tax_amount = move_line.price_total
tax_base = move_line.tax_base_amount / currency_rate['CalculationRate']
if tax_type == 'withholding_tax' and move_line.tax_line_id.amount == 0:
raise UserError(msg2 % move_line.name)
if tax_type == 'tax' and move_line.tax_line_id.amount <= 0:
raise UserError(msg3 % move_line.name)
if tax_amount != (tax_base * move_line.tax_line_id.amount / 100):
tax_amount = tax_base * move_line.tax_line_id.amount / 100
if tax_type == 'withholding_tax' and move_line.tax_line_id.amount > 0:
if tax_code not in withholding_taxes:
withholding_taxes[tax_code] = {}
withholding_taxes[tax_code]['total'] = 0
withholding_taxes[tax_code]['name'] = tax_name
withholding_taxes[tax_code]['taxes'] = {}
if tax_percent not in withholding_taxes[tax_code]['taxes']:
withholding_taxes[tax_code]['taxes'][tax_percent] = {}
withholding_taxes[tax_code]['taxes'][tax_percent][
'base'] = 0
withholding_taxes[tax_code]['taxes'][tax_percent][
'amount'] = 0
withholding_taxes[tax_code]['total'] += tax_amount * (-1)
withholding_taxes[tax_code]['taxes'][tax_percent]['base'] += tax_base
tax_total_base += tax_base
withholding_taxes[tax_code]['taxes'][tax_percent][
'amount'] += tax_amount * (-1)
elif tax_type == 'withholding_tax' and move_line.tax_line_id.amount < 0:
# TODO 3.0 Las retenciones se recomienda no enviarlas a la DIAN
# Solo las positivas que indicarian una autorretencion, Si la DIAN
# pide que se envien las retenciones, seria quitar o comentar este if
pass
else:
if tax_code not in taxes:
taxes[tax_code] = {}
taxes[tax_code]['total'] = 0
taxes[tax_code]['name'] = tax_name
taxes[tax_code]['taxes'] = {}
if tax_percent not in taxes[tax_code]['taxes']:
taxes[tax_code]['taxes'][tax_percent] = {}
taxes[tax_code]['taxes'][tax_percent]['base'] = 0
taxes[tax_code]['taxes'][tax_percent]['amount'] = 0
taxes[tax_code]['total'] += tax_amount
taxes[tax_code]['taxes'][tax_percent]['base'] += tax_base
tax_total_base += tax_base
taxes[tax_code]['taxes'][tax_percent]['amount'] += tax_amount
if '01' not in taxes:
taxes['01'] = {}
taxes['01']['total'] = 0
taxes['01']['name'] = 'IVA'
taxes['01']['taxes'] = {}
taxes['01']['taxes']['0.00'] = {}
taxes['01']['taxes']['0.00']['base'] = 0
taxes['01']['taxes']['0.00']['amount'] = 0
if '04' not in taxes:
taxes['04'] = {}
taxes['04']['total'] = 0
taxes['04']['name'] = 'ICA'
taxes['04']['taxes'] = {}
taxes['04']['taxes']['0.00'] = {}
taxes['04']['taxes']['0.00']['base'] = 0
taxes['04']['taxes']['0.00']['amount'] = 0
if '03' not in taxes:
taxes['03'] = {}
taxes['03']['total'] = 0
taxes['03']['name'] = 'INC'
taxes['03']['taxes'] = {}
taxes['03']['taxes']['0.00'] = {}
taxes['03']['taxes']['0.00']['base'] = 0
taxes['03']['taxes']['0.00']['amount'] = 0
return {
'TaxesTotal': taxes,
'TaxesTotalBase': tax_total_base,
'WithholdingTaxesTotal': withholding_taxes}
def _get_invoice_lines(self):
msg1 = _("Your Unit of Measure: '%s', has no Unit of Measure Code, contact " +
"with your administrator.")
msg2 = _("The invoice line %s has no reference")
msg3 = _("Your product: '%s', has no reference price, contact with your " +
"administrator.")
msg4 = _("Your tax: '%s', has no e-invoicing tax group type, contact with " +
"your administrator.")
msg5 = _("Your product: '%s', cannot have two VAT type taxes.")
msg6 = _("Your withholding tax: '%s', has amount equal to zero (0), the " +
"withholding taxes must have amount different to zero (0), contact " +
"with your administrator.")
msg7 = _("Your tax: '%s', has negative amount or an amount equal to zero " +
"(0), the taxes must have an amount greater than zero (0), contact " +
"with your administrator.")
invoice_lines = {}
count = 1
exception = False
for invoice_line in self.invoice_line_ids.filtered(
lambda line: not line.display_type):
if not invoice_line.product_uom_id.product_uom_code_id:
raise UserError(msg1 % invoice_line.product_uom_id.name)
disc_amount = 0
total_wo_disc = 0
brand_name = False
model_name = invoice_line.product_id.default_code
if invoice_line.price_unit != 0 and invoice_line.quantity != 0:
total_wo_disc = invoice_line.price_unit * invoice_line.quantity
if total_wo_disc != 0 and invoice_line.discount != 0:
disc_amount = (total_wo_disc * invoice_line.discount) / 100
if not invoice_line.product_id or not invoice_line.product_id.default_code:
raise UserError(msg2 % invoice_line.name)
if invoice_line.price_subtotal <= 0 and invoice_line.reference_price <= 0:
raise UserError(msg3 % invoice_line.product_id.default_code)
if self.invoice_type_code == '02':
if invoice_line.product_id.product_brand_id:
brand_name = invoice_line.product_id.product_brand_id.name
if invoice_line.product_id.manufacturer_pref:
model_name = invoice_line.product_id.manufacturer_pref
invoice_lines[count] = {}
invoice_lines[count][
'unitCode'] = invoice_line.product_uom_id.product_uom_code_id.code
invoice_lines[count]['Quantity'] = '{:.2f}'.format(
invoice_line.quantity)
invoice_lines[count][
'PricingReferencePriceAmount'] = '{:.2f}'.format(
invoice_line.reference_price)
invoice_lines[count]['LineExtensionAmount'] = '{:.2f}'.format(
invoice_line.price_subtotal)
invoice_lines[count]['MultiplierFactorNumeric'] = '{:.2f}'.format(
invoice_line.discount)
invoice_lines[count]['AllowanceChargeAmount'] = '{:.2f}'.format(
disc_amount)
invoice_lines[count][
'AllowanceChargeBaseAmount'] = '{:.2f}'.format(total_wo_disc)
invoice_lines[count]['TaxesTotal'] = {}
invoice_lines[count]['WithholdingTaxesTotal'] = {}
invoice_lines[count][
'StandardItemIdentification'] = invoice_line.product_id.default_code
iva_count = 0
for tax in invoice_line.tax_ids:
if tax.amount_type == 'group':
tax_ids = tax.children_tax_ids
else:
tax_ids = tax
for tax_id in tax_ids:
if tax_id.tax_group_id.is_einvoicing:
if not tax_id.tax_group_id.tax_group_type_id:
raise UserError(msg4 % tax.name)
tax_type = tax_id.tax_group_id.tax_group_type_id.type
if tax_id.tax_group_id.tax_group_type_id.name == 'IVA':
iva_count += 1
if iva_count > 1:
dian_document_id = self.dian_document_ids.filtered(
lambda d: d.state != 'cancel')
exception = True
msg = msg5 % invoice_line.product_id.default_code
dian_document_id.write({'get_status_zip_response': msg})
if tax_type == 'withholding_tax' and tax_id.amount == 0:
raise UserError(msg6 % tax_id.name)
if tax_type == 'tax' and tax_id.amount <= 0:
raise UserError(msg7 % tax_id.name)
if tax_type == 'withholding_tax' and tax_id.amount > 0:
invoice_lines[count]['WithholdingTaxesTotal'] = (
invoice_line._get_invoice_lines_taxes(
tax_id,
tax_id.amount,
invoice_lines[count]['WithholdingTaxesTotal']))
elif tax_type == 'withholding_tax' and tax_id.amount < 0:
# TODO 3.0 Las retenciones se recomienda no enviarlas a la DIAN.
# Solo la parte positiva que indicaria una autoretencion, Si la DIAN
# pide que se envie la parte negativa, seria quitar o comentar este if
pass
else:
invoice_lines[count]['TaxesTotal'] = (
invoice_line._get_invoice_lines_taxes(
tax_id,
tax_id.amount,
invoice_lines[count]['TaxesTotal']))
if '01' not in invoice_lines[count]['TaxesTotal']:
invoice_lines[count]['TaxesTotal']['01'] = {}
invoice_lines[count]['TaxesTotal']['01']['total'] = 0
invoice_lines[count]['TaxesTotal']['01']['name'] = 'IVA'
invoice_lines[count]['TaxesTotal']['01']['taxes'] = {}
invoice_lines[count]['TaxesTotal']['01']['taxes']['0.00'] = {}
invoice_lines[count]['TaxesTotal']['01']['taxes']['0.00'][
'base'] = invoice_line.price_subtotal
invoice_lines[count]['TaxesTotal']['01']['taxes']['0.00'][
'amount'] = 0
if '04' not in invoice_lines[count]['TaxesTotal']:
invoice_lines[count]['TaxesTotal']['04'] = {}
invoice_lines[count]['TaxesTotal']['04']['total'] = 0
invoice_lines[count]['TaxesTotal']['04']['name'] = 'ICA'
invoice_lines[count]['TaxesTotal']['04']['taxes'] = {}
invoice_lines[count]['TaxesTotal']['04']['taxes']['0.00'] = {}
invoice_lines[count]['TaxesTotal']['04']['taxes']['0.00'][
'base'] = invoice_line.price_subtotal
invoice_lines[count]['TaxesTotal']['04']['taxes']['0.00'][
'amount'] = 0
if '03' not in invoice_lines[count]['TaxesTotal']:
invoice_lines[count]['TaxesTotal']['03'] = {}
invoice_lines[count]['TaxesTotal']['03']['total'] = 0
invoice_lines[count]['TaxesTotal']['03']['name'] = 'INC'
invoice_lines[count]['TaxesTotal']['03']['taxes'] = {}
invoice_lines[count]['TaxesTotal']['03']['taxes']['0.00'] = {}
invoice_lines[count]['TaxesTotal']['03']['taxes']['0.00'][
'base'] = invoice_line.price_subtotal
invoice_lines[count]['TaxesTotal']['03']['taxes']['0.00'][
'amount'] = 0
invoice_lines[count]['BrandName'] = brand_name
invoice_lines[count]['ModelName'] = model_name
invoice_lines[count]['ItemDescription'] = invoice_line.name
invoice_lines[count]['InformationContentProviderParty'] = (
invoice_line._get_information_content_provider_party_values())
invoice_lines[count]['PriceAmount'] = '{:.2f}'.format(
invoice_line.price_unit)
count += 1
if exception:
self.write({'dian_document_state': 'exception'})
else:
self.write({'dian_document_state': 'pending'})
return invoice_lines
def _set_invoice_lines_price_reference(self):
for invoice_line in self.invoice_line_ids.filtered(
lambda line: not line.display_type):
percentage = 100
margin_percentage = invoice_line.product_id.margin_percentage
if invoice_line.product_id.reference_price > 0:
reference_price = invoice_line.product_id.reference_price
elif 0 < margin_percentage < 100:
percentage = (percentage - margin_percentage) / 100
reference_price = invoice_line.product_id.standard_price / percentage
else:
reference_price = 0
invoice_line.write({
'cost_price': invoice_line.product_id.standard_price,
'reference_price': reference_price
})
return True
def update(self, values):
res = super(AccountMove, self).update(values)
for invoice_id in self:
if values.get('refund_type') == "credit":
invoice_id.operation_type = '20'
elif values.get('refund_type') == "debit":
invoice_id.operation_type = '30'
return res
def action_set_dian_document(self):
msg = _("The 'delivery date' must be equal or greater per maximum 10 days to "
"the 'invoice date'.")
timezone = pytz.timezone(self.env.user.tz or 'America/Bogota')
from_zone = tz.gettz('UTC')
to_zone = tz.gettz(timezone.zone)
for invoice_id in self:
if not invoice_id.company_id.einvoicing_enabled:
return True
if invoice_id.journal_id.sequence_id.dian_type not in DIAN_TYPES:
return True
if invoice_id.dian_document_ids.filtered(lambda d: d.state != 'cancel'):
return True
if not invoice_id.invoice_datetime:
invoice_datetime = datetime.strptime(
str(invoice_id.invoice_date) + ' 13:00:00',
'%Y-%m-%d %H:%M:%S').replace(tzinfo=from_zone)
invoice_datetime = invoice_datetime.astimezone(
to_zone).strftime('%Y-%m-%d %H:%M:%S')
invoice_id.invoice_datetime = invoice_datetime
if (invoice_id.company_id.automatic_delivery_datetime
and not invoice_id.delivery_datetime):
invoice_datetime = invoice_id.invoice_datetime
hours_added = timedelta(
hours=invoice_id.company_id.additional_hours_delivery_datetime)
invoice_id.delivery_datetime = invoice_datetime + hours_added
if not invoice_id.delivery_datetime:
raise UserError(msg)
invoice_date = invoice_id.invoice_date
delivery_date = datetime.strftime(invoice_id.delivery_datetime, '%Y-%m-%d')
delivery_date = datetime.strptime(delivery_date, '%Y-%m-%d').date()
days = (delivery_date - invoice_date).days
if days < 0 or days > 10:
raise UserError(msg)
invoice_id._set_invoice_lines_price_reference()
xml_filename = False
zipped_filename = False
ar_xml_filename = False
ad_zipped_filename = False
for dian_document_id in invoice_id.dian_document_ids:
xml_filename = dian_document_id.xml_filename
zipped_filename = dian_document_id.zipped_filename
ar_xml_filename = dian_document_id.ar_xml_filename
ad_zipped_filename = dian_document_id.ad_zipped_filename
break
dian_document_id = self.env['account.move.dian.document'].create({
'invoice_id': invoice_id.id,
'company_id': invoice_id.company_id.id,
'xml_filename': xml_filename,
'zipped_filename': zipped_filename,
'ar_xml_filename': ar_xml_filename,
'ad_zipped_filename': ad_zipped_filename})
set_files = dian_document_id.action_set_files()
if (not invoice_id.send_invoice_to_dian
and invoice_id.company_id.send_invoice_to_dian):
invoice_id.send_invoice_to_dian = invoice_id.company_id.send_invoice_to_dian
if (not invoice_id.invoice_type_code
and invoice_id.company_id.invoice_type_code):
invoice_id.invoice_type_code = invoice_id.company_id.invoice_type_code
if invoice_id.send_invoice_to_dian == '0':
if set_files:
if invoice_id.invoice_type_code in ('01', '02', '05'):
if dian_document_id.zip_key:
to_return = dian_document_id.action_GetStatusZip()
else:
to_return = dian_document_id._get_GetStatus(True)
if not to_return:
dian_document_id.action_send_zipped_file()
elif invoice_id.invoice_type_code == '04':
dian_document_id.action_send_mail()
else:
dian_document_id.send_failure_mail()
return True
def action_process_dian_document(self):
for invoice_id in self:
dian_document_id = invoice_id.dian_document_ids.filtered(
lambda d: d.state not in ('cancel', 'done'))
if dian_document_id:
dian_document_id.action_process()
return True
def action_merge_with_sale_order(self):
for invoice_id in self:
sale_order_id = invoice_id.sale_order_id
if sale_order_id:
move_id = sale_order_id._create_invoices()
move_line_data = []
for invoice_line_id in invoice_id.invoice_line_ids:
move_line_data.append({
'move_id': move_id.id,
'currency_id': invoice_line_id.currency_id.id,
'product_id': invoice_line_id.product_id.id,
'name': invoice_line_id.name,
'quantity': invoice_line_id.quantity,
'product_uom_id': invoice_line_id.product_uom_id.id,
'price_unit': invoice_line_id.price_unit,
'discount': invoice_line_id.discount,
'tax_ids': invoice_line_id.tax_ids,
'price_subtotal': invoice_line_id.price_subtotal,
'price_total': invoice_line_id.price_total,
'cost_price': invoice_line_id.cost_price,
'reference_price': invoice_line_id.reference_price})
self.env['account.move.summary.line'].create(move_line_data)
move_id.write({
'name': invoice_id.name,
'move_name': invoice_id.move_name,
'invoice_date': invoice_id.invoice_date,
'summary_amount_untaxed': invoice_id.amount_untaxed,
'summary_amount_tax': invoice_id.amount_tax,
'summary_amount_total': invoice_id.amount_total})
mail_message_ids = self.env['mail.message'].search(
[('model', '=', 'account.move'), ('res_id', '=', invoice_id.id)])
if mail_message_ids:
mail_message_ids.write({'res_id': move_id.id})
if invoice_id.dian_document_ids:
invoice_id.dian_document_ids.write({'invoice_id': move_id.id})
move_id.write({
'invoice_datetime': invoice_id.invoice_datetime,
'delivery_datetime': invoice_id.delivery_datetime,
'operation_type': invoice_id.operation_type,
'invoice_type_code': invoice_id.invoice_type_code,
'send_invoice_to_dian': invoice_id.send_invoice_to_dian,
'access_token': invoice_id.access_token,
'is_accepted_rejected': invoice_id.is_accepted_rejected,
'accepted_rejected_datetime': invoice_id.accepted_rejected_datetime,
'receipt_document_reference': invoice_id.receipt_document_reference,
'dian_document_state': invoice_id.dian_document_state,
'send_invoice_to_dian': invoice_id.send_invoice_to_dian})
if not invoice_id.dian_document_ids.filtered(lambda d: d.state == 'done'):
move_id.write({'merge_with_sale_order': True})
invoice_id.write({'move_name': False})
invoice_id.with_context(force_delete=True).unlink()
return sale_order_id.action_view_invoice()
return True
def action_post(self):
msg = _('Invoice totals do not match summary totals.')
res = super(AccountMove, self).action_post()
for invoice_id in self:
invoice_id.action_set_dian_document()
if invoice_id.merge_with_sale_order:
if not invoice_id.summary_line_ids:
invoice_id.button_draft()
else:
if (invoice_id.summary_amount_untaxed != invoice_id.amount_untaxed
or invoice_id.summary_amount_tax != invoice_id.amount_tax
or invoice_id.summary_amount_total != invoice_id.amount_total):
raise UserError(msg)
return res
def button_draft(self):
msg = _('You cannot cancel a invoice sent to the DIAN and that was approved.')
user = self.env['res.users'].search([('id', '=', self.env.user.id)])
allow_cancel_invoice_dian_document_done = False
if user.has_group(
'l10n_co_account_e_invoicing.group_allow_cancel_invoice_dian_document_done'):
allow_cancel_invoice_dian_document_done = True
for invoice_id in self:
if invoice_id.merge_with_sale_order and not invoice_id.summary_line_ids:
continue
for dian_document_id in invoice_id.dian_document_ids:
if dian_document_id.state == 'done':
if not allow_cancel_invoice_dian_document_done:
raise UserError(msg)
else:
dian_document_id.state = 'cancel'
self.write({'dian_document_state': 'pending'})
return super(AccountMove, self).button_draft()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields
class AccountInvoiceDianDocumentLine(models.Model):
_name = "account.move.dian.document.line"
_description = 'DIAN Document Lines'
dian_document_id = fields.Many2one(
comodel_name='account.move.dian.document',
string='DIAN Document')
send_async_status_code = fields.Char(string='Status Code')
send_async_reason = fields.Char(string='Reason')
send_async_response = fields.Text(string='Response')

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
cost_price = fields.Float(
string='Cost Price',
digits='Product Price')
reference_price = fields.Float(
string='Reference Price',
digits='Product Price')
def _get_invoice_lines_taxes(self, tax, tax_amount, invoice_line_taxes_total):
tax_code = tax.tax_group_id.tax_group_type_id.code
tax_name = tax.tax_group_id.tax_group_type_id.name
tax_percent = '{:.2f}'.format(tax_amount)
if tax_code not in invoice_line_taxes_total:
invoice_line_taxes_total[tax_code] = {}
invoice_line_taxes_total[tax_code]['total'] = 0
invoice_line_taxes_total[tax_code]['name'] = tax_name
invoice_line_taxes_total[tax_code]['taxes'] = {}
if tax_percent not in invoice_line_taxes_total[tax_code]['taxes']:
invoice_line_taxes_total[tax_code]['taxes'][tax_percent] = {}
invoice_line_taxes_total[tax_code]['taxes'][tax_percent]['base'] = 0
invoice_line_taxes_total[tax_code]['taxes'][tax_percent]['amount'] = 0
invoice_line_taxes_total[tax_code]['total'] += (
self.price_subtotal * tax_amount / 100)
invoice_line_taxes_total[tax_code]['taxes'][tax_percent]['base'] += (
self.price_subtotal)
invoice_line_taxes_total[tax_code]['taxes'][tax_percent]['amount'] += (
self.price_subtotal * tax_amount / 100)
return invoice_line_taxes_total
def _get_information_content_provider_party_values(self):
return {'IDschemeID': False, 'IDschemeName': False, 'ID': False}

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api
class AccountMoveLineSummary(models.Model):
_name = "account.move.line.summary"
_description = "Journal Item Summary"
move_id = fields.Many2one(
'account.move',
string='Journal Entry',
index=True,
required=True,
readonly=True,
auto_join=True,
ondelete="cascade",
check_company=True,
help="The move of this entry line.")
company_id = fields.Many2one(
related='move_id.company_id',
store=True,
readonly=True,
default=lambda self: self.env.company)
product_id = fields.Many2one(
'product.product',
string='Product',
ondelete='restrict')
product_uom_category_id = fields.Many2one(
'uom.category',
related='product_id.uom_id.category_id')
name = fields.Char(string='Label', tracking=True)
quantity = fields.Float(
string='Quantity',
default=1.0,
digits='Product Unit of Measure')
product_uom_id = fields.Many2one(
'uom.uom',
string='Unit of Measure',
domain="[('category_id', '=', product_uom_category_id)]")
price_unit = fields.Float(
string='Unit Price',
digits='Product Price')
discount = fields.Float(
string='Discount (%)',
digits='Discount',
default=0.0)
cost_price = fields.Float(
string='Cost Price',
digits='Product Price')
reference_price = fields.Float(
string='Reference Price',
digits='Product Price')

View File

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api
class AccountMoveSummaryLine(models.Model):
_name = "account.move.summary.line"
_description = "Summary of Invoice Lines"
move_id = fields.Many2one(
comodel_name='account.move',
string='Journal Entry',
index=True,
required=True,
readonly=True,
auto_join=True,
ondelete="cascade",
check_company=True,
help="The move of this entry line.")
currency_id = fields.Many2one(
comodel_name='res.currency',
string='Currency',
required=True)
company_id = fields.Many2one(
related='move_id.company_id',
store=True,
readonly=True,
default=lambda self: self.env.company)
product_id = fields.Many2one(
comodel_name='product.product',
string='Product',
ondelete='restrict')
name = fields.Char(string='Label', tracking=True)
quantity = fields.Float(
string='Quantity',
default=1.0,
digits='Product Unit of Measure')
product_uom_category_id = fields.Many2one(
comodel_name='uom.category',
related='product_id.uom_id.category_id')
product_uom_id = fields.Many2one(
comodel_name='uom.uom',
string='Unit of Measure',
domain="[('category_id', '=', product_uom_category_id)]")
price_unit = fields.Float(
string='Unit Price',
digits='Product Price')
discount = fields.Float(
string='Discount (%)',
digits='Discount',
default=0.0)
tax_ids = fields.Many2many(
comodel_name='account.tax',
string="Taxes",
context={'active_test': False},
check_company=True,
help="Taxes that apply on the base amount")
price_subtotal = fields.Monetary(
string='Subtotal',
store=True,
readonly=True,
currency_field='currency_id')
price_total = fields.Monetary(
string='Total',
store=True,
readonly=True,
currency_field='currency_id')
cost_price = fields.Float(string='Cost Price', digits='Product Price')
reference_price = fields.Float(string='Reference Price', digits='Product Price')

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class AccountTaxGroup(models.Model):
_inherit = "account.tax.group"
is_einvoicing = fields.Boolean(
string="Does it Apply for E-Invoicing?",
default=True)

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import re
from odoo import api, fields, models
from odoo.exceptions import ValidationError
class EInvoiceNotificationGroup(models.Model):
_name = 'einvoice.notification.group'
_description = 'Notification Groups'
name = fields.Char(string='Name')
email = fields.Char(string='Email')
company_id = fields.Many2one(comodel_name='res.company', string='Company')
@api.model
def create(self, vals):
rec = super(EInvoiceNotificationGroup, self).create(vals)
# Check email address is valid or not
if rec.email:
if re.match(
"^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$",
rec.email) is None:
raise ValidationError("Please enter a valid email address")
return rec
def write(self, values):
result = super(EInvoiceNotificationGroup, self).write(values)
# Check email address is valid or not
if values.get('email'):
if re.match(
"^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$",
values.get('email')) is None:
raise ValidationError("Please enter a valid email address")
return result

View File

@ -0,0 +1,244 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import hashlib
from os import path
from uuid import uuid4
from base64 import b64encode, b64decode
from io import BytesIO
from datetime import datetime, timedelta
import OpenSSL.crypto as crypto
from lxml import etree
from jinja2 import Environment, FileSystemLoader
from pgxades import XAdESContext, PolicyId, template
import xmlsig
from qrcode import QRCode, constants
from odoo import _
from odoo.exceptions import ValidationError
def get_software_security_code(IdSoftware, Pin, NroDocumentos):
uncoded_value = (IdSoftware + ' + ' + Pin + ' + ' + NroDocumentos)
software_security_code = IdSoftware + Pin + NroDocumentos
software_security_code = hashlib.sha384(software_security_code.encode('utf-8'))
return {
'SoftwareSecurityCodeUncoded': uncoded_value,
'SoftwareSecurityCode': software_security_code.hexdigest()}
def get_cufe_cude(
NumFac,
FecFac,
HorFac,
ValFac,
CodImp1,
ValImp1,
CodImp2,
ValImp2,
CodImp3,
ValImp3,
ValTot,
NitOFE,
DocAdq,
ClTec,
SoftwarePIN,
TipoAmbie):
# CUFE = SHA-384(NumFac + FecFac + HorFac + ValFac + CodImp1 + ValImp1 +
# CodImp2 + ValImp2 + CodImp3 + ValImp3 + ValTot + NitOFE + DocAdq +
# ClTec + TipoAmbie)
# CUDE = SHA-384(NumFac + FecFac + HorFac + ValFac + CodImp1 + ValImp1 +
# CodImp2 + ValImp2 + CodImp3 + ValImp3 + ValTot + NitOFE + DocAdq +
# Software-PIN + TipoAmbie)
uncoded_value = (NumFac + ' + ' + FecFac + ' + ' + HorFac + ' + ' +
ValFac + ' + ' + CodImp1 + ' + ' + ValImp1 + ' + ' +
CodImp2 + ' + ' + ValImp2 + ' + ' + CodImp3 + ' + ' +
ValImp3 + ' + ' + ValTot + ' + ' + NitOFE + ' + ' +
DocAdq + ' + ' + (ClTec if ClTec else SoftwarePIN) +
' + ' + TipoAmbie)
CUFE_CUDE = (
NumFac + FecFac + HorFac + ValFac + CodImp1 + ValImp1 + CodImp2 +
ValImp2 + CodImp3 + ValImp3 + ValTot + NitOFE + DocAdq +
(ClTec if ClTec else SoftwarePIN) + TipoAmbie)
CUFE_CUDE = hashlib.sha384(CUFE_CUDE.encode('utf-8'))
return {
'CUFE/CUDEUncoded': uncoded_value,
'CUFE/CUDE': CUFE_CUDE.hexdigest()}
# https://stackoverflow.com/questions/38432809/dynamic-xml-template-generation-using-get-template-jinja2
def get_template_xml(values, template_name):
base_path = path.dirname(path.dirname(__file__))
env = Environment(loader=FileSystemLoader(path.join(base_path, 'templates')))
template_xml = env.get_template('{}.xml'.format(template_name))
xml = template_xml.render(values)
return xml.replace('&', '&amp;').encode('utf-8')
def get_pkcs12(certificate_file, certificate_password):
msg = _("The certificate password or certificate file is not valid.\n\n"
"Exception: %s")
try:
return crypto.load_pkcs12(b64decode(certificate_file), certificate_password)
except Exception as e:
raise ValidationError(msg % e)
# https://www.decalage.info/en/python/lxml-c14n
def get_xml_with_c14n(xml):
if not isinstance(xml, etree._Element):
xml = etree.fromstring(xml.encode('utf-8'))
out = BytesIO()
xml.getroottree().write_c14n(out)
value = b64encode(out.getvalue()).decode("utf-8")
out.close()
return value
# https://github.com/etobella/python-xades
def get_xml_with_signature(
xml_without_signature,
signature_policy_url,
signature_policy_file,
signature_policy_description,
certificate_file,
certificate_password):
# https://lxml.de/tutorial.html
# root = etree.fromstring(response.content)
# root = etree.tostring(root, encoding='utf-8')
# parser = etree.XMLParser(encoding='utf-8', remove_blank_text=True)
ds = "http://www.w3.org/2000/09/xmldsig#"
ext = "urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"
parser = etree.XMLParser(remove_comments=True, remove_blank_text=True)
root = etree.fromstring(xml_without_signature, parser=parser)
signature_id = "xmldsig-{}".format(uuid4())
signature = xmlsig.template.create(
xmlsig.constants.TransformInclC14N,
xmlsig.constants.TransformRsaSha512,
signature_id)
# Complememto para añadir atributo faltante
for element in root.iter("{%s}SignatureValue" % ds):
element.attrib['Id'] = signature_id + "-sigvalue"
ref = xmlsig.template.add_reference(
signature,
xmlsig.constants.TransformSha512,
uri="",
name=signature_id + "-ref0")
xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped)
sp = xmlsig.template.add_reference(
signature,
xmlsig.constants.TransformSha512,
uri="#" + signature_id + "-signedprops",
uri_type="http://uri.etsi.org/01903#SignedProperties")
xmlsig.template.add_transform(sp, xmlsig.constants.TransformInclC14N)
xmlsig.template.add_reference(
signature,
xmlsig.constants.TransformSha512,
uri="#" + signature_id + "-keyinfo")
ki = xmlsig.template.ensure_key_info(signature, name=signature_id + "-keyinfo")
data = xmlsig.template.add_x509_data(ki)
xmlsig.template.x509_data_add_certificate(data)
xmlsig.template.x509_data_add_subject_name(data)
serial = xmlsig.template.x509_data_add_issuer_serial(data)
xmlsig.template.x509_issuer_serial_add_issuer_name(serial)
xmlsig.template.x509_issuer_serial_add_serial_number(serial)
xmlsig.template.add_key_value(ki)
qualifying = template.create_qualifying_properties(signature)
props = template.create_signed_properties(
qualifying, name=signature_id + "-signedprops")
template.add_claimed_role(props, "supplier")
policy = PolicyId()
policy.id = signature_policy_url
policy.name = signature_policy_description
policy.remote = b64decode(signature_policy_file)
policy.hash_method = xmlsig.constants.TransformSha512
ctx = XAdESContext(policy)
ctx.load_pkcs12(get_pkcs12(certificate_file, certificate_password))
root.append(signature)
ctx.sign(signature)
ctx.verify(signature)
root.remove(signature)
position = 0
for element in root.iter("{%s}ExtensionContent" % ext):
if position == 1:
element.append(signature)
position += 1
return get_xml_with_c14n(root)
def get_xml_soap_values(certificate_file, certificate_password):
Created = datetime.utcnow()
Expires = Created + timedelta(seconds=60000)
Created = Created.strftime("%Y-%m-%dT%H:%M:%S.001Z")
Expires = Expires.strftime("%Y-%m-%dT%H:%M:%S.001Z")
# https://github.com/mit-dig/idm/blob/master/idm_query_functions.py#L151
pkcs12 = get_pkcs12(certificate_file, certificate_password)
cert = pkcs12.get_certificate()
der = b64encode(crypto.dump_certificate(crypto.FILETYPE_ASN1, cert)).decode('utf-8')
return {
'Created': Created,
'Expires': Expires,
'Id': uuid4(),
'BinarySecurityToken': der}
def get_xml_soap_with_signature(
xml_soap_without_signature, Id, certificate_file, certificate_password):
wsse = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
wsu = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
X509v3 = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
parser = etree.XMLParser(remove_comments=True, remove_blank_text=True)
root = etree.fromstring(xml_soap_without_signature, parser=parser)
signature_id = "{}".format(Id)
signature = xmlsig.template.create(
xmlsig.constants.TransformExclC14N,
xmlsig.constants.TransformRsaSha256, # solo me ha funcionado con esta
"SIG-" + signature_id)
ref = xmlsig.template.add_reference(
signature, xmlsig.constants.TransformSha256, uri="#id-" + signature_id)
xmlsig.template.add_transform(ref, xmlsig.constants.TransformExclC14N)
ki = xmlsig.template.ensure_key_info(signature, name="KI-" + signature_id)
ctx = xmlsig.SignatureContext()
ctx.load_pkcs12(get_pkcs12(certificate_file, certificate_password))
for element in root.iter("{%s}Security" % wsse):
element.append(signature)
ki_str = etree.SubElement(ki, "{%s}SecurityTokenReference" % wsse)
ki_str.attrib["{%s}Id" % wsu] = "STR-" + signature_id
ki_str_reference = etree.SubElement(ki_str, "{%s}Reference" % wsse)
ki_str_reference.attrib['URI'] = "#X509-" + signature_id
ki_str_reference.attrib['ValueType'] = X509v3
ctx.sign(signature)
ctx.verify(signature)
return root
def get_qr_image(data):
qr_code = QRCode(
version=1,
error_correction=constants.ERROR_CORRECT_L,
box_size=20,
border=4)
qr_code.add_data(data)
qr_code.make(fit=True)
image = qr_code.make_image()
temp = BytesIO()
image.save(temp, format="PNG")
qr_image = b64encode(temp.getvalue()).decode('utf-8')
temp.close()
return qr_image

View File

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models, _
class IrSequence(models.Model):
_inherit = 'ir.sequence'
dian_type = fields.Selection(
selection_add=[
('e-invoicing', _('E-Invoicing')),
('e-credit_note', _('E-Credit Note')),
('e-debit_note', _('E-Debit Note')),
('e-support_document', _('E-Support Document')),
('contingency_checkbook_e-invoicing', _('Contingency Checkbook E-Invoicing'))])

View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class IrSequenceDateRange(models.Model):
_inherit = 'ir.sequence.date_range'
dian_type = fields.Selection(
string='DIAN Type',
related='sequence_id.dian_type',
store=False)
technical_key = fields.Char(string="Technical Key")

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields
class ProductScheme(models.Model):
_name = 'product.scheme'
_description = 'Product Schemes'
code = fields.Char(string='schemeID')
name = fields.Char(string='schemeName')
scheme_agency_id = fields.Char(string='schemeAgencyID')

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api
class ProductTemplate(models.Model):
_inherit = 'product.template'
@api.model
def _default_product_scheme(self):
return self.env['product.scheme'].search([('code', '=', '999')]).id
margin_percentage = fields.Float(
string='Margin Percentage',
help='The cost price + this percentage will be the reference price',
digits='Discount',
default=10)
reference_price = fields.Float(
string='Reference Price',
help='Use this field if the reference price does not depend on the cost price',
digits='Product Price')
product_scheme_id = fields.Many2one(
comodel_name='product.scheme',
string='Product Scheme',
default=_default_product_scheme)

View File

@ -0,0 +1,208 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import datetime
from urllib import request
from requests import post, exceptions
from lxml import etree
import ssl
from . import global_functions
from odoo import api, models, fields, _
from odoo.exceptions import ValidationError
ssl._create_default_https_context = ssl._create_unverified_context
class ResCompany(models.Model):
_inherit = "res.company"
einvoicing_enabled = fields.Boolean(string='E-Invoicing Enabled')
automatic_delivery_datetime = fields.Boolean(
string='Automatic Delivery Datetime?')
additional_hours_delivery_datetime = fields.Float(
string='Additional Hours',
help='Additional hours to invoice date for delivery date',
digits=(12, 4),
default=False)
send_invoice_to_dian = fields.Selection(
selection=[
('0', 'Immediately'),
('1', 'After 1 Day'),
('2', 'After 2 Days')],
string='Send Invoice to DIAN?',
default='0')
operation_type = fields.Selection(
selection=[('09', 'AIU'), ('10', 'Standard')],
string='Operation Type',
default=False,
copy=False)
invoice_type_code = fields.Selection(
selection=[('01', 'E-invoice of sale')],
string='Invoice Type',
default=False,
copy=False)
force_send_mail = fields.Boolean(string='Force Send Mail?')
#send_pdf_of_invoice = fields.Boolean(string='Send PDF of the Invoice?')
#send_attachments_of_invoice = fields.Boolean(string='Send Attachments of the Invoice?')
profile_execution_id = fields.Selection(
selection=[('1', 'Production'), ('2', 'Test')],
string='Destination Environment of Document',
default='2',
required=True)
have_technological_provider = fields.Boolean(
string='Do you have a technological provider?')
technological_provider_id = fields.Many2one(
string='Technological Provider',
comodel_name='res.partner')
assignment_code = fields.Char(string='Assignment Code', size=3)
test_set_id = fields.Char(string='Test Set ID')
software_id = fields.Char(string='Software ID')
software_pin = fields.Char(string='Software PIN')
certificate_filename = fields.Char(string='Certificate Filename')
certificate_file = fields.Binary(string='Certificate File')
certificate_password = fields.Char(string='Certificate Password')
certificate_date = fields.Date(string='Certificate Date Validity')
certificate_remaining_days = fields.Integer(
string='Certificate Remaining Days',
default=False)
signature_policy_url = fields.Char(
string='Signature Policy URL',
default='https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf')
signature_policy_filename = fields.Char(string='Signature Policy Filename')
signature_policy_file = fields.Binary(string='Signature Policy File')
signature_policy_description = fields.Char(
string='Signature Policy Description',
default='Política de firma para facturas electrónicas de la República de Colombia.')
einvoicing_email = fields.Char(
string='E-Invoice Email, From:',
help="Enter the e-invoice sender's email.")
validate_einvoicing_email = fields.Boolean(
string='Validate E-Invoicing Email?',
default=True)
einvoicing_partner_no_email = fields.Char(
string='Failed Emails, To:',
help='Enter the email where the invoice will be sent when the customer does not have an email.')
einvoicing_receives_all_emails = fields.Char(
string='Email that receives all emails')
report_template_id = fields.Many2one(
string='Report Template',
comodel_name='ir.actions.report')
send_failure_mail = fields.Boolean(
string='Send Failure Mail?',
default=False)
notification_group_ids = fields.One2many(
comodel_name='einvoice.notification.group',
inverse_name='company_id',
string='Notification Group')
get_numbering_range_response = fields.Text(string='GetNumberingRange Response')
def write(self, vals):
msg = _('Invalid URL.')
if vals.get('signature_policy_url'):
try:
for company in self:
response = request.urlopen(
vals.get('signature_policy_url'), timeout=2)
if response.getcode() != 200:
raise ValidationError(msg)
except Exception as e:
raise ValidationError(msg % e)
rec = super(ResCompany, self).write(vals)
if vals.get('certificate_file') or vals.get('certificate_password'):
for company in self:
pkcs12 = global_functions.get_pkcs12(
company.certificate_file, company.certificate_password)
x509 = pkcs12.get_certificate()
date = x509.get_notAfter()
company.certificate_date = datetime.strptime(
date.decode("utf-8"), '%Y%m%d%H%M%SZ').date()
return rec
def _get_GetNumberingRange_values(self):
xml_soap_values = global_functions.get_xml_soap_values(
self.certificate_file, self.certificate_password)
xml_soap_values['accountCode'] = self.partner_id.ref_num
xml_soap_values['accountCodeT'] = self.partner_id.ref_num
xml_soap_values['softwareCode'] = self.software_id
if self.have_technological_provider:
xml_soap_values['accountCodeT'] = self.technological_provider_id.ref_num
return xml_soap_values
def action_GetNumberingRange(self):
msg1 = _("Unknown Error,\nStatus Code: %s,\nReason: %s.")
msg2 = _("Unknown Error: %s\n.")
wsdl = 'https://vpfe.dian.gov.co/WcfDianCustomerServices.svc?wsdl'
s = "http://www.w3.org/2003/05/soap-envelope"
GetNumberingRange_values = self._get_GetNumberingRange_values()
GetNumberingRange_values['To'] = wsdl.replace('?wsdl', '')
xml_soap_with_signature = global_functions.get_xml_soap_with_signature(
global_functions.get_template_xml(
GetNumberingRange_values, 'GetNumberingRange'),
GetNumberingRange_values['Id'],
self.certificate_file,
self.certificate_password)
try:
response = post(
wsdl,
headers={'content-type': 'application/soap+xml;charset=utf-8'},
data=etree.tostring(xml_soap_with_signature))
if response.status_code == 200:
root = etree.fromstring(response.text)
response = ''
for element in root.iter("{%s}Body" % s):
response = etree.tostring(element, pretty_print=True)
if response == '':
response = etree.tostring(root, pretty_print=True)
self.write({'get_numbering_range_response': response})
else:
raise ValidationError(msg1 % (response.status_code, response.reason))
except exceptions.RequestException as e:
raise ValidationError(msg2 % (e))
return True
def action_process_dian_documents(self):
for company in self:
count = 0
dian_documents = self.env['account.move.dian.document'].search(
[('state', 'in', ('draft', 'sent')), ('company_id', '=', company.id)],
order='zipped_filename asc')
for dian_document in dian_documents:
today = fields.Date.context_today(self)
date_from = dian_document.invoice_id.invoice_date
days = (today - date_from).days
if int(dian_document.invoice_id.send_invoice_to_dian) - 1 <= days:
try:
dian_document.action_process()
except:
count -= 1
if dian_document.state == 'done':
count += 1
# 10 -> 50 -JEG
if count == 50:
return True
return True
@api.model
def cron_process_dian_documents(self):
for company_id in self.search([]):
company_id.action_process_dian_documents()

View File

@ -0,0 +1,248 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import re
from odoo import api, models, fields, _
from odoo.exceptions import UserError, ValidationError
class ResPartner(models.Model):
_inherit = "res.partner"
is_einvoicing_agent = fields.Selection(
selection=[
('yes', 'Yes'),
('no_but', 'No, but has email'),
('no', 'No'),
('unknown', 'Unknown')],
string='Is an E-Invoicing Agent?',
default=False)
einvoicing_email = fields.Char(string='E-Invoicing Email')
validate_einvoicing_email = fields.Boolean(
string='Validate E-Invoicing Email?',
related='company_id.validate_einvoicing_email')
view_einvoicing_email_field = fields.Boolean(
string="View 'E-Invoicing Email' Fields",
compute='_get_view_einvoicing_email_field',
store=False)
edit_is_einvoicing_agent_field = fields.Boolean(
string="Edit 'Is an E-Invoicing Agent?' Field",
compute='_get_edit_is_einvoicing_agent_field',
store=False)
@api.onchange('person_type')
def onchange_person_type(self):
if self.person_type == '1':
self.is_einvoicing_agent = 'yes'
self.property_account_position_id = False
def _get_view_einvoicing_email_field(self):
user = self.env['res.users'].search([('id', '=', self.env.user.id)])
view_einvoicing_email_field = False
if user.has_group(
'l10n_co_account_e_invoicing.group_view_einvoicing_email_fields'):
view_einvoicing_email_field = True
for partner in self:
partner.view_einvoicing_email_field = view_einvoicing_email_field
def _get_edit_is_einvoicing_agent_field(self):
user = self.env['res.users'].search([('id', '=', self.env.user.id)])
edit_is_einvoicing_agent_field = False
if user.has_group(
'l10n_co_account_e_invoicing.group_edit_is_einvoicing_agent_field'):
edit_is_einvoicing_agent_field = True
for partner in self:
partner.edit_is_einvoicing_agent_field = edit_is_einvoicing_agent_field
@api.constrains('einvoicing_email')
@api.onchange('einvoicing_email')
def validate_mail(self):
if self.einvoicing_email:
for email in self.einvoicing_email.replace(' ', '').split(","):
match = re.match(
r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", email)
if match is None:
raise ValidationError(_("The field 'E-Invoicing Email' is not "
"correctly filled.\n\n"
"Please add @ and dot (.)"))
def _get_accounting_partner_party_values(self):
msg1 = _("'%s' does not have a person type established.")
msg2 = _("'%s' does not have a city established.")
msg3 = _("'%s' does not have a state established.")
msg4 = _("'%s' does not have a country established.")
msg5 = _("'%s' does not have a verification digit established.")
msg6 = _("'%s' does not have a DIAN document type established.")
msg7 = _("The document type of '%s' does not seem to correspond with the "
"person type.")
msg8 = _("'%s' does not have a identification document established.")
msg9 = _("'%s' does not have a fiscal position correctly configured.")
msg10 = _("'%s' does not have a fiscal position established.")
msg11 = _("E-Invoicing Agent: '%s' does not have a E-Invoicing Email.")
name = self.name
zip_code = False
identification_document = self.ref_num
telephone = False
if not self.person_type:
raise UserError(msg1 % self.name)
if self.country_id:
if self.country_id.code == 'CO':
if not self.city_id:
raise UserError(msg2 % self.name)
elif not self.state_id:
raise UserError(msg3 % self.name)
else:
raise UserError(msg4 % self.name)
if self.ref_type_id:
document_type_code = self.ref_type_id.code_dian
if (document_type_code == '31'
and not self.verification_code
and str(self.verification_code) != '0'):
raise UserError(msg5 % self.name)
# Punto 13.2.1. Documento de identificación (Tipo de Identificador Fiscal):
# cbc:CompanyID.@schemeName; sts:ProviderID.@schemeName
# Factura electrónica del anexo tecnico version 1.8
if document_type_code not in (
'11', '12', '13', '21', '22', '31', '41', '42', '47', '50', '91'):
if self.person_type == '1':
raise UserError(msg6 % self.name)
else:
name = 'consumidor final'
document_type_code = '13'
identification_document = '222222222222'
else:
raise UserError(msg6 % self.name)
if self.person_type == '1' and document_type_code not in ('31', '50'):
raise UserError(msg7 % self.name)
if not identification_document:
raise UserError(msg8 % self.name)
if self.property_account_position_id:
if (not self.property_account_position_id.tax_level_code_ids
or not self.property_account_position_id.party_tax_scheme_id
or not self.property_account_position_id.listname):
raise UserError(msg9 % self.name)
tax_level_codes = ''
tax_scheme_code = self.property_account_position_id.party_tax_scheme_id.code
tax_scheme_name = self.property_account_position_id.party_tax_scheme_id.name
else:
raise UserError(msg10 % self.name)
if (self.validate_einvoicing_email
and (not self.is_einvoicing_agent
or self.is_einvoicing_agent in ('yes', 'not_but'))
and not self.einvoicing_email):
raise UserError(msg11 % self.name)
if self.zip_id:
zip_code = self.zip_id.name
for tax_level_code_id in self.property_account_position_id.tax_level_code_ids:
if tax_level_codes == '':
tax_level_codes = tax_level_code_id.code
else:
tax_level_codes += ';' + tax_level_code_id.code
if self.phone and self.mobile:
telephone = self.phone + " / " + self.mobile
elif self.phone:
telephone = self.phone
elif self.mobile:
telephone = self.mobile
if identification_document == '222222222222':
tax_level_codes = 'R-99-PN'
tax_scheme_code = 'ZZ'
tax_scheme_name = 'No causa'
if self.property_account_position_id.listname != '49':
raise UserError(msg8 % self.name)
return {
'AdditionalAccountID': self.person_type,
'PartyName': self.commercial_name,
'Name': name,
'AddressID': (self.state_id.numeric_code or '') + (self.city_id.code or ''),
'AddressCityName': self.city_id.name or '',
'AddressPostalZone': zip_code,
'AddressCountrySubentity': self.state_id.name or '',
'AddressCountrySubentityCode': self.state_id.numeric_code or '',
'AddressLine': self.street or '',
'CompanyIDschemeID': self.verification_code,
'CompanyIDschemeName': document_type_code,
'CompanyID': identification_document,
'listName': self.property_account_position_id.listname,
'TaxLevelCode': tax_level_codes,
'TaxSchemeID': tax_scheme_code,
'TaxSchemeName': tax_scheme_name,
'CorporateRegistrationSchemeName': self.coc_registration_number,
'CountryIdentificationCode': self.country_id.code,
'CountryName': self.country_id.name,
'Telephone': telephone,
'Telefax': self.fax,
'ElectronicMail': self.einvoicing_email or self.email
}
def _get_delivery_values(self):
msg1 = _("'%s' does not have a city established.")
msg2 = _("'%s' does not have a state established.")
msg3 = _("'%s' does not have a country established.")
zip_code = False
if self.country_id:
if self.country_id.code == 'CO':
if not self.city_id:
raise UserError(msg1 % self.name)
elif not self.state_id:
raise UserError(msg2 % self.name)
else:
raise UserError(msg3 % self.name)
if self.zip_id:
zip_code = self.zip_id.name
return {
'AddressID': (self.state_id.numeric_code or '') + (self.city_id.code or ''),
'AddressCityName': self.city_id.name or '',
'AddressPostalZone': zip_code,
'AddressCountrySubentity': self.state_id.name or '',
'AddressCountrySubentityCode': self.state_id.numeric_code or '',
'AddressLine': self.street or '',
'CountryIdentificationCode': self.country_id.code,
'CountryName': self.country_id.name
}
def _get_tax_representative_party_values(self):
msg1 = _("'%s' does not have a verification digit established.")
msg2 = _("'%s' does not have a document type established.")
msg3 = _("'%s' does not have a identification document established.")
if self.ref_type_id:
if self.ref_type_id.code_dian == '31' and not self.verification_code:
raise UserError(msg1 % self.name)
else:
raise UserError(msg2 % self.name)
if not self.ref_num:
raise UserError(msg3 % self.name)
return {
'IDschemeID': self.verification_code,
'IDschemeName': self.ref_type_id.code_dian,
'ID': self.ref_num
}

View File

@ -0,0 +1,12 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<report id="einvoice_report"
model="account.move"
string="Factura Electrónica DIAN"
report_type="qweb-pdf"
name="l10n_co_account_e_invoicing.report_einvoice"
file="l10n_co_account_e_invoicing.report_einvoice"
attachment="(object.state == 'posted') and ((object.name or 'INV').replace('/','_')+'.pdf')"
print_report_name="(object._get_report_base_filename())"
groups="l10n_co_account_e_invoicing.group_view_dian_documents"/>
</odoo>

View File

@ -0,0 +1,459 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="customer_accept_invoice">
<title>Factura Aceptada</title>
<div style="border-radius: 2px; max-width: 1200px; height: auto;margin-left: auto;margin-right: auto;background-color:#f9f9f9;">
<div style="height:auto;text-align: center;font-size : 30px;color: #3b9c17;">
<strong>Factura Aceptada</strong>
</div>
<div style="height: 50px;text-align: center;font-size : 10px;border-collapse: separate;margin-top:10px">
<p style="margin-left:12px">
Por Avancys SAS
<a href="http://www.avancys.com">http://www.avancys.com</a>
. Proveedor tecnologico.
</p>
</div>
</div>
</template>
<template id="customer_reject_invoice">
<title>Factura Rechazada</title>
<div style="border-radius: 2px; max-width: 1200px; height: auto;margin-left: auto;margin-right: auto;background-color:#f9f9f9;">
<div style="height:auto;text-align: center;font-size : 30px;color: #d66058;">
<strong>Factura Rechazada</strong>
</div>
<div style="height: 50px;text-align: center;font-size : 10px;border-collapse: separate;margin-top:10px">
<p style="margin-left:12px">
Por Avancys SAS
<a href="http://www.avancys.com">http://www.avancys.com</a>
. Proveedor tecnologico.
</p>
</div>
</div>
</template>
<template id="report_einvoice_document">
<t t-call="web.basic_layout">
<style>
.infolabel {
font-size: 11px;
text-align: left;
font-weight: bold;
border-bottom: 0px
}
div.infolabel {
font-size: 11px;
text-align: center;
font-weight: bold;
border-bottom: 0px
}
div.infolabelbig {
font-size: 14px;
text-align: center;
font-weight: bold;
border-bottom: 0px
}
span.infodata{
font-size: 11px;
text-align: left;
border-bottom: 0px
}
span.infodatabottom{
font-size: 11px;
text-align: left;
border-bottom: 0px
}
strong.infodatabottom{
font-size: 11px;
text-align: left;
border-bottom: 0px
}
tr.inforow {
line-height: 25px;
min-height: 25px;
height: 25px;
}
.borderless td, .borderless tr {
border: none;
line-height: 15px;
min-height: 15px;
padding: 1px;
}
</style>
<div class="header">
<div class="row">
<div class="col-3">
<img t-if="o.company_id.logo" t-att-src="image_data_uri(o.company_id.logo)" width="130" height="auto" />
</div>
<div class="col-3">
<span style="font-size:10px" t-field="o.company_id.partner_id.name" />
<span style="font-size:10px">NIT </span><span style="font-size:10px" t-field="o.company_id.partner_id.ref_num" />
<br/>
<span style="font-size:10px" t-field="o.company_id.street" />
<span style="font-size:10px" t-field="o.company_id.street2" />
<br/>
<span style="font-size:10px" t-field="o.company_id.partner_id.city_id.name" />
,
<span style="font-size:10px" t-field="o.company_id.partner_id.country_id.name" />
<br/>
<span style="font-size:10px">TELEFONO </span><span style="font-size:10px" t-field="o.company_id.phone" />
<span style="font-size:10px" t-if="o.company_id.partner_id.mobile">/</span>
<span style="font-size:10px" t-field="o.company_id.partner_id.mobile" />
<br/>
<span style="font-size:10px" t-field="o.company_id.email" />
</div>
<div class="col-3">
</div>
<div t-if="o.dian_document_ids" class="col-3" style="width: 120px; height: 120px; text-align: right;">
<t t-foreach="o.dian_document_ids" t-as="dd">
<t t-if="dd.qr_image and dd.state != 'cancel'">
<img t-att-src="image_data_uri(dd.qr_image)" style="width:120px;height:120px"/>
</t>
</t>
</div>
</div>
</div>
<div class="row">
<div class="col-6 card card-body">
<table class="table borderless">
<tr class="inforow">
<td>
<span class="w-15 infolabel">
CLIENTE
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.name" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
NIT
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.ref_num" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
DIRECCIÓN
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.street" />
<span class="infodata" t-field="o.partner_id.street2" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
CIUDAD
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.city_id.name" />
-
<span class="infodata" t-field="o.partner_id.country_id.name" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
TELEFONO
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.phone" />
<span class="infodata" t-if="o.partner_id.mobile">/</span>
<span class="infodata" t-field="o.partner_id.mobile" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
EMAIL
</span>
</td>
<td>
<span class="infodata" t-field="o.partner_id.email" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
REFERENCIA
</span>
</td>
<td>
<span class="infodata" t-field="o.ref" />
</td>
</tr>
</table>
</div>
<div class="col-6 card">
<div class="infolabelbig card-header">
<span t-if="o.move_type == 'out_invoice' and not o.refund_type and o.dian_document_ids">
FACTURA ELECTRÓNICA DE VENTA
</span>
<span t-if="o.move_type == 'out_invoice' and not o.refund_type and not o.dian_document_ids">
FACTURA DE VENTA
</span>
<span t-if="o.move_type == 'out_refund' and o.refund_type == 'credit' and o.dian_document_ids">
NOTA CRÉDITO ELECTRONICA
</span>
<span t-if="o.move_type == 'out_refund' and o.refund_type == 'credit' and not o.dian_document_ids">
NOTA CRÉDITO
</span>
<span t-if="o.move_type == 'out_invoice' and o.refund_type == 'debit' and o.dian_document_ids">
NOTA DÉBITO ELECTRONICA
</span>
<span t-if="o.move_type == 'out_invoice' and o.refund_type == 'debit' and not o.dian_document_ids">
NOTA DÉBITO
</span>
<span t-if="o.move_type in ('in_invoice', 'in_refund') and o.dian_document_ids">
DOCUMENTO DE SOPORTE
</span>
<span t-if="o.move_type == 'in_invoice' and not o.dian_document_ids">
FACTURA PROVEEDOR
</span>
<span t-if="o.move_type == 'in_refund' and not o.dian_document_ids">
NOTA DÉBITO PROVEEDOR
</span>
<span t-if="o.move_type == 'out_invoice' and o.state == 'cancel'">CANCELADA</span>
<span t-if="o.name != '/'" t-field="o.name"/>
</div>
<div class="card-body">
<table class="table borderless">
<tr class="inforow">
<td>
<span class="w-15 infolabel">
FECHA DE EMISION
</span>
</td>
<td>
<span class="infodata" t-field="o.invoice_datetime" />
</td>
</tr>
<tr>
<td>
<span class="w-15 infolabel">
FECHA DE VENCIMIENTO
</span>
</td>
<td>
<span class="infodata" t-field="o.invoice_date_due" />
</td>
</tr>
<tr t-if="o.invoice_payment_term_id">
<td>
<span class="w-15 infolabel">
TERMINO DE PAGO
</span>
</td>
<td>
<span class="infodata" t-field="o.invoice_payment_term_id.name" />
</td>
</tr>
</table>
<div class="row">
<t t-foreach="o.dian_document_ids" t-as="dd">
<t t-if="dd.cufe_cude and o.move_type == 'out_invoice' and not o.refund_type and dd.state != 'cancel'">
<div class="col-12" style="font-size:8px; float:left; text-align:left; white-space: pre;"><strong>CUFE : </strong><span t-field="dd.cufe_cude"/></div>
</t>
<t t-if="dd.cufe_cude and dd.state != 'cancel' and (o.refund_type or o.move_type == 'in_invoice')">
<div class="col-12" style="font-size:8px; float:left; text-align:left; white-space: pre;"><strong>CUDE : </strong><span t-field="dd.cufe_cude"/></div>
</t>
</t>
</div>
</div>
</div>
</div>
<div class="page">
<t t-set="display_discount" t-value="any(l.discount for l in o.invoice_line_ids)"/>
<table class="table table-sm o_main_table" name="invoice_line_table">
<thead>
<tr>
<th name="th_defaultcode" class="text-left"><span class="infolabel"></span></th>
<th name="th_defaultcode" class="text-left"><span class="infolabel">REFERENCIA</span></th>
<th name="th_description" class="text-left"><span class="infolabel">DESCRIPCIÓN</span></th>
<th name="th_quantity" class="text-right"><span class="infolabel">CANTIDAD</span></th>
<th name="th_priceunit" t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}"><span class="infolabel">PRECIO UNIT.</span></th>
<th name="th_price_unit" t-if="display_discount" t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span class="infolabel">DESC.%</span>
</th>
<th name="th_taxes" t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}"><span class="infolabel">IMPUESTOS</span></th>
<th name="th_subtotal" class="text-right">
<span groups="account.group_show_line_subtotals_tax_excluded" class="infolabel">VALOR</span>
<span groups="account.group_show_line_subtotals_tax_included" class="infolabel">PRECIO TOTAL</span>
</th>
</tr>
</thead>
<tbody class="invoice_tbody">
<t t-set="current_subtotal" t-value="0"/>
<t t-set="lines" t-value="o.invoice_line_ids.sorted(key=lambda l: (-l.sequence, l.date, l.move_name, -l.id), reverse=True)"/>
<t t-set="n_item" t-value="0"/>
<t t-foreach="lines" t-as="line">
<t t-set="n_item" t-value="n_item+1"/>
<t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
<t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''">
<t t-if="not line.display_type" name="account_invoice_line_accountable">
<td name="account_invoice_line_number"><span class="infodatabottom" t-esc="n_item"/></td>
<td name="account_invoice_default_code"><span class="infodatabottom" t-field="line.product_id.default_code" t-options="{'widget': 'text'}"/></td>
<td name="account_invoice_line_name"><span class="infodatabottom" t-field="line.name" t-options="{'widget': 'text'}"/></td>
<td class="text-right">
<span class="infodatabottom" t-field="line.quantity"/>
<span class="infodatabottom" t-field="line.product_uom_id" groups="uom.group_uom"/>
</td>
<td t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span class="text-nowrap infodatabottom" t-field="line.price_unit"/>
</td>
<td t-if="display_discount" t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span class="text-nowrap infodatabottom" t-field="line.discount"/>
</td>
<td t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span class="infodatabottom" t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_ids))" id="line_tax_ids"/>
</td>
<td class="text-right o_price_total">
<span class="text-nowrap infodatabottom" t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
<span class="text-nowrap infodatabottom" t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
</td>
</t>
<t t-if="line.display_type == 'line_section'">
<td colspan="99">
<span t-field="line.name" t-options="{'widget': 'text'}"/>
</td>
<t t-set="current_section" t-value="line"/>
<t t-set="current_subtotal" t-value="0"/>
</t>
<t t-if="line.display_type == 'line_note'">
<td colspan="99">
<span t-field="line.name" t-options="{'widget': 'text'}"/>
</td>
</t>
</tr>
<t t-if="current_section and (line_last or lines[line_index+1].display_type == 'line_section')">
<tr class="is-subtotal text-right">
<td colspan="99">
<strong class="mr16">SUBTOTAL</strong>
<span t-esc="current_subtotal" t-options="{&quot;widget&quot;: &quot;monetary&quot;, &quot;display_currency&quot;: o.currency_id}"/>
</td>
</tr>
</t>
</t>
</tbody>
</table>
<div>
<span class="infodata" t-esc="n_item"></span> <span class="infodata" >Items</span>
</div>
<br/>
<div class="clearfix">
<div id="total" class="row">
<div class="col-6">
<t t-if="o.journal_id.sequence_id.description">
<div class="col-12" style="font-size:10px; float:left; text-align:left;"><span t-field="o.journal_id.sequence_id.description"/></div>
</t>
</div>
<div class="col-6">
<table class="table table-sm" style="page-break-inside: avoid;">
<tr class="border-black o_subtotal" style="">
<td><strong class="infodatabottom">SUBTOTAL</strong></td>
<td class="text-right">
<span class="infodatabottom" t-field="o.amount_untaxed"/>
</td>
</tr>
<t t-foreach="o.amount_by_group" t-as="amount_by_group">
<tr style="">
<t t-if="len(o.line_ids.filtered(lambda line: line.tax_line_id)) in [0, 1] and o.amount_untaxed == amount_by_group[2]">
<td><span class="text-nowrap infodatabottom" t-esc="amount_by_group[0]"/></td>
<td class="text-right o_price_total">
<span class="text-nowrap infodatabottom" t-esc="amount_by_group[3]"/>
</td>
</t>
<t t-else="">
<td>
<span class="infodatabottom" t-esc="amount_by_group[0]"/>
<span class="text-nowrap infodatabottom"> sobre
<t t-esc="amount_by_group[4]"/>
</span>
</td>
<td class="text-right o_price_total">
<span class="text-nowrap infodatabottom" t-esc="amount_by_group[3]"/>
</td>
</t>
</tr>
</t>
<tr class="border-black o_total">
<td><strong class="infodatabottom">TOTAL</strong></td>
<td class="text-right">
<span class="text-nowrap infodatabottom" t-field="o.amount_total"/>
</td>
</tr>
</table>
</div>
</div>
</div>
<p t-if="o.narration" name="comment">
<span class="infodata" t-field="o.narration"/>
</p>
<p t-if="o.fiscal_position_id.note" name="note">
<span class="infodata" t-field="o.fiscal_position_id.note"/>
</p>
<p t-if="o.invoice_incoterm_id" name="incoterm">
<strong class="infodatabottom">Incoterm: </strong><span class="infodata" t-field="o.invoice_incoterm_id.code"/> - <span class="infodata" t-field="o.invoice_incoterm_id.name"/>
</p>
</div>
<div class="footer">
<t t-foreach="o.dian_document_ids" t-as="dd">
<t t-if="dd.state != 'cancel'">
<span style="font-size:9px"><b>FECHA DE VALIDACION: </b></span><span style="font-size:9px" t-esc="dd.validation_datetime"/><br/>
<span style="font-size:9px"><b>INFORMACIÓN: </b></span><a style="font-size:9px"><span t-esc="dd.invoice_url"/></a>
</t>
</t>
<div class="text-center border-top" >
<small>
<strong style="font-size:10px">
<span style="font-size:10px" t-field="o.company_id.website" />
</strong>
</small>
</div>
<div class="text-left">
<p style="font-size:9px; margin-bottom: 10px">AvancysERP, por Avancys SAS, NIT 900297700</p>
</div>
</div>
</t>
</template>
<template id="report_einvoice">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-set="lang" t-value="o.invoice_user_id.sudo().lang if o.move_type in ('in_invoice', 'in_refund') else o.partner_id.lang"/>
<t t-call="l10n_co_account_e_invoicing.report_einvoice_document" t-lang="lang"/>
</t>
</t>
</template>
</odoo>

View File

@ -0,0 +1,10 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_fiscal_position_party_tax_scheme_manager,account_fiscal_position_party_tax_scheme_manager,model_account_fiscal_position_party_tax_scheme,account.group_account_manager,1,1,1,1
access_account_fiscal_position_party_tax_scheme_users,account_fiscal_position_party_tax_scheme_users,model_account_fiscal_position_party_tax_scheme,,1,0,0,0
access_account_fiscal_position_tax_level_code_manager,account_fiscal_position_tax_level_code_manager,model_account_fiscal_position_tax_level_code,account.group_account_manager,1,1,1,1
access_account_fiscal_position_tax_level_code_users,account_fiscal_position_tax_level_code_users,model_account_fiscal_position_tax_level_code,,1,0,0,0
access_account_move_dian_document_line_user,account_move_dian_document_line_user,model_account_move_dian_document_line,,1,1,1,0
access_account_move_dian_document_user,account_move_dian_document_user,model_account_move_dian_document,,1,1,1,0
access_account_move_summary_line_user,account_move_summary_line_user,model_account_move_summary_line,,1,1,1,0
access_einvoice_notification_group_user,einvoice_notification_group_user,model_einvoice_notification_group,,1,1,1,1
access_product_scheme_user,product_scheme_user,model_product_scheme,,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_fiscal_position_party_tax_scheme_manager account_fiscal_position_party_tax_scheme_manager model_account_fiscal_position_party_tax_scheme account.group_account_manager 1 1 1 1
3 access_account_fiscal_position_party_tax_scheme_users account_fiscal_position_party_tax_scheme_users model_account_fiscal_position_party_tax_scheme 1 0 0 0
4 access_account_fiscal_position_tax_level_code_manager account_fiscal_position_tax_level_code_manager model_account_fiscal_position_tax_level_code account.group_account_manager 1 1 1 1
5 access_account_fiscal_position_tax_level_code_users account_fiscal_position_tax_level_code_users model_account_fiscal_position_tax_level_code 1 0 0 0
6 access_account_move_dian_document_line_user account_move_dian_document_line_user model_account_move_dian_document_line 1 1 1 0
7 access_account_move_dian_document_user account_move_dian_document_user model_account_move_dian_document 1 1 1 0
8 access_account_move_summary_line_user account_move_summary_line_user model_account_move_summary_line 1 1 1 0
9 access_einvoice_notification_group_user einvoice_notification_group_user model_einvoice_notification_group 1 1 1 1
10 access_product_scheme_user product_scheme_user model_product_scheme 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AttachedDocument xmlns="urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:ccts="urn:un:unece:uncefact:data:specification:CoreComponentTypeSchemaModule:2"
xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"
xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"
xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#">
<cbc:UBLVersionID>UBL 2.1</cbc:UBLVersionID>
<cbc:CustomizationID>Documentos adjuntos</cbc:CustomizationID>
<cbc:ProfileID>DIAN 2.1</cbc:ProfileID>
<cbc:ProfileExecutionID>{{ProfileExecutionID}}</cbc:ProfileExecutionID>
<cbc:ID>{{ID}}</cbc:ID>
<cbc:IssueDate>{{IssueDate}}</cbc:IssueDate>
<cbc:IssueTime>{{IssueTime}}</cbc:IssueTime>
<cbc:DocumentType>Contenedor de Factura Electrónica</cbc:DocumentType>
<cbc:ParentDocumentID>{{ParentDocumentID}}</cbc:ParentDocumentID>
<cac:SenderParty>
<cac:PartyTaxScheme>
<cbc:RegistrationName>{{SenderParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195" schemeID="{{SenderParty.CompanyIDschemeID}}" schemeName="{{SenderParty.CompanyIDschemeName}}">{{SenderParty.CompanyID}}</cbc:CompanyID>
<cbc:TaxLevelCode listName="{{SenderParty.listName}}">{{SenderParty.TaxLevelCode}}</cbc:TaxLevelCode>
<cac:TaxScheme>
<cbc:ID>{{SenderParty.TaxSchemeID}}</cbc:ID>
<cbc:Name>{{SenderParty.TaxSchemeName}}</cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
</cac:SenderParty>
<cac:ReceiverParty>
<cac:PartyTaxScheme>
<cbc:RegistrationName>{{ReceiverParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195" schemeID="{{ReceiverParty.CompanyIDschemeID}}" schemeName="{{ReceiverParty.CompanyIDschemeName}}">{{ReceiverParty.CompanyID}}</cbc:CompanyID>
<cbc:TaxLevelCode listName="{{ReceiverParty.listName}}">{{ReceiverParty.TaxLevelCode}}</cbc:TaxLevelCode>
<cac:TaxScheme>
<cbc:ID>{{ReceiverParty.TaxSchemeID}}</cbc:ID>
<cbc:Name>{{ReceiverParty.TaxSchemeName}}</cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
</cac:ReceiverParty>
<cac:Attachment>
<cac:ExternalReference>
<cbc:MimeCode>text/xml</cbc:MimeCode>
<cbc:EncodingCode>UTF-8</cbc:EncodingCode>
<cbc:Description><![CDATA[{{InvoiceCNDN}}]]></cbc:Description>
</cac:ExternalReference>
</cac:Attachment>
<cac:ParentDocumentLineReference>
<cbc:LineID>1</cbc:LineID>
<cac:DocumentReference>
<cbc:ID>{{ParentDocumentID}}</cbc:ID>
<cbc:UUID schemeName="CUFE-SHA384">{{UUID}}</cbc:UUID>
<cbc:IssueDate>{{ValidationDate}}</cbc:IssueDate>
<cbc:DocumentType>ApplicationResponse</cbc:DocumentType>
<cac:Attachment>
<cac:ExternalReference>
<cbc:MimeCode>text/xml</cbc:MimeCode>
<cbc:EncodingCode>UTF-8</cbc:EncodingCode>
<cbc:Description><![CDATA[{{ApplicationResponse}}]]></cbc:Description>
</cac:ExternalReference>
</cac:Attachment>
<cac:ResultOfVerification>
<cbc:ValidatorID>Unidad Especial Dirección de Impuestos y Aduanas Nacionales</cbc:ValidatorID>
<cbc:ValidationResultCode>02</cbc:ValidationResultCode>
<cbc:ValidationDate>{{ValidationDate}}</cbc:ValidationDate>
<cbc:ValidationTime>{{ValidationTime}}</cbc:ValidationTime>
</cac:ResultOfVerification>
</cac:DocumentReference>
</cac:ParentDocumentLineReference>
</AttachedDocument>

View File

@ -0,0 +1,636 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<CreditNote xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"
xmlns:sts="http://www.dian.gov.co/contratos/facturaelectronica/v1/Structures"
xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"
xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2 http://docs.oasis-open.org/ubl/os-UBL-2.1/xsd/maindoc/UBL-CreditNote-2.1.xsd">
<ext:UBLExtensions>
<ext:UBLExtension>
<ext:ExtensionContent>
<sts:DianExtensions>
<sts:InvoiceSource>
<cbc:IdentificationCode listAgencyID="6"
listAgencyName="United Nations Economic Commission for Europe"
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1">CO</cbc:IdentificationCode>
</sts:InvoiceSource>
<sts:SoftwareProvider>
<sts:ProviderID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="{{ProviderIDschemeID}}"
schemeName="{{ProviderIDschemeName}}">{{ProviderID}}</sts:ProviderID>
<sts:SoftwareID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)">{{SoftwareID}}</sts:SoftwareID>
</sts:SoftwareProvider>
<sts:SoftwareSecurityCode schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)">{{SoftwareSecurityCode}}</sts:SoftwareSecurityCode>
<sts:AuthorizationProvider>
<sts:AuthorizationProviderID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="4"
schemeName="31">800197268</sts:AuthorizationProviderID>
</sts:AuthorizationProvider>
<sts:QRCode>NumFac: {{ID}}
FecFac: {{IssueDate}}
HorFac: {{IssueTime}}
NitFac: {{NitFac}}
DocAdq: {{DocAdq}}
ValFac: {{LineExtensionAmount}}
ValIva: {{ValIva}}
ValOtroIm: {{ValOtroIm}}
ValTolFac: {{PayableAmount}}
CUDE: {{UUID}}
{{QRCodeURL}}</sts:QRCode>
</sts:DianExtensions>
</ext:ExtensionContent>
</ext:UBLExtension>
<ext:UBLExtension>
<ext:ExtensionContent/>
</ext:UBLExtension>
</ext:UBLExtensions>
<cbc:UBLVersionID>UBL 2.1</cbc:UBLVersionID>
<cbc:CustomizationID>{{CustomizationID}}</cbc:CustomizationID>
<cbc:ProfileID>DIAN 2.1: Nota Crédito de Factura Electrónica de Venta</cbc:ProfileID>
<cbc:ProfileExecutionID>{{ProfileExecutionID}}</cbc:ProfileExecutionID>
<cbc:ID>{{ID}}</cbc:ID>
<cbc:UUID schemeID="{{ProfileExecutionID}}" schemeName="CUDE-SHA384">{{UUID}}</cbc:UUID>
<cbc:IssueDate>{{IssueDate}}</cbc:IssueDate>
<cbc:IssueTime>{{IssueTime}}</cbc:IssueTime>
<cbc:CreditNoteTypeCode>{{CreditNoteTypeCode}}</cbc:CreditNoteTypeCode>
<!--TODO 1.0: Que nota colocar?-->
<cbc:Note>{{Note}}</cbc:Note>
<cbc:DocumentCurrencyCode listAgencyID="6"
listAgencyName="United Nations Economic Commission for Europe"
listID="ISO 4217 Alpha">{{DocumentCurrencyCode}}</cbc:DocumentCurrencyCode>
<cbc:LineCountNumeric>{{LineCountNumeric}}</cbc:LineCountNumeric>
<!--TODO 3.0, Grupo de campos relativos al Periodo de Facturación: Intervalo de fechas la las que
referencia la factura por ejemplo en servicios públicos>
<cac:InvoicePeriod>
<cbc:StartDate>{{InvoicePeriodStartDate}}</cbc:StartDate>
<cbc:EndDate>{{InvoicePeriodEndDate}}</cbc:EndDate>
</cac:InvoicePeriod-->
<cac:DiscrepancyResponse>
<cbc:ReferenceID>{{DiscrepancyReferenceID}}</cbc:ReferenceID>
<cbc:ResponseCode>{{DiscrepancyResponseCode}}</cbc:ResponseCode>
<cbc:Description>{{DiscrepancyDescription}}</cbc:Description>
</cac:DiscrepancyResponse>
<!--TODO 1.0 TODAS LAS FACTURAS DEBEN SER DEL MISMO ADQUIRIENTE-->
<!--Si CreditNoteTypeCode igual a 20 es obligatorio-->
{% if BillingReference.ID %}
<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID>{{BillingReference.ID}}</cbc:ID>
<cbc:UUID schemeName="CUFE-SHA384">{{BillingReference.UUID}}</cbc:UUID>
<cbc:IssueDate>{{BillingReference.IssueDate}}</cbc:IssueDate>
</cac:InvoiceDocumentReference>
</cac:BillingReference>
{% endif %}
<!--TODO 2.0: Si se habilita sale Error Regla ZB01>
<cac:OrderReference>
<cbc:ID>{{OrderReferenceID}}</cbc:ID>
<cbc:IssueDate>{{OrderReferenceIssueDate}}</cbc:IssueDate>
</cac:OrderReference-->
<!--TODO 3.0: opcional, solo interés mercantil, para referenciar uno o más documentos de despacho asociado>
<cac:DespatchDocumentReference>
<cbc:ID>8124167214 DA</cbc:ID>
<cbc:IssueDate>2019-12-12</cbc:IssueDate>
</cac:DespatchDocumentReference-->
<!--TODO 3.0: opcional, solo interés mercantil, para referenciar uno o más documentos de recepción asociado>
<cac:ReceiptDocumentReference>
<cbc:ID>12314129 GR</cbc:ID>
<cbc:IssueDate>2019-12-12</cbc:IssueDate>
</cac:ReceiptDocumentReference-->
<!--TODO 3.0: opcional, Referencia a documentos adicionales que hacen parte de la NC.
Especifación de este grupo igual a la del documento Invoice>
<cac:AdditionalDocumentReference>
<cbc:ID>12314129 GR</cbc:ID>
<cbc:IssueDate>2019-12-12</cbc:IssueDate>
<cbc:DocumentTypeCode>Punto 13.1.4. Referencia a otros documentos. (la tabla existe en el anexo 1.7 punto 6.1.4) del anexo tecnico version 1.8</cbc:DocumentTypeCode>
</cac:AdditionalDocumentReference-->
<cac:AccountingSupplierParty>
<cbc:AdditionalAccountID>{{AccountingSupplierParty.AdditionalAccountID}}</cbc:AdditionalAccountID>
<cac:Party>
{% if IndustryClassificationCode %}
<cbc:IndustryClassificationCode>{{IndustryClassificationCode}}</cbc:IndustryClassificationCode>
{% endif %}
{% if AccountingSupplierParty.PartyName %}
<cac:PartyName>
<cbc:Name>{{AccountingSupplierParty.PartyName}}</cbc:Name>
</cac:PartyName>
{% endif %}
<cac:PhysicalLocation>
<cac:Address>
<cbc:ID>{{AccountingSupplierParty.AddressID}}</cbc:ID>
<cbc:CityName>{{AccountingSupplierParty.AddressCityName}}</cbc:CityName>
{% if AccountingSupplierParty.AddressPostalZone %}
<cbc:PostalZone>{{AccountingSupplierParty.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{AccountingSupplierParty.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{AccountingSupplierParty.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{AccountingSupplierParty.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{AccountingSupplierParty.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{AccountingSupplierParty.CountryName}}</cbc:Name>
</cac:Country>
</cac:Address>
</cac:PhysicalLocation>
<cac:PartyTaxScheme>
<cbc:RegistrationName>{{AccountingSupplierParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="{{AccountingSupplierParty.CompanyIDschemeID}}"
schemeName="{{AccountingSupplierParty.CompanyIDschemeName}}">{{AccountingSupplierParty.CompanyID}}</cbc:CompanyID>
<!--TODO 2.0: listName el anexo dice que eliminar o valor No aplica,
pero para consumidor final dice informar 49-->
<cbc:TaxLevelCode listName="{{AccountingSupplierParty.listName}}">{{AccountingSupplierParty.TaxLevelCode}}</cbc:TaxLevelCode>
<cac:RegistrationAddress>
<cbc:ID>{{AccountingSupplierParty.AddressID}}</cbc:ID>
<cbc:CityName>{{AccountingSupplierParty.AddressCityName}}</cbc:CityName>
{% if AccountingSupplierParty.AddressPostalZone %}
<cbc:PostalZone>{{AccountingSupplierParty.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{AccountingSupplierParty.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{AccountingSupplierParty.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{AccountingSupplierParty.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{AccountingSupplierParty.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{AccountingCustomerParty.CountryName}}</cbc:Name>
</cac:Country>
</cac:RegistrationAddress>
<cac:TaxScheme>
<cbc:ID>{{AccountingSupplierParty.TaxSchemeID}}</cbc:ID>
<cbc:Name>{{AccountingSupplierParty.TaxSchemeName}}</cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>{{AccountingSupplierParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="{{AccountingSupplierParty.CompanyIDschemeID}}"
schemeName="{{AccountingSupplierParty.CompanyIDschemeName}}">{{AccountingSupplierParty.CompanyID}}</cbc:CompanyID>
{% if AccountingSupplierParty.CorporateRegistrationSchemeName %}
<cac:CorporateRegistrationScheme>
<cbc:Name>{{AccountingSupplierParty.CorporateRegistrationSchemeName}}</cbc:Name>
</cac:CorporateRegistrationScheme>
{% endif %}
<!--TODO 3.0 Si se va a opera bajo modalidad de Consorcio, entonces este grupo de información debe ser informada.
De debe completar un grupo de elementos por cada participante del consorcio.>
<cac:ShareholderParty>
<cbc:PartecipationPercent>10.00</cbc:PartecipationPercent>
<cac:Party>
<cac:PartyTaxScheme>
<cbc:RegistrationName></cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID=""
schemeName=""></cbc:CompanyID>
<TODO 2.0: listName el anexo dice que eliminar o valor No aplica,
pero para consumidor final dice informar 49>
<cbc:TaxLevelCode listName=""></cbc:TaxLevelCode>
<cac:TaxScheme>
<cbc:ID></cbc:ID>
<cbc:Name></cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
</cac:Party>
</cac:ShareholderParty-->
</cac:PartyLegalEntity>
{% if AccountingSupplierParty.Telephone or AccountingSupplierParty.Telefax or AccountingSupplierParty.ElectronicMail %}
<cac:Contact>
<!--TODO 3.0: opcional, de momento se manda info de la empresa sin nombre de contacto>
<cbc:Name>Diana Cruz</cbc:Name-->
{% if AccountingSupplierParty.Telephone %}
<cbc:Telephone>{{AccountingSupplierParty.Telephone}}</cbc:Telephone>
{% endif %}
{% if AccountingSupplierParty.Telefax %}
<cbc:Telefax>{{AccountingSupplierParty.Telefax}}</cbc:Telefax>
{% endif %}
{% if AccountingSupplierParty.ElectronicMail %}
<cbc:ElectronicMail>{{AccountingSupplierParty.ElectronicMail}}</cbc:ElectronicMail>
{% endif %}
<!--TODO 2.0: opcional, de momento se manda info de la empresa no se sabe que mandar aca de momento>
<cbc:Note></cbc:Note-->
</cac:Contact>
{% endif %}
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cbc:AdditionalAccountID>{{AccountingCustomerParty.AdditionalAccountID}}</cbc:AdditionalAccountID>
<cac:Party>
<cac:PartyIdentification>
<cbc:ID {% if AccountingCustomerParty.CompanyIDschemeName == '31' %}schemeID="{{AccountingCustomerParty.CompanyIDschemeID}}"{% endif %}
schemeName="{{AccountingCustomerParty.CompanyIDschemeName}}">{{AccountingCustomerParty.CompanyID}}</cbc:ID>
</cac:PartyIdentification>
{% if AccountingCustomerParty.PartyName %}
<cac:PartyName>
<cbc:Name>{{AccountingCustomerParty.PartyName}}</cbc:Name>
</cac:PartyName>
{% endif %}
{% if AccountingCustomerParty.CompanyID != '222222222222' %}
<cac:PhysicalLocation>
<cac:Address>
<cbc:ID>{{AccountingCustomerParty.AddressID}}</cbc:ID>
<cbc:CityName>{{AccountingCustomerParty.AddressCityName}}</cbc:CityName>
{% if AccountingCustomerParty.AddressPostalZone %}
<cbc:PostalZone>{{AccountingCustomerParty.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{AccountingCustomerParty.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{AccountingCustomerParty.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{AccountingCustomerParty.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{AccountingCustomerParty.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{AccountingCustomerParty.CountryName}}</cbc:Name>
</cac:Country>
</cac:Address>
</cac:PhysicalLocation>
{% endif %}
<cac:PartyTaxScheme>
<cbc:RegistrationName>{{AccountingCustomerParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
{% if AccountingCustomerParty.CompanyIDschemeName == '31' %}schemeID="{{AccountingCustomerParty.CompanyIDschemeID}}"{% endif %}
schemeName="{{AccountingCustomerParty.CompanyIDschemeName}}">{{AccountingCustomerParty.CompanyID}}</cbc:CompanyID>
<!--TODO 2.0: listName el anexo dice que eliminar o valor No aplica,
pero para consumidor final dice informar 49-->
<cbc:TaxLevelCode listName="{{AccountingCustomerParty.listName}}">{{AccountingCustomerParty.TaxLevelCode}}</cbc:TaxLevelCode>
{% if AccountingCustomerParty.CompanyID != '222222222222' %}
<cac:RegistrationAddress>
<cbc:ID>{{AccountingCustomerParty.AddressID}}</cbc:ID>
<cbc:CityName>{{AccountingCustomerParty.AddressCityName}}</cbc:CityName>
{% if AccountingCustomerParty.AddressPostalZone %}
<cbc:PostalZone>{{AccountingCustomerParty.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{AccountingCustomerParty.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{AccountingCustomerParty.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{AccountingCustomerParty.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{AccountingCustomerParty.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{AccountingCustomerParty.CountryName}}</cbc:Name>
</cac:Country>
</cac:RegistrationAddress>
{% endif %}
<cac:TaxScheme>
<cbc:ID>{{AccountingCustomerParty.TaxSchemeID}}</cbc:ID>
<cbc:Name>{{AccountingCustomerParty.TaxSchemeName}}</cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>{{AccountingCustomerParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
{% if AccountingCustomerParty.CompanyIDschemeName == '31' %}schemeID="{{AccountingCustomerParty.CompanyIDschemeID}}"{% endif %}
schemeName="{{AccountingCustomerParty.CompanyIDschemeName}}">{{AccountingCustomerParty.CompanyID}}</cbc:CompanyID>
{% if AccountingCustomerParty.CorporateRegistrationSchemeName %}
<cac:CorporateRegistrationScheme>
<cbc:Name>{{AccountingCustomerParty.CorporateRegistrationSchemeName}}</cbc:Name>
</cac:CorporateRegistrationScheme>
{% endif %}
</cac:PartyLegalEntity>
{% if AccountingCustomerParty.CompanyID != '222222222222' and (AccountingCustomerParty.Telephone or AccountingCustomerParty.Telefax or AccountingCustomerParty.ElectronicMail) %}
<cac:Contact>
<!--TODO 3.0: opcional, de momento se manda info de la empresa sin nombre de contacto>
<cbc:Name>Diana Cruz</cbc:Name-->
{% if AccountingCustomerParty.Telephone %}
<cbc:Telephone>{{AccountingCustomerParty.Telephone}}</cbc:Telephone>
{% endif %}
{% if AccountingCustomerParty.Telefax %}
<cbc:Telefax>{{AccountingCustomerParty.Telefax}}</cbc:Telefax>
{% endif %}
{% if AccountingCustomerParty.ElectronicMail %}
<cbc:ElectronicMail>{{AccountingCustomerParty.ElectronicMail}}</cbc:ElectronicMail>
{% endif %}
<!--TODO 2.0: opcional, de momento se manda info de la empresa no se sabe que mandar aca de momento>
<cbc:Note></cbc:Note-->
</cac:Contact>
{% endif %}
</cac:Party>
</cac:AccountingCustomerParty>
<!--TODO 2.0 Grupo de información de la Persona autorizada para descargar documentos-->
<!--cac:TaxRepresentativeParty>
<cac:PartyIdentification>
<cbc:ID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID=""
schemeName=""></cbc:ID>
</cac:PartyIdentification>
</cac:TaxRepresentativeParty-->
<cac:Delivery>
<cbc:ActualDeliveryDate>{{ActualDeliveryDate}}</cbc:ActualDeliveryDate>
<cbc:ActualDeliveryTime>{{ActualDeliveryTime}}</cbc:ActualDeliveryTime>
{% if AccountingCustomerParty.CompanyID != '222222222222' %}
<cac:DeliveryAddress>
<cbc:ID>{{Delivery.AddressID}}</cbc:ID>
<cbc:CityName>{{Delivery.AddressCityName}}</cbc:CityName>
{% if Delivery.AddressPostalZone %}
<cbc:PostalZone>{{Delivery.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{Delivery.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{Delivery.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{Delivery.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{Delivery.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{Delivery.CountryName}}</cbc:Name>
</cac:Country>
</cac:DeliveryAddress>
{% endif %}
<!--TODO 3.0, Grupo de datos con información sobre la empresa de transporte Desarrollo futuro>
<cac:DeliveryParty>
<cac:PartyName>
<cbc:Name></cbc:Name>
</cac:PartyName>
<cac:PhysicalLocation>
<cac:Address>
<cbc:ID>11001</cbc:ID>
<cbc:CityName>Bogotá, D.c. </cbc:CityName>
<cbc:PostalZone>Bogotá, D.c. </cbc:PostalZone>
<cbc:CountrySubentity>Bogotá</cbc:CountrySubentity>
<cbc:CountrySubentityCode>11</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>Av. #17 - 193</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>CO</cbc:IdentificationCode>
<TODO 2.0: languageID podria variar, siguiente version revisar esto>
<cbc:Name languageID="es">Colombia</cbc:Name>
</cac:Country>
</cac:Address>
</cac:PhysicalLocation>
<cac:PartyTaxScheme>
<cbc:RegistrationName>Empresa de transporte</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="1"
schemeName="31">981223983</cbc:CompanyID>
<TODO 2.0: listName el anexo dice que eliminar o valor No aplica,
pero para consumidor final dice informar 49>
<cbc:TaxLevelCode listName="">O-99</cbc:TaxLevelCode>
<cac:RegistrationAddress>
<cbc:ID>11001</cbc:ID>
<cbc:CityName>Bogotá, D.c. </cbc:CityName>
<cbc:PostalZone>Bogotá, D.c. </cbc:PostalZone>
<cbc:CountrySubentity>Bogotá</cbc:CountrySubentity>
<cbc:CountrySubentityCode>11</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>Av. #17 - 193</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>CO</cbc:IdentificationCode>
<TODO 2.0: languageID podria variar, siguiente version revisar esto>
<cbc:Name languageID="es">Colombia</cbc:Name>
</cac:Country>
</cac:RegistrationAddress>
<cac:TaxScheme>
<cbc:ID>01</cbc:ID>
<cbc:Name>IVA</cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Empresa de transporte</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="1"
schemeName="31">981223983</cbc:CompanyID>
<cac:CorporateRegistrationScheme>
<cbc:Name>75433</cbc:Name>
</cac:CorporateRegistrationScheme>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>Eric Van Boxsom</cbc:Name>
<cbc:Telephone>9712311</cbc:Telephone>
<cbc:Telefax>12431241</cbc:Telefax>
<cbc:ElectronicMail>eric.vanboxsom@gosocket.net</cbc:ElectronicMail>
<cbc:Note>Test descripcion contacto</cbc:Note>
</cac:Contact>
</cac:DeliveryParty-->
</cac:Delivery>
{% if DeliveryTerms.LossRiskResponsibilityCode %}
<cac:DeliveryTerms>
<cbc:ID>1</cbc:ID>
<!--TODO 2.0: Con otro modulo complementario se puede resolver el texto libre-->
<!--cbc:SpecialTerms>Portes Pagados</cbc:SpecialTerms-->
<cbc:LossRiskResponsibilityCode>{{DeliveryTerms.LossRiskResponsibilityCode}}</cbc:LossRiskResponsibilityCode>
<cbc:LossRisk>{{DeliveryTerms.LossRisk}}</cbc:LossRisk>
</cac:DeliveryTerms>
{% endif %}
<cac:PaymentMeans>
<cbc:ID>{{PaymentMeansID}}</cbc:ID>
<cbc:PaymentMeansCode>{{PaymentMeansCode}}</cbc:PaymentMeansCode>
<cbc:PaymentDueDate>{{PaymentDueDate}}</cbc:PaymentDueDate>
<!--TODO 2.0: Identificador del pago, pueden ir de cero a varios PaymentID-->
<!--cbc:PaymentID>{{PaymentID}}</cbc:PaymentID-->
</cac:PaymentMeans>
{% if DocumentCurrencyCode != 'COP' %}
<cac:PaymentExchangeRate>
<cbc:SourceCurrencyCode>{{PaymentExchangeRate.SourceCurrencyCode}}</cbc:SourceCurrencyCode>
<cbc:SourceCurrencyBaseRate>1.00</cbc:SourceCurrencyBaseRate>
<cbc:TargetCurrencyCode>{{PaymentExchangeRate.TargetCurrencyCode}}</cbc:TargetCurrencyCode>
<cbc:TargetCurrencyBaseRate>1.00</cbc:TargetCurrencyBaseRate>
<cbc:CalculationRate>{{'{:.2f}'.format(PaymentExchangeRate.CalculationRate)}}</cbc:CalculationRate>
<cbc:Date>{{PaymentExchangeRate.Date}}</cbc:Date>
</cac:PaymentExchangeRate>
{% endif %}
<!--TODO 3.0: Grupo de campos para información relacionadas con un cargo o un descuento>
<cac:AllowanceCharge>
<cbc:ID>SFR3123856</cbc:ID>
<cbc:ChargeIndicator></cbc:ChargeIndicator>
<cbc:AllowanceChargeReasonCode></cbc:AllowanceChargeReasonCode>
<cbc:AllowanceChargeReason></cbc:AllowanceChargeReason>
<cbc:MultiplierFactorNumeric>Prepago recibido</cbc:MultiplierFactorNumeric>
<cbc:Amount currencyID="{{DocumentCurrencyCode}}">1000.00</cbc:Amount>
<cbc:BaseAmount currencyID="{{DocumentCurrencyCode}}">1000.00</cbc:BaseAmount>
</cac:AllowanceCharge-->
{% for TaxTotalID, TaxTotal in TaxesTotal.items() %}
{% if TaxTotal.total != 0 %}
<cac:TaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxTotal.total)}}</cbc:TaxAmount>
<!--TODO 2.0: Se manda 0.00 por ahora mientras se evalua la necesidad de calculos-->
<cbc:RoundingAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:RoundingAmount>
{% for Percent, TaxSubtotal in TaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<!--TODO 1.0: Usado en el caso de que el tributo es un valor fijo>
<cbc:BaseUnitMeasure></cbc:BaseUnitMeasure>
<cbc:unitCode></cbc:unitCode>
<cbc:PerUnitAmount currencyID="{{DocumentCurrencyCode}}"></cbc:TaxAmount-->
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{TaxTotalID}}</cbc:ID>
<cbc:Name>{{TaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:TaxTotal>
{% endif %}
{% endfor %}
{% for WithholdingTaxTotalID, WithholdingTaxTotal in WithholdingTaxesTotal.items() %}
<cac:WithholdingTaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(WithholdingTaxTotal.total)}}</cbc:TaxAmount>
{% for Percent, TaxSubtotal in WithholdingTaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{WithholdingTaxTotalID}}</cbc:ID>
<cbc:Name>{{WithholdingTaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:WithholdingTaxTotal>
{% endfor %}
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="{{DocumentCurrencyCode}}">{{LineExtensionAmount}}</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="{{DocumentCurrencyCode}}">{{TaxExclusiveAmount}}</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="{{DocumentCurrencyCode}}">{{TaxInclusiveAmount}}</cbc:TaxInclusiveAmount>
<!--TODO 3.0: los prepagos son los anticipos, no hay soporte aun en el odoo para identificar esto
PrepaidAmount == 0 mientras tanto, PrepaidAmount = suma de PrepaidPayment
AllowanceTotalAmount == 0 y ChargeTotalAmount == 0 mientras tanto, suma de AllowanceCharge
TaxInclusiveAmount == PayableAmount mientras tanto-->
<cbc:AllowanceTotalAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:AllowanceTotalAmount>
<cbc:ChargeTotalAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:ChargeTotalAmount>
<cbc:PrepaidAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:PrepaidAmount>
<cbc:PayableAmount currencyID="{{DocumentCurrencyCode}}">{{PayableAmount}}</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
{% for CreditNoteLineID, CreditNoteLine in CreditNoteLines.items() %}
<cac:CreditNoteLine>
<cbc:ID>{{CreditNoteLineID}}</cbc:ID>
{% if BillingReference.CustomizationID == '09' %}
<cbc:Note>Contrato de servicios AIU por concepto de: {{CreditNoteLine.ItemDescription}}</cbc:Note>
{% endif %}
<cbc:CreditedQuantity unitCode="{{CreditNoteLine.unitCode}}">{{CreditNoteLine.Quantity}}</cbc:CreditedQuantity>
<cbc:LineExtensionAmount currencyID="{{DocumentCurrencyCode}}">{{CreditNoteLine.LineExtensionAmount}}</cbc:LineExtensionAmount>
{% if CreditNoteLine.LineExtensionAmount == '0.00' %}
<cac:PricingReference>
<cac:AlternativeConditionPrice>
<cbc:PriceAmount currencyID="{{DocumentCurrencyCode}}">{{CreditNoteLine.PricingReferencePriceAmount}}</cbc:PriceAmount>
<cbc:PriceTypeCode>01</cbc:PriceTypeCode>
</cac:AlternativeConditionPrice>
</cac:PricingReference>
{% endif %}
{% for TaxTotalID, TaxTotal in CreditNoteLine.TaxesTotal.items() %}
{% if TaxTotal.total != 0 %}
<cac:TaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxTotal.total)}}</cbc:TaxAmount>
<!--TODO 2.0: Se manda 0.00 por ahora mientras se evalua la necesidad de calculos-->
<cbc:RoundingAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:RoundingAmount>
{% for Percent, TaxSubtotal in TaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<!--TODO 1.0: Usado en el caso de que el tributo es un valor fijo>
<cbc:BaseUnitMeasure></cbc:BaseUnitMeasure>
<cbc:unitCode></cbc:unitCode>
<cbc:PerUnitAmount currencyID="{{DocumentCurrencyCode}}"></cbc:TaxAmount-->
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{TaxTotalID}}</cbc:ID>
<cbc:Name>{{TaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:TaxTotal>
{% endif %}
{% endfor %}
{% for WithholdingTaxTotalID, WithholdingTaxTotal in CreditNoteLine.WithholdingTaxesTotal.items() %}
<cac:WithholdingTaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(WithholdingTaxTotal.total)}}</cbc:TaxAmount>
{% for Percent, TaxSubtotal in WithholdingTaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{WithholdingTaxTotalID}}</cbc:ID>
<cbc:Name>{{WithholdingTaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:WithholdingTaxTotal>
{% endfor %}
<!--TODO 2.0, puede iterar en el campo ID aumenta segun la cantidad de descuentos o cargos por lineas
ChargeIndicator true seria un cargo-->
<cac:AllowanceCharge>
<cbc:ID>1</cbc:ID>
<cbc:ChargeIndicator>false</cbc:ChargeIndicator>
<cbc:AllowanceChargeReason>Descuento</cbc:AllowanceChargeReason>
<cbc:MultiplierFactorNumeric>{{CreditNoteLine.MultiplierFactorNumeric}}</cbc:MultiplierFactorNumeric>
<cbc:Amount currencyID="{{DocumentCurrencyCode}}">{{CreditNoteLine.AllowanceChargeAmount}}</cbc:Amount>
<cbc:BaseAmount currencyID="{{DocumentCurrencyCode}}">{{CreditNoteLine.AllowanceChargeBaseAmount}}</cbc:BaseAmount>
</cac:AllowanceCharge>
<cac:Item>
<cbc:Description>{{CreditNoteLine.ItemDescription}}</cbc:Description>
<!--TODO 2.0, Cantidad de unidad de este artículo por empaque>
<cbc:PackSizeNumeric>{{CreditNoteLine.PackSizeNumeric}}</cbc:PackSizeNumeric-->
{% if CreditNoteLine.BrandName %}
<cbc:BrandName>{{CreditNoteLine.BrandName}}</cbc:BrandName>
{% endif %}
<!--TODO 2.0, Mandaremos la referencia del fabricante por ahora, hay que definir luego si esto cambia-->
{% if CreditNoteLine.ModelName %}
<cbc:ModelName>{{CreditNoteLine.ModelName}}</cbc:ModelName>
{% endif %}
<!--TODO 2.0, Grupo de datos de identificación del artículo o servicio de acuerdo con el vendedor>
<cac:SellersItemIdentification>
<cbc:ID>AOHV84-225</cbc:ID>
<cbc:ExtendedID>AOHV84-225</cbc:ExtendedID>
</cac:SellersItemIdentification-->
<!--TODO 2.0, Grupo de datos de identificación del artículo o servicio de acuerdo con un estándar-->
<!--OPCIONAL schemeName="Estándar de adopción del contribuyente" schemeAgencyID="" schemeAgencyName=""-->
<cac:StandardItemIdentification>
<cbc:ID schemeID="999">{{CreditNoteLine.StandardItemIdentification}}</cbc:ID>
</cac:StandardItemIdentification>
<!--TODO 2.0, opcional, Grupo de información para adicionar información específica del ítem que puede
ser solicitada por autoridades o entidades diferentes a la DIAN>
<cac:AdditionalItemProperty>
<cbc:Name></cbc:Name>
<cbc:Value></cbc:Value>
<cbc:ValueQuantity unitCode=""></cbc:ValueQuantity>
</cac:AdditionalItemProperty-->
{% if BillingReference.CustomizationID == '11' %}
<cac:InformationContentProviderParty>
<cac:PowerOfAttorney>
<cac:AgentParty>
<cac:PartyIdentification>
<cbc:ID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="{{CreditNoteLine.InformationContentProviderParty.IDschemeID}}"
schemeName="{{CreditNoteLine.InformationContentProviderParty.IDschemeName}}">{{CreditNoteLine.InformationContentProviderParty.ID}}</cbc:ID>
</cac:PartyIdentification>
</cac:AgentParty>
</cac:PowerOfAttorney>
</cac:InformationContentProviderParty>
{% endif %}
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="{{DocumentCurrencyCode}}">{{CreditNoteLine.PriceAmount}}</cbc:PriceAmount>
<cbc:BaseQuantity unitCode="{{CreditNoteLine.unitCode}}">{{CreditNoteLine.Quantity}}</cbc:BaseQuantity>
</cac:Price>
</cac:CreditNoteLine>
{% endfor %}
</CreditNote>

View File

@ -0,0 +1,635 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<DebitNote xmlns="urn:oasis:names:specification:ubl:schema:xsd:DebitNote-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"
xmlns:sts="http://www.dian.gov.co/contratos/facturaelectronica/v1/Structures"
xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"
xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:specification:ubl:schema:xsd:DebitNote-2 http://docs.oasis-open.org/ubl/os-UBL-2.1/xsd/maindoc/UBL-DebitNote-2.1.xsd">
<ext:UBLExtensions>
<ext:UBLExtension>
<ext:ExtensionContent>
<sts:DianExtensions>
<sts:InvoiceSource>
<cbc:IdentificationCode listAgencyID="6"
listAgencyName="United Nations Economic Commission for Europe"
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1">CO</cbc:IdentificationCode>
</sts:InvoiceSource>
<sts:SoftwareProvider>
<sts:ProviderID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="{{ProviderIDschemeID}}"
schemeName="{{ProviderIDschemeName}}">{{ProviderID}}</sts:ProviderID>
<sts:SoftwareID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)">{{SoftwareID}}</sts:SoftwareID>
</sts:SoftwareProvider>
<sts:SoftwareSecurityCode schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)">{{SoftwareSecurityCode}}</sts:SoftwareSecurityCode>
<sts:AuthorizationProvider>
<sts:AuthorizationProviderID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="4"
schemeName="31">800197268</sts:AuthorizationProviderID>
</sts:AuthorizationProvider>
<sts:QRCode>NumFac: {{ID}}
FecFac: {{IssueDate}}
HorFac: {{IssueTime}}
NitFac: {{NitFac}}
DocAdq: {{DocAdq}}
ValFac: {{LineExtensionAmount}}
ValIva: {{ValIva}}
ValOtroIm: {{ValOtroIm}}
ValTolFac: {{PayableAmount}}
CUDE: {{UUID}}
{{QRCodeURL}}</sts:QRCode>
</sts:DianExtensions>
</ext:ExtensionContent>
</ext:UBLExtension>
<ext:UBLExtension>
<ext:ExtensionContent/>
</ext:UBLExtension>
</ext:UBLExtensions>
<cbc:UBLVersionID>UBL 2.1</cbc:UBLVersionID>
<cbc:CustomizationID>{{CustomizationID}}</cbc:CustomizationID>
<cbc:ProfileID>DIAN 2.1: Nota Débito de Factura Electrónica de Venta</cbc:ProfileID>
<cbc:ProfileExecutionID>{{ProfileExecutionID}}</cbc:ProfileExecutionID>
<cbc:ID>{{ID}}</cbc:ID>
<cbc:UUID schemeID="{{ProfileExecutionID}}" schemeName="CUDE-SHA384">{{UUID}}</cbc:UUID>
<cbc:IssueDate>{{IssueDate}}</cbc:IssueDate>
<cbc:IssueTime>{{IssueTime}}</cbc:IssueTime>
<!--TODO 1.0: Establecer opcional-->
<cbc:Note>{{Note}}</cbc:Note>
<cbc:DocumentCurrencyCode listAgencyID="6"
listAgencyName="United Nations Economic Commission for Europe"
listID="ISO 4217 Alpha">{{DocumentCurrencyCode}}</cbc:DocumentCurrencyCode>
<cbc:LineCountNumeric>{{LineCountNumeric}}</cbc:LineCountNumeric>
<!--TODO 3.0, Grupo de campos relativos al Periodo de Facturación: Intervalo de fechas la las que
referencia la factura por ejemplo en servicios públicos>
<cac:InvoicePeriod>
<cbc:StartDate>{{InvoicePeriodStartDate}}</cbc:StartDate>
<cbc:EndDate>{{InvoicePeriodEndDate}}</cbc:EndDate>
</cac:InvoicePeriod-->
<cac:DiscrepancyResponse>
<cbc:ReferenceID>{{DiscrepancyReferenceID}}</cbc:ReferenceID>
<cbc:ResponseCode>{{DiscrepancyResponseCode}}</cbc:ResponseCode>
<cbc:Description>{{DiscrepancyDescription}}</cbc:Description>
</cac:DiscrepancyResponse>
<!--TODO 1.0 TODAS LAS FACTURAS DEBEN SER DEL MISMO ADQUIRIENTE-->
<!--Si DebitNoteTypeCode igual a 30 es obligatorio-->
{% if BillingReference.ID %}
<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID>{{BillingReference.ID}}</cbc:ID>
<cbc:UUID schemeName="CUFE-SHA384">{{BillingReference.UUID}}</cbc:UUID>
<cbc:IssueDate>{{BillingReference.IssueDate}}</cbc:IssueDate>
</cac:InvoiceDocumentReference>
</cac:BillingReference>
{% endif %}
<!--TODO 2.0: Si se habilita sale Error Regla ZB01>
<cac:OrderReference>
<cbc:ID>{{OrderReferenceID}}</cbc:ID>
<cbc:IssueDate>{{OrderReferenceIssueDate}}</cbc:IssueDate>
</cac:OrderReference-->
<!--TODO 3.0: opcional, solo interés mercantil, para referenciar uno o más documentos de despacho asociado>
<cac:DespatchDocumentReference>
<cbc:ID>8124167214 DA</cbc:ID>
<cbc:IssueDate>2019-12-12</cbc:IssueDate>
</cac:DespatchDocumentReference-->
<!--TODO 3.0: opcional, solo interés mercantil, para referenciar uno o más documentos de recepción asociado>
<cac:ReceiptDocumentReference>
<cbc:ID>12314129 GR</cbc:ID>
<cbc:IssueDate>2019-12-12</cbc:IssueDate>
</cac:ReceiptDocumentReference-->
<!--TODO 3.0: opcional, Referencia a documentos adicionales que hacen parte de la NC.
Especifación de este grupo igual a la del documento Invoice>
<cac:AdditionalDocumentReference>
<cbc:ID>12314129 GR</cbc:ID>
<cbc:IssueDate>2019-12-12</cbc:IssueDate>
<cbc:DocumentTypeCode>Punto 13.1.4. Referencia a otros documentos. (la tabla existe en el anexo 1.7 punto 6.1.4) del anexo tecnico version 1.8</cbc:DocumentTypeCode>
</cac:AdditionalDocumentReference-->
<cac:AccountingSupplierParty>
<cbc:AdditionalAccountID>{{AccountingSupplierParty.AdditionalAccountID}}</cbc:AdditionalAccountID>
<cac:Party>
{% if IndustryClassificationCode %}
<cbc:IndustryClassificationCode>{{IndustryClassificationCode}}</cbc:IndustryClassificationCode>
{% endif %}
{% if AccountingSupplierParty.PartyName %}
<cac:PartyName>
<cbc:Name>{{AccountingSupplierParty.PartyName}}</cbc:Name>
</cac:PartyName>
{% endif %}
<cac:PhysicalLocation>
<cac:Address>
<cbc:ID>{{AccountingSupplierParty.AddressID}}</cbc:ID>
<cbc:CityName>{{AccountingSupplierParty.AddressCityName}}</cbc:CityName>
{% if AccountingSupplierParty.AddressPostalZone %}
<cbc:PostalZone>{{AccountingSupplierParty.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{AccountingSupplierParty.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{AccountingSupplierParty.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{AccountingSupplierParty.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{AccountingSupplierParty.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{AccountingSupplierParty.CountryName}}</cbc:Name>
</cac:Country>
</cac:Address>
</cac:PhysicalLocation>
<cac:PartyTaxScheme>
<cbc:RegistrationName>{{AccountingSupplierParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="{{AccountingSupplierParty.CompanyIDschemeID}}"
schemeName="{{AccountingSupplierParty.CompanyIDschemeName}}">{{AccountingSupplierParty.CompanyID}}</cbc:CompanyID>
<!--TODO 2.0: listName el anexo dice que eliminar o valor No aplica,
pero para consumidor final dice informar 49-->
<cbc:TaxLevelCode listName="{{AccountingSupplierParty.listName}}">{{AccountingSupplierParty.TaxLevelCode}}</cbc:TaxLevelCode>
<cac:RegistrationAddress>
<cbc:ID>{{AccountingSupplierParty.AddressID}}</cbc:ID>
<cbc:CityName>{{AccountingSupplierParty.AddressCityName}}</cbc:CityName>
{% if AccountingSupplierParty.AddressPostalZone %}
<cbc:PostalZone>{{AccountingSupplierParty.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{AccountingSupplierParty.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{AccountingSupplierParty.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{AccountingSupplierParty.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{AccountingSupplierParty.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{AccountingCustomerParty.CountryName}}</cbc:Name>
</cac:Country>
</cac:RegistrationAddress>
<cac:TaxScheme>
<cbc:ID>{{AccountingSupplierParty.TaxSchemeID}}</cbc:ID>
<cbc:Name>{{AccountingSupplierParty.TaxSchemeName}}</cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>{{AccountingSupplierParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="{{AccountingSupplierParty.CompanyIDschemeID}}"
schemeName="{{AccountingSupplierParty.CompanyIDschemeName}}">{{AccountingSupplierParty.CompanyID}}</cbc:CompanyID>
{% if AccountingSupplierParty.CorporateRegistrationSchemeName %}
<cac:CorporateRegistrationScheme>
<cbc:Name>{{AccountingSupplierParty.CorporateRegistrationSchemeName}}</cbc:Name>
</cac:CorporateRegistrationScheme>
{% endif %}
<!--TODO 3.0 Si se va a opera bajo modalidad de Consorcio, entonces este grupo de información debe ser informada.
De debe completar un grupo de elementos por cada participante del consorcio.>
<cac:ShareholderParty>
<cbc:PartecipationPercent>10.00</cbc:PartecipationPercent>
<cac:Party>
<cac:PartyTaxScheme>
<cbc:RegistrationName></cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID=""
schemeName=""></cbc:CompanyID>
<TODO 2.0: listName el anexo dice que eliminar o valor No aplica,
pero para consumidor final dice informar 49>
<cbc:TaxLevelCode listName=""></cbc:TaxLevelCode>
<cac:TaxScheme>
<cbc:ID></cbc:ID>
<cbc:Name></cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
</cac:Party>
</cac:ShareholderParty-->
</cac:PartyLegalEntity>
{% if AccountingSupplierParty.Telephone or AccountingSupplierParty.Telefax or AccountingSupplierParty.ElectronicMail %}
<cac:Contact>
<!--TODO 3.0: opcional, de momento se manda info de la empresa sin nombre de contacto>
<cbc:Name>Diana Cruz</cbc:Name-->
{% if AccountingSupplierParty.Telephone %}
<cbc:Telephone>{{AccountingSupplierParty.Telephone}}</cbc:Telephone>
{% endif %}
{% if AccountingSupplierParty.Telefax %}
<cbc:Telefax>{{AccountingSupplierParty.Telefax}}</cbc:Telefax>
{% endif %}
{% if AccountingSupplierParty.ElectronicMail %}
<cbc:ElectronicMail>{{AccountingSupplierParty.ElectronicMail}}</cbc:ElectronicMail>
{% endif %}
<!--TODO 2.0: opcional, de momento se manda info de la empresa no se sabe que mandar aca de momento>
<cbc:Note></cbc:Note-->
</cac:Contact>
{% endif %}
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cbc:AdditionalAccountID>{{AccountingCustomerParty.AdditionalAccountID}}</cbc:AdditionalAccountID>
<cac:Party>
<cac:PartyIdentification>
<cbc:ID {% if AccountingCustomerParty.CompanyIDschemeName == '31' %}schemeID="{{AccountingCustomerParty.CompanyIDschemeID}}"{% endif %}
schemeName="{{AccountingCustomerParty.CompanyIDschemeName}}">{{AccountingCustomerParty.CompanyID}}</cbc:ID>
</cac:PartyIdentification>
{% if AccountingCustomerParty.PartyName%}
<cac:PartyName>
<cbc:Name>{{AccountingCustomerParty.PartyName}}</cbc:Name>
</cac:PartyName>
{% endif %}
{% if AccountingCustomerParty.CompanyID != '222222222222' %}
<cac:PhysicalLocation>
<cac:Address>
<cbc:ID>{{AccountingCustomerParty.AddressID}}</cbc:ID>
<cbc:CityName>{{AccountingCustomerParty.AddressCityName}}</cbc:CityName>
{% if AccountingCustomerParty.AddressPostalZone %}
<cbc:PostalZone>{{AccountingCustomerParty.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{AccountingCustomerParty.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{AccountingCustomerParty.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{AccountingCustomerParty.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{AccountingCustomerParty.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{AccountingCustomerParty.CountryName}}</cbc:Name>
</cac:Country>
</cac:Address>
</cac:PhysicalLocation>
{% endif %}
<cac:PartyTaxScheme>
<cbc:RegistrationName>{{AccountingCustomerParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
{% if AccountingCustomerParty.CompanyIDschemeName == '31' %}schemeID="{{AccountingCustomerParty.CompanyIDschemeID}}"{% endif %}
schemeName="{{AccountingCustomerParty.CompanyIDschemeName}}">{{AccountingCustomerParty.CompanyID}}</cbc:CompanyID>
<!--TODO 2.0: listName el anexo dice que eliminar o valor No aplica,
pero para consumidor final dice informar 49-->
<cbc:TaxLevelCode listName="{{AccountingCustomerParty.listName}}">{{AccountingCustomerParty.TaxLevelCode}}</cbc:TaxLevelCode>
{% if AccountingCustomerParty.CompanyID != '222222222222' %}
<cac:RegistrationAddress>
<cbc:ID>{{AccountingCustomerParty.AddressID}}</cbc:ID>
<cbc:CityName>{{AccountingCustomerParty.AddressCityName}}</cbc:CityName>
{% if AccountingCustomerParty.AddressPostalZone %}
<cbc:PostalZone>{{AccountingCustomerParty.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{AccountingCustomerParty.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{AccountingCustomerParty.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{AccountingCustomerParty.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{AccountingCustomerParty.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{AccountingCustomerParty.CountryName}}</cbc:Name>
</cac:Country>
</cac:RegistrationAddress>
{% endif %}
<cac:TaxScheme>
<cbc:ID>{{AccountingCustomerParty.TaxSchemeID}}</cbc:ID>
<cbc:Name>{{AccountingCustomerParty.TaxSchemeName}}</cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>{{AccountingCustomerParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
{% if AccountingCustomerParty.CompanyIDschemeName == '31' %}schemeID="{{AccountingCustomerParty.CompanyIDschemeID}}"{% endif %}
schemeName="{{AccountingCustomerParty.CompanyIDschemeName}}">{{AccountingCustomerParty.CompanyID}}</cbc:CompanyID>
{% if AccountingCustomerParty.CorporateRegistrationSchemeName %}
<cac:CorporateRegistrationScheme>
<cbc:Name>{{AccountingCustomerParty.CorporateRegistrationSchemeName}}</cbc:Name>
</cac:CorporateRegistrationScheme>
{% endif %}
</cac:PartyLegalEntity>
{% if AccountingCustomerParty.CompanyID != '222222222222' and (AccountingCustomerParty.Telephone or AccountingCustomerParty.Telefax or AccountingCustomerParty.ElectronicMail) %}
<cac:Contact>
<!--TODO 3.0: opcional, de momento se manda info de la empresa sin nombre de contacto>
<cbc:Name>Diana Cruz</cbc:Name-->
{% if AccountingCustomerParty.Telephone %}
<cbc:Telephone>{{AccountingCustomerParty.Telephone}}</cbc:Telephone>
{% endif %}
{% if AccountingCustomerParty.Telefax %}
<cbc:Telefax>{{AccountingCustomerParty.Telefax}}</cbc:Telefax>
{% endif %}
{% if AccountingCustomerParty.ElectronicMail %}
<cbc:ElectronicMail>{{AccountingCustomerParty.ElectronicMail}}</cbc:ElectronicMail>
{% endif %}
<!--TODO 2.0: opcional, de momento se manda info de la empresa no se sabe que mandar aca de momento>
<cbc:Note></cbc:Note-->
</cac:Contact>
{% endif %}
</cac:Party>
</cac:AccountingCustomerParty>
<!--TODO 2.0 Grupo de información de la Persona autorizada para descargar documentos-->
<!--cac:TaxRepresentativeParty>
<cac:PartyIdentification>
<cbc:ID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID=""
schemeName=""></cbc:ID>
</cac:PartyIdentification>
</cac:TaxRepresentativeParty-->
<!--TODO 3.0: Grupo de campos para información relacionadas con un cargo o un descuento>
<cac:AllowanceCharge>
<cbc:ID>SFR3123856</cbc:ID>
<cbc:ChargeIndicator></cbc:ChargeIndicator>
<cbc:AllowanceChargeReasonCode></cbc:AllowanceChargeReasonCode>
<cbc:AllowanceChargeReason></cbc:AllowanceChargeReason>
<cbc:MultiplierFactorNumeric>Prepago recibido</cbc:MultiplierFactorNumeric>
<cbc:Amount currencyID="{{DocumentCurrencyCode}}">1000.00</cbc:Amount>
<cbc:BaseAmount currencyID="{{DocumentCurrencyCode}}">1000.00</cbc:BaseAmount>
</cac:AllowanceCharge-->
<cac:Delivery>
<cbc:ActualDeliveryDate>{{ActualDeliveryDate}}</cbc:ActualDeliveryDate>
<cbc:ActualDeliveryTime>{{ActualDeliveryTime}}</cbc:ActualDeliveryTime>
{% if AccountingCustomerParty.CompanyID != '222222222222' %}
<cac:DeliveryAddress>
<cbc:ID>{{Delivery.AddressID}}</cbc:ID>
<cbc:CityName>{{Delivery.AddressCityName}}</cbc:CityName>
{% if Delivery.AddressPostalZone %}
<cbc:PostalZone>{{Delivery.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{Delivery.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{Delivery.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{Delivery.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{Delivery.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{Delivery.CountryName}}</cbc:Name>
</cac:Country>
</cac:DeliveryAddress>
{% endif %}
<!--TODO 3.0, Grupo de datos con información sobre la empresa de transporte Desarrollo futuro>
<cac:DeliveryParty>
<cac:PartyName>
<cbc:Name></cbc:Name>
</cac:PartyName>
<cac:PhysicalLocation>
<cac:Address>
<cbc:ID>11001</cbc:ID>
<cbc:CityName>Bogotá, D.c. </cbc:CityName>
<cbc:PostalZone>Bogotá, D.c. </cbc:PostalZone>
<cbc:CountrySubentity>Bogotá</cbc:CountrySubentity>
<cbc:CountrySubentityCode>11</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>Av. #17 - 193</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>CO</cbc:IdentificationCode>
<TODO 2.0: languageID podria variar, siguiente version revisar esto>
<cbc:Name languageID="es">Colombia</cbc:Name>
</cac:Country>
</cac:Address>
</cac:PhysicalLocation>
<cac:PartyTaxScheme>
<cbc:RegistrationName>Empresa de transporte</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="1"
schemeName="31">981223983</cbc:CompanyID>
<TODO 2.0: listName el anexo dice que eliminar o valor No aplica,
pero para consumidor final dice informar 49>
<cbc:TaxLevelCode listName="">O-99</cbc:TaxLevelCode>
<cac:RegistrationAddress>
<cbc:ID>11001</cbc:ID>
<cbc:CityName>Bogotá, D.c. </cbc:CityName>
<cbc:PostalZone>Bogotá, D.c. </cbc:PostalZone>
<cbc:CountrySubentity>Bogotá</cbc:CountrySubentity>
<cbc:CountrySubentityCode>11</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>Av. #17 - 193</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>CO</cbc:IdentificationCode>
<TODO 2.0: languageID podria variar, siguiente version revisar esto>
<cbc:Name languageID="es">Colombia</cbc:Name>
</cac:Country>
</cac:RegistrationAddress>
<cac:TaxScheme>
<cbc:ID>01</cbc:ID>
<cbc:Name>IVA</cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Empresa de transporte</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="1"
schemeName="31">981223983</cbc:CompanyID>
<cac:CorporateRegistrationScheme>
<cbc:Name>75433</cbc:Name>
</cac:CorporateRegistrationScheme>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>Eric Van Boxsom</cbc:Name>
<cbc:Telephone>9712311</cbc:Telephone>
<cbc:Telefax>12431241</cbc:Telefax>
<cbc:ElectronicMail>eric.vanboxsom@gosocket.net</cbc:ElectronicMail>
<cbc:Note>Test descripcion contacto</cbc:Note>
</cac:Contact>
</cac:DeliveryParty-->
</cac:Delivery>
{% if DeliveryTerms.LossRiskResponsibilityCode %}
<cac:DeliveryTerms>
<cbc:ID>1</cbc:ID>
<!--TODO 2.0: Con otro modulo complementario se puede resolver el texto libre-->
<!--cbc:SpecialTerms>Portes Pagados</cbc:SpecialTerms-->
<cbc:LossRiskResponsibilityCode>{{DeliveryTerms.LossRiskResponsibilityCode}}</cbc:LossRiskResponsibilityCode>
<cbc:LossRisk>{{DeliveryTerms.LossRisk}}</cbc:LossRisk>
</cac:DeliveryTerms>
{% endif %}
<cac:PaymentMeans>
<cbc:ID>{{PaymentMeansID}}</cbc:ID>
<cbc:PaymentMeansCode>{{PaymentMeansCode}}</cbc:PaymentMeansCode>
<cbc:PaymentDueDate>{{PaymentDueDate}}</cbc:PaymentDueDate>
<!--TODO 2.0: Identificador del pago, pueden ir de cero a varios PaymentID-->
<!--cbc:PaymentID></cbc:PaymentID-->
</cac:PaymentMeans>
{% if DocumentCurrencyCode != 'COP' %}
<cac:PaymentExchangeRate>
<cbc:SourceCurrencyCode>{{PaymentExchangeRate.SourceCurrencyCode}}</cbc:SourceCurrencyCode>
<cbc:SourceCurrencyBaseRate>1.00</cbc:SourceCurrencyBaseRate>
<cbc:TargetCurrencyCode>{{PaymentExchangeRate.TargetCurrencyCode}}</cbc:TargetCurrencyCode>
<cbc:TargetCurrencyBaseRate>1.00</cbc:TargetCurrencyBaseRate>
<cbc:CalculationRate>{{'{:.2f}'.format(PaymentExchangeRate.CalculationRate)}}</cbc:CalculationRate>
<cbc:Date>{{PaymentExchangeRate.Date}}</cbc:Date>
</cac:PaymentExchangeRate>
{% endif %}
{% for TaxTotalID, TaxTotal in TaxesTotal.items() %}
{% if TaxTotal.total != 0 %}
<cac:TaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxTotal.total)}}</cbc:TaxAmount>
<!--TODO 2.0: Se manda 0.00 por ahora mientras se evalua la necesidad de calculos-->
<cbc:RoundingAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:RoundingAmount>
{% for Percent, TaxSubtotal in TaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<!--TODO 1.0: Usado en el caso de que el tributo es un valor fijo>
<cbc:BaseUnitMeasure></cbc:BaseUnitMeasure>
<cbc:unitCode></cbc:unitCode>
<cbc:PerUnitAmount currencyID="{{DocumentCurrencyCode}}"></cbc:TaxAmount-->
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{TaxTotalID}}</cbc:ID>
<cbc:Name>{{TaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:TaxTotal>
{% endif %}
{% endfor %}
{% for WithholdingTaxTotalID, WithholdingTaxTotal in WithholdingTaxesTotal.items() %}
<cac:WithholdingTaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(WithholdingTaxTotal.total)}}</cbc:TaxAmount>
{% for Percent, TaxSubtotal in WithholdingTaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{WithholdingTaxTotalID}}</cbc:ID>
<cbc:Name>{{WithholdingTaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:WithholdingTaxTotal>
{% endfor %}
<cac:RequestedMonetaryTotal>
<cbc:LineExtensionAmount currencyID="{{DocumentCurrencyCode}}">{{LineExtensionAmount}}</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="{{DocumentCurrencyCode}}">{{TaxExclusiveAmount}}</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="{{DocumentCurrencyCode}}">{{TaxInclusiveAmount}}</cbc:TaxInclusiveAmount>
<!--TODO 3.0: los prepagos son los anticipos, no hay soporte aun en el odoo para identificar esto
PrepaidAmount == 0 mientras tanto, PrepaidAmount = suma de PrepaidPayment
AllowanceTotalAmount == 0 y ChargeTotalAmount == 0 mientras tanto, suma de AllowanceCharge
TaxInclusiveAmount == PayableAmount mientras tanto-->
<cbc:AllowanceTotalAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:AllowanceTotalAmount>
<cbc:ChargeTotalAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:ChargeTotalAmount>
<cbc:PrepaidAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:PrepaidAmount>
<cbc:PayableAmount currencyID="{{DocumentCurrencyCode}}">{{PayableAmount}}</cbc:PayableAmount>
</cac:RequestedMonetaryTotal>
{% for DebitNoteLineID, DebitNoteLine in DebitNoteLines.items() %}
<cac:DebitNoteLine>
<cbc:ID>{{DebitNoteLineID}}</cbc:ID>
{% if BillingReference.CustomizationID == '09' %}
<cbc:Note>Contrato de servicios AIU por concepto de: {{DebitNoteLine.ItemDescription}}</cbc:Note>
{% endif %}
<cbc:DebitedQuantity unitCode="{{DebitNoteLine.unitCode}}">{{DebitNoteLine.Quantity}}</cbc:DebitedQuantity>
<cbc:LineExtensionAmount currencyID="{{DocumentCurrencyCode}}">{{DebitNoteLine.LineExtensionAmount}}</cbc:LineExtensionAmount>
{% if DebitNoteLine.LineExtensionAmount == '0.00' %}
<cac:PricingReference>
<cac:AlternativeConditionPrice>
<cbc:PriceAmount currencyID="{{DocumentCurrencyCode}}">{{DebitNoteLine.PricingReferencePriceAmount}}</cbc:PriceAmount>
<cbc:PriceTypeCode>01</cbc:PriceTypeCode>
</cac:AlternativeConditionPrice>
</cac:PricingReference>
{% endif %}
{% for TaxTotalID, TaxTotal in DebitNoteLine.TaxesTotal.items() %}
{% if TaxTotal.total != 0 %}
<cac:TaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxTotal.total)}}</cbc:TaxAmount>
<!--TODO 2.0: Se manda 0.00 por ahora mientras se evalua la necesidad de calculos-->
<cbc:RoundingAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:RoundingAmount>
{% for Percent, TaxSubtotal in TaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<!--TODO 1.0: Usado en el caso de que el tributo es un valor fijo>
<cbc:BaseUnitMeasure></cbc:BaseUnitMeasure>
<cbc:unitCode></cbc:unitCode>
<cbc:PerUnitAmount currencyID="{{DocumentCurrencyCode}}"></cbc:TaxAmount-->
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{TaxTotalID}}</cbc:ID>
<cbc:Name>{{TaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:TaxTotal>
{% endif %}
{% endfor %}
{% for WithholdingTaxTotalID, WithholdingTaxTotal in DebitNoteLine.WithholdingTaxesTotal.items() %}
<cac:WithholdingTaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(WithholdingTaxTotal.total)}}</cbc:TaxAmount>
{% for Percent, TaxSubtotal in WithholdingTaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{WithholdingTaxTotalID}}</cbc:ID>
<cbc:Name>{{WithholdingTaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:WithholdingTaxTotal>
{% endfor %}
<!--TODO 2.0, puede iterar en el campo ID aumenta segun la cantidad de descuentos o cargos por lineas
ChargeIndicator true seria un cargo-->
<cac:AllowanceCharge>
<cbc:ID>1</cbc:ID>
<cbc:ChargeIndicator>false</cbc:ChargeIndicator>
<cbc:AllowanceChargeReason>Descuento</cbc:AllowanceChargeReason>
<cbc:MultiplierFactorNumeric>{{DebitNoteLine.MultiplierFactorNumeric}}</cbc:MultiplierFactorNumeric>
<cbc:Amount currencyID="{{DocumentCurrencyCode}}">{{DebitNoteLine.AllowanceChargeAmount}}</cbc:Amount>
<cbc:BaseAmount currencyID="{{DocumentCurrencyCode}}">{{DebitNoteLine.AllowanceChargeBaseAmount}}</cbc:BaseAmount>
</cac:AllowanceCharge>
<cac:Item>
<cbc:Description>{{DebitNoteLine.ItemDescription}}</cbc:Description>
<!--TODO 2.0, Cantidad de unidad de este artículo por empaque>
<cbc:PackSizeNumeric>{{DebitNoteLine.PackSizeNumeric}}</cbc:PackSizeNumeric-->
{% if DebitNoteLine.BrandName %}
<cbc:BrandName>{{DebitNoteLine.BrandName}}</cbc:BrandName>
{% endif %}
<!--TODO 2.0, Mandaremos la referencia del fabricante por ahora, hay que definir luego si esto cambia-->
{% if DebitNoteLine.ModelName %}
<cbc:ModelName>{{DebitNoteLine.ModelName}}</cbc:ModelName>
{% endif %}
<!--TODO 2.0, Grupo de datos de identificación del artículo o servicio de acuerdo con el vendedor>
<cac:SellersItemIdentification>
<cbc:ID>AOHV84-225</cbc:ID>
<cbc:ExtendedID>AOHV84-225</cbc:ExtendedID>
</cac:SellersItemIdentification-->
<!--TODO 2.0, Grupo de datos de identificación del artículo o servicio de acuerdo con un estándar-->
<!--OPCIONAL schemeName="Estándar de adopción del contribuyente" schemeAgencyID="" schemeAgencyName=""-->
<cac:StandardItemIdentification>
<cbc:ID schemeID="999">{{DebitNoteLine.StandardItemIdentification}}</cbc:ID>
</cac:StandardItemIdentification>
<!--TODO 2.0, opcional, Grupo de información para adicionar información específica del ítem que puede
ser solicitada por autoridades o entidades diferentes a la DIAN>
<cac:AdditionalItemProperty>
<cbc:Name></cbc:Name>
<cbc:Value></cbc:Value>
<cbc:ValueQuantity unitCode=""></cbc:ValueQuantity>
</cac:AdditionalItemProperty-->
{% if BillingReference.CustomizationID == '11' %}
<cac:InformationContentProviderParty>
<cac:PowerOfAttorney>
<cac:AgentParty>
<cac:PartyIdentification>
<cbc:ID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="{{DebitNoteLine.InformationContentProviderParty.IDschemeID}}"
schemeName="{{DebitNoteLine.InformationContentProviderParty.IDschemeName}}">{{DebitNoteLine.InformationContentProviderParty.ID}}</cbc:ID>
</cac:PartyIdentification>
</cac:AgentParty>
</cac:PowerOfAttorney>
</cac:InformationContentProviderParty>
{% endif %}
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="{{DocumentCurrencyCode}}">{{DebitNoteLine.PriceAmount}}</cbc:PriceAmount>
<cbc:BaseQuantity unitCode="{{DebitNoteLine.unitCode}}">{{DebitNoteLine.Quantity}}</cbc:BaseQuantity>
</cac:Price>
</cac:DebitNoteLine>
{% endfor %}
</DebitNote>

View File

@ -0,0 +1,574 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"
xmlns:sts="dian:gov:co:facturaelectronica:Structures-2-1"
xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"
xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2 http://docs.oasis-open.org/ubl/os-UBL-2.1/xsd/maindoc/UBL-Invoice-2.1.xsd">
<ext:UBLExtensions>
<ext:UBLExtension>
<ext:ExtensionContent>
<sts:DianExtensions>
<sts:InvoiceControl>
<sts:InvoiceAuthorization>{{InvoiceControl.InvoiceAuthorization}}</sts:InvoiceAuthorization>
<sts:AuthorizationPeriod>
<cbc:StartDate>{{InvoiceControl.StartDate}}</cbc:StartDate>
<cbc:EndDate>{{InvoiceControl.EndDate}}</cbc:EndDate>
</sts:AuthorizationPeriod>
<sts:AuthorizedInvoices>
{% if InvoiceControl.Prefix %}
<sts:Prefix>{{InvoiceControl.Prefix}}</sts:Prefix>
{% endif %}
<sts:From>{{InvoiceControl.From}}</sts:From>
<sts:To>{{InvoiceControl.To}}</sts:To>
</sts:AuthorizedInvoices>
</sts:InvoiceControl>
<sts:InvoiceSource>
<cbc:IdentificationCode listAgencyID="6"
listAgencyName="United Nations Economic Commission for Europe"
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1">CO</cbc:IdentificationCode>
</sts:InvoiceSource>
<sts:SoftwareProvider>
<sts:ProviderID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="{{ProviderIDschemeID}}"
schemeName="{{ProviderIDschemeName}}">{{ProviderID}}</sts:ProviderID>
<sts:SoftwareID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)">{{SoftwareID}}</sts:SoftwareID>
</sts:SoftwareProvider>
<sts:SoftwareSecurityCode schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)">{{SoftwareSecurityCode}}</sts:SoftwareSecurityCode>
<sts:AuthorizationProvider>
<sts:AuthorizationProviderID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="4"
schemeName="31">800197268</sts:AuthorizationProviderID>
</sts:AuthorizationProvider>
<sts:QRCode>NumFac: {{ID}}
FecFac: {{IssueDate}}
HorFac: {{IssueTime}}
NitFac: {{NitFac}}
DocAdq: {{DocAdq}}
ValFac: {{LineExtensionAmount}}
ValIva: {{ValIva}}
ValOtroIm: {{ValOtroIm}}
ValTolFac: {{PayableAmount}}
CUDE: {{UUID}}
{{QRCodeURL}}</sts:QRCode>
</sts:DianExtensions>
</ext:ExtensionContent>
</ext:UBLExtension>
<ext:UBLExtension>
<ext:ExtensionContent/>
</ext:UBLExtension>
</ext:UBLExtensions>
<cbc:UBLVersionID>UBL 2.1</cbc:UBLVersionID>
<cbc:CustomizationID>{{CustomizationID}}</cbc:CustomizationID>
<cbc:ProfileID>DIAN 2.1: documento soporte en adquisiciones efectuadas a no obligados a facturar.</cbc:ProfileID>
<cbc:ProfileExecutionID>{{ProfileExecutionID}}</cbc:ProfileExecutionID>
<cbc:ID>{{ID}}</cbc:ID>
<cbc:UUID schemeID="{{ProfileExecutionID}}" schemeName="CUDE-SHA384">{{UUID}}</cbc:UUID>
<cbc:IssueDate>{{IssueDate}}</cbc:IssueDate>
<cbc:IssueTime>{{IssueTime}}</cbc:IssueTime>
<cbc:DueDate>{{DueDate}}</cbc:DueDate>
<cbc:InvoiceTypeCode>{{InvoiceTypeCode}}</cbc:InvoiceTypeCode>
<!--TODO 1.0: Que nota colocar?-->
<cbc:Note>{{Note}}</cbc:Note>
<cbc:DocumentCurrencyCode listAgencyID="6"
listAgencyName="United Nations Economic Commission for Europe"
listID="ISO 4217 Alpha">{{DocumentCurrencyCode}}</cbc:DocumentCurrencyCode>
<cbc:LineCountNumeric>{{LineCountNumeric}}</cbc:LineCountNumeric>
<!--TODO 3.0: Grupo de campos relativos al Periodo de Facturación: Intervalo de fechas la las que
referencia la factura por ejemplo en servicios públicos>
<cac:InvoicePeriod>
<cbc:StartDate>{{InvoicePeriodStartDate}}</cbc:StartDate>
<cbc:EndDate>{{InvoicePeriodEndDate}}</cbc:EndDate>
</cac:InvoicePeriod-->
{% if OrderReferenceID %}
<cac:OrderReference>
<cbc:ID>{{OrderReferenceID}}</cbc:ID>
<!--TODO 2.0: No hay un campo validado aun, para la fecha de la orden del cliente>
<cbc:IssueDate>{{OrderReferenceIssueDate}}</cbc:IssueDate-->
</cac:OrderReference>
{% endif %}
<!--TODO 2.0: Se debe diligenciar únicamente cuando la FE se origina a partir de la corrección o
ajuste que se da mediante un Nota Crédito, ES POSIBLE PERO NO HAY PROBLEMA SI NO ESTA,
odoo esto es poco probable y no esta la relacion directa, desarrollo para cumplir con esto>
<cac:BillingReference>
<cac:CreditNoteDocumentReference>
<cbc:ID>SFR3123856</cbc:ID>
<cbc:UUID schemeName="CUFE-SHA1">a675432fecc1d537361dcdbdfbd08d6e5283f2bc</cbc:UUID>
<cbc:IssueDate>2018-09-29</cbc:IssueDate>
</cac:InvoiceDocumentReference>
</cac:BillingReference-->
<!--TODO 2.0: Se debe diligenciar únicamente cuando la FE se origina a partir de la corrección o
ajuste que se da mediante un Nota Débito, CREO QUE ESTO ES IMPOSIBLE DESDE EL ODOO, Y TAMPOCO TIENE SENTIDO
GENERAR UNA NUEVA FACTURA, SABIENDO QUE LA NOTA DEBITO PRECISAMENTE ES LA CORRECCION PARA NO GENERAR UNA NOTA
CREDITO Y UNA FACTURA DE NUEVO EN EL CASO DE QUE LA FACTURA ORIGINAL ESTE ERRADA POR FALTA DE UN ITEM O VALOR>
<cac:BillingReference>
<cac:DebitNoteDocumentReference>
<cbc:ID>SETP990000101</cbc:ID>
<cbc:UUID schemeName="CUFE-SHA384">1dc661228f152332d876e1f1cd2042ecdea1804ed0da78f84dc9ee0938d69f17037dc53f97778ed2721d65c1fc3c73ac</cbc:UUID>
<cbc:IssueDate>2018-09-29</cbc:IssueDate>
</cac:InvoiceDocumentReference>
</cac:BillingReference-->
<!--TODO 3.0: opcional, solo interés mercantil, para referenciar uno o más documentos de despacho asociado>
<cac:DespatchDocumentReference>
<cbc:ID>8124167214 DA</cbc:ID>
<cbc:IssueDate>2019-12-12</cbc:IssueDate>
</cac:DespatchDocumentReference-->
<!--TODO 3.0: opcional, solo interés mercantil, para referenciar uno o más documentos de recepción asociado-->
{% if ReceiptDocumentReferenceID %}
<cac:ReceiptDocumentReference>
<cbc:ID>{{ReceiptDocumentReferenceID}}</cbc:ID>
<!--TODO 2.0: No hay un campo validado aun, para la fecha de la orden del cliente>
<cbc:IssueDate>{{ReceiptDocumentReferenceDate}}</cbc:IssueDate-->
</cac:ReceiptDocumentReference>
{% endif %}
{% if InvoiceTypeCode == '03' %}
<cac:AdditionalDocumentReference>
<cbc:ID>{{ID}}</cbc:ID>
<cbc:IssueDate>{{IssueDate}}</cbc:IssueDate>
<!--TODO 2.0: Opcional, Corresponde a una codificación propia de la empresa.
<cbc:DocumentTypeCode></cbc:DocumentTypeCode-->
</cac:AdditionalDocumentReference>
{% endif %}
<cac:AccountingSupplierParty>
<cbc:AdditionalAccountID schemeID="01">{{AccountingSupplierParty.AdditionalAccountID}}</cbc:AdditionalAccountID>
<cac:Party>
<cac:PhysicalLocation>
<cac:Address>
<cbc:ID>{{AccountingSupplierParty.AddressID}}</cbc:ID>
<cbc:CityName>{{AccountingSupplierParty.AddressCityName}}</cbc:CityName>
{% if AccountingSupplierParty.AddressPostalZone %}
<cbc:PostalZone>{{AccountingSupplierParty.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{AccountingSupplierParty.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{AccountingSupplierParty.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{AccountingSupplierParty.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{AccountingSupplierParty.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{AccountingSupplierParty.CountryName}}</cbc:Name>
</cac:Country>
</cac:Address>
</cac:PhysicalLocation>
<cac:PartyTaxScheme>
<cbc:RegistrationName>{{AccountingSupplierParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="{{AccountingSupplierParty.CompanyIDschemeID}}"
schemeName="{{AccountingSupplierParty.CompanyIDschemeName}}">{{AccountingSupplierParty.CompanyID}}</cbc:CompanyID>
<!--TODO 2.0: listName el anexo dice que eliminar o valor No aplica, pero para consumidor final dice informar 49-->
<cbc:TaxLevelCode listName="{{AccountingSupplierParty.listName}}">{{AccountingSupplierParty.TaxLevelCode}}</cbc:TaxLevelCode>
<cac:TaxScheme>
<cbc:ID>{{AccountingSupplierParty.TaxSchemeID}}</cbc:ID>
<cbc:Name>{{AccountingSupplierParty.TaxSchemeName}}</cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<!--TODO 2.0: opciona>
<cac:PartyLegalEntity>
<cbc:RegistrationName>{{AccountingSupplierParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195" schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)" schemeID="{{AccountingSupplierParty.CompanyIDschemeID}}" schemeName="{{AccountingSupplierParty.CompanyIDschemeName}}">{{AccountingSupplierParty.CompanyID}}</cbc:CompanyID>
<TODO 3.0: Si se va a opera bajo modalidad de Consorcio, entonces este grupo de información
debe ser informada. Se debe completar un grupo de elementos por cada participante del consorcio.>
<cac:ShareholderParty>
<cbc:PartecipationPercent>10.00</cbc:PartecipationPercent>
<cac:Party>
<cac:PartyTaxScheme>
<cbc:RegistrationName></cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195" schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)" schemeID="" schemeName=""></cbc:CompanyID>
<TODO 2.0: listName el anexo dice que eliminar o valor No aplica,
pero para consumidor final dice informar 49>
<cbc:TaxLevelCode listName=""></cbc:TaxLevelCode>
<cac:TaxScheme>
<cbc:ID></cbc:ID>
<cbc:Name></cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
</cac:Party>
</cac:ShareholderParty>
</cac:PartyLegalEntity-->
{% if AccountingSupplierParty.Telephone or AccountingSupplierParty.Telefax or AccountingSupplierParty.ElectronicMail %}
<cac:Contact>
<!--TODO 3.0: opcional, de momento se manda info de la empresa sin nombre de contacto>
<cbc:Name>Diana Cruz</cbc:Name-->
{% if AccountingSupplierParty.Telephone %}
<cbc:Telephone>{{AccountingSupplierParty.Telephone}}</cbc:Telephone>
{% endif %}
{% if AccountingSupplierParty.Telefax %}
<cbc:Telefax>{{AccountingSupplierParty.Telefax}}</cbc:Telefax>
{% endif %}
{% if AccountingSupplierParty.ElectronicMail %}
<cbc:ElectronicMail>{{AccountingSupplierParty.ElectronicMail}}</cbc:ElectronicMail>
{% endif %}
<!--TODO 2.0: opcional, de momento se manda info de la empresa no se sabe que mandar aca de momento>
<cbc:Note></cbc:Note-->
</cac:Contact>
{% endif %}
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cbc:AdditionalAccountID>{{AccountingCustomerParty.AdditionalAccountID}}</cbc:AdditionalAccountID>
<cac:Party>
{% if IndustryClassificationCode %}
<cbc:IndustryClassificationCode>{{IndustryClassificationCode}}</cbc:IndustryClassificationCode>
{% endif %}
{% if AccountingCustomerParty.CompanyID != '222222222222' %}
<cac:PhysicalLocation>
<cac:Address>
<cbc:ID>{{AccountingCustomerParty.AddressID}}</cbc:ID>
<cbc:CityName>{{AccountingCustomerParty.AddressCityName}}</cbc:CityName>
{% if AccountingCustomerParty.AddressPostalZone %}
<cbc:PostalZone>{{AccountingCustomerParty.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{AccountingCustomerParty.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{AccountingCustomerParty.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{AccountingCustomerParty.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{AccountingCustomerParty.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{AccountingCustomerParty.CountryName}}</cbc:Name>
</cac:Country>
</cac:Address>
</cac:PhysicalLocation>
{% endif %}
<cac:PartyTaxScheme>
<cbc:RegistrationName>{{AccountingCustomerParty.Name}}</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
{% if AccountingCustomerParty.CompanyIDschemeName == '31' %}schemeID="{{AccountingCustomerParty.CompanyIDschemeID}}"{% endif %}
schemeName="{{AccountingCustomerParty.CompanyIDschemeName}}">{{AccountingCustomerParty.CompanyID}}</cbc:CompanyID>
<!--TODO 2.0: listName el anexo dice que eliminar o valor No aplica, pero para consumidor final dice informar 49-->
<cbc:TaxLevelCode listName="{{AccountingCustomerParty.listName}}">{{AccountingCustomerParty.TaxLevelCode}}</cbc:TaxLevelCode>
{% if AccountingCustomerParty.CompanyID != '222222222222' %}
{% endif %}
<cac:TaxScheme>
<cbc:ID>{{AccountingCustomerParty.TaxSchemeID}}</cbc:ID>
<cbc:Name>{{AccountingCustomerParty.TaxSchemeName}}</cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
{% if AccountingCustomerParty.CompanyID != '222222222222' and (AccountingCustomerParty.Telephone or AccountingCustomerParty.Telefax or AccountingCustomerParty.ElectronicMail) %}
<cac:Contact>
<!--TODO 2.0: opcional, de momento se manda info de la empresa sin nombre de contacto>
<cbc:Name>Diana Cruz</cbc:Name-->
{% if AccountingCustomerParty.Telephone %}
<cbc:Telephone>{{AccountingCustomerParty.Telephone}}</cbc:Telephone>
{% endif %}
{% if AccountingCustomerParty.Telefax %}
<cbc:Telefax>{{AccountingCustomerParty.Telefax}}</cbc:Telefax>
{% endif %}
{% if AccountingCustomerParty.ElectronicMail %}
<cbc:ElectronicMail>{{AccountingCustomerParty.ElectronicMail}}</cbc:ElectronicMail>
{% endif %}
<!--TODO 2.0: opcional, de momento se manda info de la empresa no se sabe que mandar aca de momento>
<cbc:Note></cbc:Note-->
</cac:Contact>
{% endif %}
</cac:Party>
</cac:AccountingCustomerParty>
<cac:Delivery>
<cbc:ActualDeliveryDate>{{ActualDeliveryDate}}</cbc:ActualDeliveryDate>
<cbc:ActualDeliveryTime>{{ActualDeliveryTime}}</cbc:ActualDeliveryTime>
{% if AccountingCustomerParty.CompanyID != '222222222222' %}
<cac:DeliveryAddress>
<cbc:ID>{{Delivery.AddressID}}</cbc:ID>
<cbc:CityName>{{Delivery.AddressCityName}}</cbc:CityName>
{% if Delivery.AddressPostalZone %}
<cbc:PostalZone>{{Delivery.AddressPostalZone}}</cbc:PostalZone>
{% endif %}
<cbc:CountrySubentity>{{Delivery.AddressCountrySubentity}}</cbc:CountrySubentity>
<cbc:CountrySubentityCode>{{Delivery.AddressCountrySubentityCode}}</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>{{Delivery.AddressLine}}</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>{{Delivery.CountryIdentificationCode}}</cbc:IdentificationCode>
<!--TODO 2.0: languageID podria variar, siguiente version revisar esto-->
<cbc:Name languageID="es">{{Delivery.CountryName}}</cbc:Name>
</cac:Country>
</cac:DeliveryAddress>
{% endif %}
<!--TODO 3.0, Grupo de datos con información sobre la empresa de transporte, desarrollo futuro>
<cac:DeliveryParty>
<cac:PartyName>
<cbc:Name></cbc:Name>
</cac:PartyName>
<cac:PhysicalLocation>
<cac:Address>
<cbc:ID>11001</cbc:ID>
<cbc:CityName>Bogotá, D.c. </cbc:CityName>
<cbc:PostalZone>Bogotá, D.c. </cbc:PostalZone>
<cbc:CountrySubentity>Bogotá</cbc:CountrySubentity>
<cbc:CountrySubentityCode>11</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>Av. #17 - 193</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>CO</cbc:IdentificationCode>
<TODO 2.0: languageID podria variar, siguiente version revisar esto>
<cbc:Name languageID="es">Colombia</cbc:Name>
</cac:Country>
</cac:Address>
</cac:PhysicalLocation>
<cac:PartyTaxScheme>
<cbc:RegistrationName>Empresa de transporte</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="1"
schemeName="31">981223983</cbc:CompanyID>
<TODO 2.0: listName el anexo dice que eliminar o valor No aplica,
pero para consumidor final dice informar 49>
<cbc:TaxLevelCode listName="">O-99</cbc:TaxLevelCode>
<cac:RegistrationAddress>
<cbc:ID>11001</cbc:ID>
<cbc:CityName>Bogotá, D.c. </cbc:CityName>
<cbc:PostalZone>Bogotá, D.c. </cbc:PostalZone>
<cbc:CountrySubentity>Bogotá</cbc:CountrySubentity>
<cbc:CountrySubentityCode>11</cbc:CountrySubentityCode>
<cac:AddressLine>
<cbc:Line>Av. #17 - 193</cbc:Line>
</cac:AddressLine>
<cac:Country>
<cbc:IdentificationCode>CO</cbc:IdentificationCode>
<TODO 2.0: languageID podria variar, siguiente version revisar esto>
<cbc:Name languageID="es">Colombia</cbc:Name>
</cac:Country>
</cac:RegistrationAddress>
<cac:TaxScheme>
<cbc:ID>01</cbc:ID>
<cbc:Name>IVA</cbc:Name>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Empresa de transporte</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="195"
schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)"
schemeID="1"
schemeName="31">981223983</cbc:CompanyID>
<cac:CorporateRegistrationScheme>
<cbc:Name>75433</cbc:Name>
</cac:CorporateRegistrationScheme>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>Eric Van Boxsom</cbc:Name>
<cbc:Telephone>9712311</cbc:Telephone>
<cbc:Telefax>12431241</cbc:Telefax>
<cbc:ElectronicMail>eric.vanboxsom@gosocket.net</cbc:ElectronicMail>
<cbc:Note>Test descripcion contacto</cbc:Note>
</cac:Contact>
</cac:DeliveryParty-->
</cac:Delivery>
{% if DeliveryTerms.LossRiskResponsibilityCode %}
<cac:DeliveryTerms>
<cbc:ID>1</cbc:ID>
<!--TODO 2.0: Con otro modulo complementario se puede resolver el texto libre-->
<!--cbc:SpecialTerms>Portes Pagados</cbc:SpecialTerms-->
<cbc:LossRiskResponsibilityCode>{{DeliveryTerms.LossRiskResponsibilityCode}}</cbc:LossRiskResponsibilityCode>
<cbc:LossRisk>{{DeliveryTerms.LossRisk}}</cbc:LossRisk>
</cac:DeliveryTerms>
{% endif %}
<cac:PaymentMeans>
<cbc:ID>{{PaymentMeansID}}</cbc:ID>
<cbc:PaymentMeansCode>{{PaymentMeansCode}}</cbc:PaymentMeansCode>
<cbc:PaymentDueDate>{{PaymentDueDate}}</cbc:PaymentDueDate>
<!--TODO 2.0: Identificador del pago, pueden ir de cero a varios PaymentID-->
<!--cbc:PaymentID></cbc:PaymentID-->
<!--TODO 2.0: En el anexo esta pero solo en facturas y no hay informacion sobre el campo-->
<!--cbc:PaymentTerms></cbc:PaymentTerms-->
</cac:PaymentMeans>
<!--TODO 3.0: Grupo de campos para informaciónrelacionadas con un anticipo -->
<!--cac:PrepaidPayment>
<cbc:ID>SFR3123856</cbc:ID>
<cbc:PaidAmount currencyID="{{DocumentCurrencyCode}}">1000.00</cbc:PaidAmount>
<cbc:ReceivedDate>2018-09-29</cbc:ReceivedDate>
<cbc:PaidDate>2018-09-29</cbc:PaidDate>
<cbc:PaidTime>2018-09-29</cbc:PaidDate>
<cbc:InstructionID>Prepago recibido</cbc:InstructionID>
</cac:PrepaidPayment-->
<!--TODO 3.0: Grupo de campos para información relacionadas con un cargo o un descuento>
<cac:AllowanceCharge>
<cbc:ID>SFR3123856</cbc:ID>
<cbc:ChargeIndicator></cbc:ChargeIndicator>
<cbc:AllowanceChargeReasonCode></cbc:AllowanceChargeReasonCode>
<cbc:AllowanceChargeReason></cbc:AllowanceChargeReason>
<cbc:MultiplierFactorNumeric>Prepago recibido</cbc:MultiplierFactorNumeric>
<cbc:Amount currencyID="{{DocumentCurrencyCode}}">1000.00</cbc:Amount>
<cbc:BaseAmount currencyID="{{DocumentCurrencyCode}}">1000.00</cbc:BaseAmount>
</cac:AllowanceCharge-->
{% if DocumentCurrencyCode != 'COP' %}
<cac:PaymentExchangeRate>
<cbc:SourceCurrencyCode>{{PaymentExchangeRate.SourceCurrencyCode}}</cbc:SourceCurrencyCode>
<cbc:SourceCurrencyBaseRate>1.00</cbc:SourceCurrencyBaseRate>
<cbc:TargetCurrencyCode>{{PaymentExchangeRate.TargetCurrencyCode}}</cbc:TargetCurrencyCode>
<cbc:TargetCurrencyBaseRate>1.00</cbc:TargetCurrencyBaseRate>
<cbc:CalculationRate>{{'{:.2f}'.format(PaymentExchangeRate.CalculationRate)}}</cbc:CalculationRate>
<cbc:Date>{{PaymentExchangeRate.Date}}</cbc:Date>
</cac:PaymentExchangeRate>
{% endif %}
<!--TODO 3.0, Utilizado como metodo alternativo para infomar conversiones a otras divisas.>
<cac:PaymentAlternativeExchangeRate>
<cbc:SourceCurrencyCode>USD</cbc:SourceCurrencyCode>
<cbc:SourceCurrencyBaseRate>1.00</cbc:SourceCurrencyBaseRate>
<cbc:TargetCurrencyCode>COP</cbc:TargetCurrencyCode>
<cbc:TargetCurrencyBaseRate>1.00</cbc:TargetCurrencyBaseRate>
<cbc:CalculationRate>3100</cbc:CalculationRate>
<cbc:Date>2019-06-21</cbc:Date>
</cac:PaymentAlternativeExchangeRate-->
{% for TaxTotalID, TaxTotal in TaxesTotal.items() %}
<cac:TaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxTotal.total)}}</cbc:TaxAmount>
<!--TODO 2.0: Se manda 0.00 por ahora mientras se evalua la necesidad de calculos-->
<cbc:RoundingAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:RoundingAmount>
{% for Percent, TaxSubtotal in TaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<!--TODO 1.0: Usado en el caso de que el tributo es un valor fijo>
<cbc:BaseUnitMeasure></cbc:BaseUnitMeasure>
<cbc:unitCode></cbc:unitCode>
<cbc:PerUnitAmount currencyID="{{DocumentCurrencyCode}}"></cbc:TaxAmount-->
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{TaxTotalID}}</cbc:ID>
<cbc:Name>{{TaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:TaxTotal>
{% endfor %}
{% for WithholdingTaxTotalID, WithholdingTaxTotal in WithholdingTaxesTotal.items() %}
<cac:WithholdingTaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(WithholdingTaxTotal.total)}}</cbc:TaxAmount>
{% for Percent, TaxSubtotal in WithholdingTaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{WithholdingTaxTotalID}}</cbc:ID>
<cbc:Name>{{WithholdingTaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:WithholdingTaxTotal>
{% endfor %}
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="{{DocumentCurrencyCode}}">{{LineExtensionAmount}}</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="{{DocumentCurrencyCode}}">{{TaxExclusiveAmount}}</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="{{DocumentCurrencyCode}}">{{TaxInclusiveAmount}}</cbc:TaxInclusiveAmount>
<!--TODO 3.0: los prepagos son los anticipos, no hay soporte aun en el odoo para identificar esto
PrepaidAmount == 0 mientras tanto, PrepaidAmount = suma de PrepaidPayment
AllowanceTotalAmount == 0 y ChargeTotalAmount == 0 mientras tanto, suma de AllowanceCharge
TaxInclusiveAmount == PayableAmount mientras tanto-->
<cbc:AllowanceTotalAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:AllowanceTotalAmount>
<cbc:ChargeTotalAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:ChargeTotalAmount>
<cbc:PrepaidAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:PrepaidAmount>
<cbc:PayableAmount currencyID="{{DocumentCurrencyCode}}">{{PayableAmount}}</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
{% for InvoiceLineID, InvoiceLine in InvoiceLines.items() %}
<cac:InvoiceLine>
<cbc:ID>{{InvoiceLineID}}</cbc:ID>
<cbc:InvoicedQuantity unitCode="{{InvoiceLine.unitCode}}">{{InvoiceLine.Quantity}}</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="{{DocumentCurrencyCode}}">{{InvoiceLine.LineExtensionAmount}}</cbc:LineExtensionAmount>
{% if InvoiceLine.LineExtensionAmount == '0.00' %}
<cac:PricingReference>
<cac:AlternativeConditionPrice>
<cbc:PriceAmount currencyID="{{DocumentCurrencyCode}}">{{InvoiceLine.PricingReferencePriceAmount}}</cbc:PriceAmount>
<cbc:PriceTypeCode>01</cbc:PriceTypeCode>
</cac:AlternativeConditionPrice>
</cac:PricingReference>
{% endif %}
<!--TODO 2.0, puede iterar en el campo ID aumenta segun la cantidad de descuentos o cargos por lineas
ChargeIndicator true seria un cargo-->
<cac:AllowanceCharge>
<cbc:ID>1</cbc:ID>
<cbc:ChargeIndicator>false</cbc:ChargeIndicator>
<cbc:AllowanceChargeReason>Descuento</cbc:AllowanceChargeReason>
<cbc:MultiplierFactorNumeric>{{InvoiceLine.MultiplierFactorNumeric}}</cbc:MultiplierFactorNumeric>
<cbc:Amount currencyID="{{DocumentCurrencyCode}}">{{InvoiceLine.AllowanceChargeAmount}}</cbc:Amount>
<cbc:BaseAmount currencyID="{{DocumentCurrencyCode}}">{{InvoiceLine.AllowanceChargeBaseAmount}}</cbc:BaseAmount>
</cac:AllowanceCharge>
<!--TODO 1.0, revisar que impuestos se deben y no informar?-->
{% for TaxTotalID, TaxTotal in InvoiceLine.TaxesTotal.items() %}
<cac:TaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxTotal.total)}}</cbc:TaxAmount>
<!--TODO 2.0: Se manda 0.00 por ahora mientras se evalua la necesidad de calculos-->
<cbc:RoundingAmount currencyID="{{DocumentCurrencyCode}}">0.00</cbc:RoundingAmount>
{% for Percent, TaxSubtotal in TaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<!--TODO 1.0: Usado en el caso de que el tributo es un valor fijo>
<cbc:BaseUnitMeasure></cbc:BaseUnitMeasure>
<cbc:unitCode></cbc:unitCode>
<cbc:PerUnitAmount currencyID="{{DocumentCurrencyCode}}"></cbc:TaxAmount-->
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{TaxTotalID}}</cbc:ID>
<cbc:Name>{{TaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:TaxTotal>
{% endfor %}
{% for WithholdingTaxTotalID, WithholdingTaxTotal in InvoiceLine.WithholdingTaxesTotal.items() %}
<cac:WithholdingTaxTotal>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(WithholdingTaxTotal.total)}}</cbc:TaxAmount>
{% for Percent, TaxSubtotal in WithholdingTaxTotal.taxes.items() %}
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.base)}}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="{{DocumentCurrencyCode}}">{{'{:.2f}'.format(TaxSubtotal.amount)}}</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:Percent>{{Percent}}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>{{WithholdingTaxTotalID}}</cbc:ID>
<cbc:Name>{{WithholdingTaxTotal.name}}</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
{% endfor %}
</cac:WithholdingTaxTotal>
{% endfor %}
<cac:Item>
<cbc:Description>{{InvoiceLine.ItemDescription}}</cbc:Description>
<!--TODO 2.0, Cantidad de unidad de este artículo por empaque>
<cbc:PackSizeNumeric>{{InvoiceLine.PackSizeNumeric}}</cbc:PackSizeNumeric-->
{% if InvoiceLine.BrandName %}
<cbc:BrandName>{{InvoiceLine.BrandName}}</cbc:BrandName>
{% endif %}
<!--TODO 2.0, Mandaremos la referencia del fabricante por ahora, hay que definir luego si esto cambia-->
{% if InvoiceLine.ModelName %}
<cbc:ModelName>{{InvoiceLine.ModelName}}</cbc:ModelName>
{% endif %}
<!--TODO 2.0, Grupo de datos de identificación del artículo o servicio de acuerdo con el vendedor>
<cac:SellersItemIdentification>
<cbc:ID>AOHV84-225</cbc:ID>
<cbc:ExtendedID>AOHV84-225</cbc:ExtendedID>
</cac:SellersItemIdentification-->
<!--TODO 2.0, Grupo de datos de identificación del artículo o servicio de acuerdo con un estándar-->
<!--OPCIONAL schemeName="Estándar de adopción del contribuyente" schemeAgencyID="" schemeAgencyName=""-->
<cac:StandardItemIdentification>
<cbc:ID schemeID="999">{{InvoiceLine.StandardItemIdentification}}</cbc:ID>
</cac:StandardItemIdentification>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="{{DocumentCurrencyCode}}">{{InvoiceLine.PriceAmount}}</cbc:PriceAmount>
<cbc:BaseQuantity unitCode="{{InvoiceLine.unitCode}}">{{InvoiceLine.Quantity}}</cbc:BaseQuantity>
</cac:Price>
</cac:InvoiceLine>
{% endfor %}
</Invoice>

Some files were not shown because too many files have changed in this diff Show More