248 lines
10 KiB
Python
248 lines
10 KiB
Python
from odoo import api, fields, models
|
||
from odoo.exceptions import ValidationError
|
||
|
||
|
||
class ResConfigSettings(models.TransientModel):
|
||
_inherit = 'res.config.settings'
|
||
|
||
l10n_co_dian_operation_mode_ids = fields.One2many(
|
||
string="DIAN Operation Modes",
|
||
related="company_id.l10n_co_dian_operation_mode_ids",
|
||
readonly=False,
|
||
help="Software configurations of DIAN",
|
||
)
|
||
l10n_co_dian_certificate_ids = fields.One2many(
|
||
string="Software Certificates",
|
||
related='company_id.l10n_co_dian_certificate_ids',
|
||
readonly=False,
|
||
help="Certificates to be used for electronic invoicing.",
|
||
)
|
||
l10n_co_dian_test_environment = fields.Boolean(
|
||
string="Test environment",
|
||
related='company_id.l10n_co_dian_test_environment',
|
||
readonly=False,
|
||
help="Activate this checkbox if you’re testing workflows for electronic invoicing.",
|
||
)
|
||
l10n_co_dian_certification_process = fields.Boolean(
|
||
string="Activate the certification process",
|
||
related='company_id.l10n_co_dian_certification_process',
|
||
readonly=False,
|
||
help="Activate this checkbox if you are in the certification process with the DIAN.",
|
||
)
|
||
l10n_co_dian_provider = fields.Selection(
|
||
string="Electronic Invoicing Provider",
|
||
related='company_id.l10n_co_dian_provider',
|
||
readonly=False,
|
||
required=True,
|
||
)
|
||
l10n_co_dian_demo_mode = fields.Boolean(
|
||
string="DIAN Demo Mode",
|
||
related='company_id.l10n_co_dian_demo_mode',
|
||
readonly=False,
|
||
help="Activate this checkbox if you’re testing elecronic invoice flows with internal validation.",
|
||
)
|
||
l10n_co_dian_cert_invoice_count = fields.Integer(
|
||
string="Number of Invoices to Certify",
|
||
default=5,
|
||
)
|
||
l10n_co_dian_cert_credit_count = fields.Integer(
|
||
string="Number of Credit Notes to Certify",
|
||
default=5,
|
||
)
|
||
l10n_co_dian_cert_debit_count = fields.Integer(
|
||
string="Number of Debit Notes to Certify",
|
||
default=5,
|
||
)
|
||
|
||
@api.constrains('l10n_co_dian_cert_invoice_count', 'l10n_co_dian_cert_credit_count', 'l10n_co_dian_cert_debit_count')
|
||
def _check_l10n_co_dian_cert_fields(self):
|
||
for setting in self:
|
||
invoice_count = setting.l10n_co_dian_cert_invoice_count
|
||
debit_count = setting.l10n_co_dian_cert_debit_count
|
||
credit_count = setting.l10n_co_dian_cert_credit_count
|
||
if not all(0 <= count < 10 for count in [invoice_count, debit_count, credit_count]):
|
||
raise ValidationError(self.env._("The number of invoices, credit notes, or debit notes to certify must be between 0 and 10."))
|
||
|
||
# ----------------------------------------------------------------
|
||
# Certification Methods
|
||
# Users are expected to go through a certification process for the
|
||
# DIAN to allow their integration. For users just coming to Odoo
|
||
# this can be a confusing process. As such these methods automate
|
||
# the process and are expected to be used in a staging or testing
|
||
# database.
|
||
# ----------------------------------------------------------------
|
||
|
||
def action_l10n_co_certify_with_dian(self):
|
||
""" Automates the required certification flow that's needed to use this
|
||
module in production. It's hard and confusing for new users. Only
|
||
expected to be called in a testing or staging database as it makes
|
||
irreversible changes. """
|
||
if not self.l10n_co_dian_test_environment or not self.l10n_co_dian_certification_process:
|
||
raise ValidationError(self.env._("This action must only be called while in certification mode."))
|
||
if not self.l10n_co_dian_certificate_ids:
|
||
raise ValidationError(self.env._("Please set a certificate to begin the certification process."))
|
||
|
||
journal_ids = self._l10n_co_dian_certify_create_or_update_journals()
|
||
product_id = self._l10n_co_dian_certify_create_or_update_product()
|
||
invoice_journal = journal_ids.filtered(lambda j: j.code == 'SETP')
|
||
credit_journal = journal_ids.filtered(lambda j: j.code == 'NC')
|
||
debit_journal = journal_ids.filtered(lambda j: j.code == 'ND')
|
||
|
||
all_moves = self._l10n_co_dian_certify_create_moves(
|
||
invoice_journal,
|
||
product_id,
|
||
self.l10n_co_dian_cert_invoice_count,
|
||
move_type='out_invoice',
|
||
l10n_co_edi_type='01',
|
||
l10n_co_edi_operation_type='10',
|
||
)
|
||
|
||
all_moves |= self._l10n_co_dian_certify_create_moves(
|
||
credit_journal,
|
||
product_id,
|
||
self.l10n_co_dian_cert_credit_count,
|
||
move_type='out_refund',
|
||
l10n_co_edi_type='91',
|
||
l10n_co_edi_operation_type='22',
|
||
l10n_co_edi_description_code_credit='1',
|
||
)
|
||
|
||
all_moves |= self._l10n_co_dian_certify_create_moves(
|
||
debit_journal,
|
||
product_id,
|
||
self.l10n_co_dian_cert_debit_count,
|
||
move_type='out_invoice',
|
||
l10n_co_edi_type='92',
|
||
l10n_co_edi_operation_type='32',
|
||
l10n_co_edi_description_code_debit='1',
|
||
)
|
||
|
||
for move in all_moves:
|
||
move.l10n_co_dian_action_send_bill_support_document()
|
||
|
||
for move in all_moves:
|
||
move.l10n_co_dian_document_ids.action_get_status()
|
||
|
||
self.env.user._bus_send('account_notification', {
|
||
'type': 'info',
|
||
'title': self.env._('Certification Process Has Concluded'),
|
||
'sticky': True,
|
||
'message': self.env._('Please click below to check the status of your documents.'),
|
||
'action_button': {
|
||
'name': self.env._('Open'),
|
||
'action_name': self.env._('All Documents'),
|
||
'model': 'account.move',
|
||
'res_ids': all_moves.ids,
|
||
},
|
||
})
|
||
|
||
def _l10n_co_dian_certify_create_or_update_journals(self):
|
||
default_account = self.env['account.chart.template'].ref('co_puc_417500')
|
||
all_journals = self.env['account.journal']
|
||
|
||
journal_info = [
|
||
{
|
||
'code': 'SETP',
|
||
'name': 'DIAN Facturas de Cliente',
|
||
'is_debit_note': False,
|
||
'min_range_number': 990000000,
|
||
'max_range_number': 995000000,
|
||
},
|
||
{
|
||
'code': 'NC',
|
||
'name': 'DIAN Notas Credito',
|
||
'is_debit_note': False,
|
||
'min_range_number': 1,
|
||
'max_range_number': 1000,
|
||
},
|
||
{
|
||
'code': 'ND',
|
||
'name': 'DIAN Notas Debito',
|
||
'is_debit_note': True,
|
||
'min_range_number': 1,
|
||
'max_range_number': 1000,
|
||
},
|
||
]
|
||
for journal_data in journal_info:
|
||
journal = self.env['account.journal'].search([('code', '=', journal_data['code'])])
|
||
if not journal:
|
||
journal = self.env['account.journal'].create({
|
||
'name': journal_data['name'],
|
||
'type': 'sale',
|
||
'code': journal_data['code'],
|
||
'company_id': self.company_id.id,
|
||
'l10n_co_edi_debit_note': journal_data['is_debit_note'],
|
||
})
|
||
journal.write({
|
||
'l10n_co_edi_min_range_number': journal_data['min_range_number'],
|
||
'l10n_co_edi_max_range_number': journal_data['max_range_number'],
|
||
})
|
||
all_journals |= journal
|
||
|
||
# The DIAN requires that all customers performing the certification process always
|
||
# use this Technical Key and authorization number. As such it can be hardcoded in
|
||
# the action.
|
||
all_journals.write({
|
||
'l10n_co_dian_technical_key': 'fc8eac422eba16e22ffd8c6f94b3f40a6e38162c',
|
||
'l10n_co_edi_dian_authorization_date': '2019-01-19',
|
||
'l10n_co_edi_dian_authorization_end_date': '2030-01-19',
|
||
'l10n_co_edi_dian_authorization_number': '18760000001',
|
||
'default_account_id': default_account.id,
|
||
})
|
||
|
||
return all_journals
|
||
|
||
def _l10n_co_dian_certify_create_or_update_product(self):
|
||
default_sales_tax = self.env['account.chart.template'].ref('l10n_co_tax_8')
|
||
default_purchase_tax = self.env['account.chart.template'].ref('l10n_co_tax_1')
|
||
product_id = self.env['product.product'].search([('default_code', '=', 'PC1')])
|
||
if not product_id:
|
||
product_id = self.env['product.product'].create({
|
||
'name': 'Producto Certificación',
|
||
'default_code': 'PC1',
|
||
'type': 'consu',
|
||
'categ_id': self.env.ref('product.product_category_goods').id,
|
||
'company_id': self.company_id.id,
|
||
})
|
||
product_id.write({
|
||
'list_price': 1000000.0,
|
||
'taxes_id': [(6, 0, default_sales_tax.ids)],
|
||
'supplier_taxes_id': [(6, 0, default_purchase_tax.ids)],
|
||
'sale_ok': True,
|
||
'purchase_ok': True,
|
||
})
|
||
|
||
return product_id
|
||
|
||
def _l10n_co_dian_certify_create_moves(self, journal_id, product_id, count, **move_values):
|
||
all_moves = self.env['account.move']
|
||
if count == 0:
|
||
return all_moves
|
||
|
||
move = self.env['account.move'].create({
|
||
'partner_id': self.env.ref('l10n_co_edi.consumidor_final_customer').id,
|
||
'journal_id': journal_id.id,
|
||
'invoice_date': fields.Date.context_today(self),
|
||
'invoice_date_due': fields.Date.context_today(self),
|
||
'invoice_line_ids': [
|
||
(0, 0, {
|
||
'product_id': product_id.id,
|
||
'quantity': 1.0,
|
||
}),
|
||
],
|
||
**move_values,
|
||
})
|
||
|
||
# Start the sequence with the min number if there isn't already one.
|
||
if not move._get_last_sequence():
|
||
move.name = f"{journal_id.code}{journal_id.l10n_co_edi_min_range_number}"
|
||
|
||
move.action_post()
|
||
|
||
all_moves |= move
|
||
for _ in range(count - 1):
|
||
new_move = move.copy()
|
||
new_move.action_post()
|
||
all_moves |= new_move
|
||
return all_moves
|