summaryrefslogtreecommitdiff
path: root/addons/l10n_latam_invoice_document/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/l10n_latam_invoice_document/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/l10n_latam_invoice_document/models')
-rw-r--r--addons/l10n_latam_invoice_document/models/__init__.py9
-rw-r--r--addons/l10n_latam_invoice_document/models/account_chart_template.py19
-rw-r--r--addons/l10n_latam_invoice_document/models/account_journal.py38
-rw-r--r--addons/l10n_latam_invoice_document/models/account_move.py288
-rw-r--r--addons/l10n_latam_invoice_document/models/account_move_line.py55
-rw-r--r--addons/l10n_latam_invoice_document/models/ir_sequence.py9
-rw-r--r--addons/l10n_latam_invoice_document/models/l10n_latam_document_type.py60
-rw-r--r--addons/l10n_latam_invoice_document/models/res_company.py12
8 files changed, 490 insertions, 0 deletions
diff --git a/addons/l10n_latam_invoice_document/models/__init__.py b/addons/l10n_latam_invoice_document/models/__init__.py
new file mode 100644
index 00000000..cb7b480f
--- /dev/null
+++ b/addons/l10n_latam_invoice_document/models/__init__.py
@@ -0,0 +1,9 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import res_company
+from . import l10n_latam_document_type
+from . import account_journal
+from . import account_move
+from . import account_move_line
+from . import account_chart_template
+from . import ir_sequence
diff --git a/addons/l10n_latam_invoice_document/models/account_chart_template.py b/addons/l10n_latam_invoice_document/models/account_chart_template.py
new file mode 100644
index 00000000..a181655d
--- /dev/null
+++ b/addons/l10n_latam_invoice_document/models/account_chart_template.py
@@ -0,0 +1,19 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo import models, api, fields, _
+
+
+class AccountChartTemplate(models.Model):
+
+ _inherit = 'account.chart.template'
+
+ @api.model
+ def _prepare_all_journals(self, acc_template_ref, company, journals_dict=None):
+ """ We add use_documents or not depending on the context"""
+ journal_data = super()._prepare_all_journals(acc_template_ref, company, journals_dict)
+
+ # if chart has localization, then we use documents by default
+ if company._localization_use_documents():
+ for vals_journal in journal_data:
+ if vals_journal['type'] in ['sale', 'purchase']:
+ vals_journal['l10n_latam_use_documents'] = True
+ return journal_data
diff --git a/addons/l10n_latam_invoice_document/models/account_journal.py b/addons/l10n_latam_invoice_document/models/account_journal.py
new file mode 100644
index 00000000..9ed4e43b
--- /dev/null
+++ b/addons/l10n_latam_invoice_document/models/account_journal.py
@@ -0,0 +1,38 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models, api, _
+from odoo.exceptions import ValidationError
+
+class AccountJournal(models.Model):
+
+ _inherit = "account.journal"
+
+ l10n_latam_use_documents = fields.Boolean(
+ 'Use Documents?', help="If active: will be using for legal invoicing (invoices, debit/credit notes)."
+ " If not set means that will be used to register accounting entries not related to invoicing legal documents."
+ " For Example: Receipts, Tax Payments, Register journal entries")
+ l10n_latam_company_use_documents = fields.Boolean(compute='_compute_l10n_latam_company_use_documents')
+
+ @api.depends('company_id')
+ def _compute_l10n_latam_company_use_documents(self):
+ for rec in self:
+ rec.l10n_latam_company_use_documents = rec.company_id._localization_use_documents()
+
+ @api.onchange('company_id', 'type')
+ def _onchange_company(self):
+ self.l10n_latam_use_documents = self.type in ['sale', 'purchase'] and \
+ self.l10n_latam_company_use_documents
+
+ @api.constrains('l10n_latam_use_documents')
+ def check_use_document(self):
+ for rec in self:
+ if rec.env['account.move'].search([('journal_id', '=', rec.id), ('posted_before', '=', True)], limit=1):
+ raise ValidationError(_(
+ 'You can not modify the field "Use Documents?" if there are validated invoices in this journal!'))
+
+ @api.onchange('type', 'l10n_latam_use_documents')
+ def _onchange_type(self):
+ res = super()._onchange_type()
+ if self.l10n_latam_use_documents:
+ self.refund_sequence = False
+ return res
diff --git a/addons/l10n_latam_invoice_document/models/account_move.py b/addons/l10n_latam_invoice_document/models/account_move.py
new file mode 100644
index 00000000..fb4e7528
--- /dev/null
+++ b/addons/l10n_latam_invoice_document/models/account_move.py
@@ -0,0 +1,288 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models, fields, api, _
+from odoo.exceptions import UserError, ValidationError
+import re
+from odoo.tools.misc import formatLang
+from odoo.tools.sql import column_exists, create_column
+
+
+class AccountMove(models.Model):
+
+ _inherit = "account.move"
+
+ def _auto_init(self):
+ # Skip the computation of the field `l10n_latam_document_type_id` at the module installation
+ # Without this, at the module installation,
+ # it would call `_compute_l10n_latam_document_type` on all existing records
+ # which can take quite a while if you already have a lot of moves. It can even fail with a MemoryError.
+ # In addition, it sets `_compute_l10n_latam_document_type = False` on all records
+ # because this field depends on the many2many `l10n_latam_available_document_type_ids`,
+ # which relies on having records for the model `l10n_latam.document.type`
+ # which only happens once the according localization module is loaded.
+ # The localization module is loaded afterwards, because the localization module depends on this module,
+ # (e.g. `l10n_cl` depends on `l10n_latam_invoice_document`, and therefore `l10n_cl` is loaded after)
+ # and therefore there are no records for the model `l10n_latam.document.type` at the time this fields
+ # gets computed on installation. Hence, all records' `_compute_l10n_latam_document_type` are set to `False`.
+ # In addition, multiple localization module depends on this module (e.g. `l10n_cl`, `l10n_ar`)
+ # So, imagine `l10n_cl` gets installed first, and then `l10n_ar` is installed next,
+ # if `l10n_latam_document_type_id` needed to be computed on install,
+ # the install of `l10n_cl` would call the compute method,
+ # because `l10n_latam_invoice_document` would be installed at the same time,
+ # but then `l10n_ar` would miss it, because `l10n_latam_invoice_document` would already be installed.
+ # Besides, this field is computed only for drafts invoices, as stated in the compute method:
+ # `for rec in self.filtered(lambda x: x.state == 'draft'):`
+ # So, if we want this field to be computed on install, it must be done only on draft invoices, and only once
+ # the localization modules are loaded.
+ # It should be done in a dedicated post init hook,
+ # filtering correctly the invoices for which it must be computed.
+ # Though I don't think this is needed.
+ # In practical, it's very rare to already have invoices (draft, in addition)
+ # for a Chilian or Argentian company (`res.company`) before installing `l10n_cl` or `l10n_ar`.
+ if not column_exists(self.env.cr, "account_move", "l10n_latam_document_type_id"):
+ create_column(self.env.cr, "account_move", "l10n_latam_document_type_id", "int4")
+ return super()._auto_init()
+
+ l10n_latam_amount_untaxed = fields.Monetary(compute='_compute_l10n_latam_amount_and_taxes')
+ l10n_latam_tax_ids = fields.One2many(compute="_compute_l10n_latam_amount_and_taxes", comodel_name='account.move.line')
+ l10n_latam_available_document_type_ids = fields.Many2many('l10n_latam.document.type', compute='_compute_l10n_latam_available_document_types')
+ l10n_latam_document_type_id = fields.Many2one(
+ 'l10n_latam.document.type', string='Document Type', readonly=False, auto_join=True, index=True,
+ states={'posted': [('readonly', True)]}, compute='_compute_l10n_latam_document_type', store=True)
+ l10n_latam_document_number = fields.Char(
+ compute='_compute_l10n_latam_document_number', inverse='_inverse_l10n_latam_document_number',
+ string='Document Number', readonly=True, states={'draft': [('readonly', False)]})
+ l10n_latam_use_documents = fields.Boolean(related='journal_id.l10n_latam_use_documents')
+ l10n_latam_manual_document_number = fields.Boolean(compute='_compute_l10n_latam_manual_document_number', string='Manual Number')
+
+ @api.depends('l10n_latam_document_type_id')
+ def _compute_name(self):
+ """ Change the way that the use_document moves name is computed:
+
+ * If move use document but does not have document type selected then name = '/' to do not show the name.
+ * If move use document and are numbered manually do not compute name at all (will be set manually)
+ * If move use document and is in draft state and has not been posted before we restart name to '/' (this is
+ when we change the document type) """
+ without_doc_type = self.filtered(lambda x: x.journal_id.l10n_latam_use_documents and not x.l10n_latam_document_type_id)
+ manual_documents = self.filtered(lambda x: x.journal_id.l10n_latam_use_documents and x.l10n_latam_manual_document_number)
+ (without_doc_type + manual_documents.filtered(lambda x: not x.name or x.name and x.state == 'draft' and not x.posted_before)).name = '/'
+ # if we change document or journal and we are in draft and not posted, we clean number so that is recomputed in super
+ self.filtered(
+ lambda x: x.journal_id.l10n_latam_use_documents and x.l10n_latam_document_type_id
+ and not x.l10n_latam_manual_document_number and x.state == 'draft' and not x.posted_before).name = '/'
+ super(AccountMove, self - without_doc_type - manual_documents)._compute_name()
+
+ @api.depends('l10n_latam_document_type_id', 'journal_id')
+ def _compute_l10n_latam_manual_document_number(self):
+ """ Indicates if this document type uses a sequence or if the numbering is made manually """
+ recs_with_journal_id = self.filtered(lambda x: x.journal_id and x.journal_id.l10n_latam_use_documents)
+ for rec in recs_with_journal_id:
+ rec.l10n_latam_manual_document_number = self._is_manual_document_number(rec.journal_id)
+ remaining = self - recs_with_journal_id
+ remaining.l10n_latam_manual_document_number = False
+
+ def _is_manual_document_number(self, journal):
+ return True if journal.type == 'purchase' else False
+
+ @api.depends('name')
+ def _compute_l10n_latam_document_number(self):
+ recs_with_name = self.filtered(lambda x: x.name != '/')
+ for rec in recs_with_name:
+ name = rec.name
+ doc_code_prefix = rec.l10n_latam_document_type_id.doc_code_prefix
+ if doc_code_prefix and name:
+ name = name.split(" ", 1)[-1]
+ rec.l10n_latam_document_number = name
+ remaining = self - recs_with_name
+ remaining.l10n_latam_document_number = False
+
+ @api.onchange('l10n_latam_document_type_id', 'l10n_latam_document_number')
+ def _inverse_l10n_latam_document_number(self):
+ for rec in self.filtered(lambda x: x.l10n_latam_document_type_id):
+ if not rec.l10n_latam_document_number:
+ rec.name = '/'
+ else:
+ l10n_latam_document_number = rec.l10n_latam_document_type_id._format_document_number(rec.l10n_latam_document_number)
+ if rec.l10n_latam_document_number != l10n_latam_document_number:
+ rec.l10n_latam_document_number = l10n_latam_document_number
+ rec.name = "%s %s" % (rec.l10n_latam_document_type_id.doc_code_prefix, l10n_latam_document_number)
+
+ @api.depends('journal_id', 'l10n_latam_document_type_id')
+ def _compute_highest_name(self):
+ manual_records = self.filtered('l10n_latam_manual_document_number')
+ manual_records.highest_name = ''
+ super(AccountMove, self - manual_records)._compute_highest_name()
+
+ @api.model
+ def _deduce_sequence_number_reset(self, name):
+ if self.l10n_latam_use_documents:
+ return 'never'
+ return super(AccountMove, self)._deduce_sequence_number_reset(name)
+
+ def _get_starting_sequence(self):
+ if self.journal_id.l10n_latam_use_documents:
+ if self.l10n_latam_document_type_id:
+ return "%s 00000000" % (self.l10n_latam_document_type_id.doc_code_prefix)
+ # There was no pattern found, propose one
+ return ""
+
+ return super(AccountMove, self)._get_starting_sequence()
+
+ def _compute_l10n_latam_amount_and_taxes(self):
+ recs_invoice = self.filtered(lambda x: x.is_invoice())
+ for invoice in recs_invoice:
+ tax_lines = invoice.line_ids.filtered('tax_line_id')
+ currencies = invoice.line_ids.filtered(lambda x: x.currency_id == invoice.currency_id).mapped('currency_id')
+ included_taxes = invoice.l10n_latam_document_type_id and \
+ invoice.l10n_latam_document_type_id._filter_taxes_included(tax_lines.mapped('tax_line_id'))
+ if not included_taxes:
+ l10n_latam_amount_untaxed = invoice.amount_untaxed
+ not_included_invoice_taxes = tax_lines
+ else:
+ included_invoice_taxes = tax_lines.filtered(lambda x: x.tax_line_id in included_taxes)
+ not_included_invoice_taxes = tax_lines - included_invoice_taxes
+ if invoice.is_inbound():
+ sign = -1
+ else:
+ sign = 1
+ amount = 'amount_currency' if len(currencies) == 1 else 'balance'
+ l10n_latam_amount_untaxed = invoice.amount_untaxed + sign * sum(included_invoice_taxes.mapped(amount))
+ invoice.l10n_latam_amount_untaxed = l10n_latam_amount_untaxed
+ invoice.l10n_latam_tax_ids = not_included_invoice_taxes
+ remaining = self - recs_invoice
+ remaining.l10n_latam_amount_untaxed = False
+ remaining.l10n_latam_tax_ids = [(5, 0)]
+
+ def _post(self, soft=True):
+ for rec in self.filtered(lambda x: x.l10n_latam_use_documents and (not x.name or x.name == '/')):
+ if rec.move_type in ('in_receipt', 'out_receipt'):
+ raise UserError(_('We do not accept the usage of document types on receipts yet. '))
+ return super()._post(soft)
+
+ @api.constrains('name', 'journal_id', 'state')
+ def _check_unique_sequence_number(self):
+ """ This uniqueness verification is only valid for customer invoices, and vendor bills that does not use
+ documents. A new constraint method _check_unique_vendor_number has been created just for validate for this purpose """
+ vendor = self.filtered(lambda x: x.is_purchase_document() and x.l10n_latam_use_documents)
+ return super(AccountMove, self - vendor)._check_unique_sequence_number()
+
+ @api.constrains('state', 'l10n_latam_document_type_id')
+ def _check_l10n_latam_documents(self):
+ """ This constraint checks that if a invoice is posted and does not have a document type configured will raise
+ an error. This only applies to invoices related to journals that has the "Use Documents" set as True.
+ And if the document type is set then check if the invoice number has been set, because a posted invoice
+ without a document number is not valid in the case that the related journals has "Use Docuemnts" set as True """
+ validated_invoices = self.filtered(lambda x: x.l10n_latam_use_documents and x.state == 'posted')
+ without_doc_type = validated_invoices.filtered(lambda x: not x.l10n_latam_document_type_id)
+ if without_doc_type:
+ raise ValidationError(_(
+ 'The journal require a document type but not document type has been selected on invoices %s.',
+ without_doc_type.ids
+ ))
+ without_number = validated_invoices.filtered(
+ lambda x: not x.l10n_latam_document_number and x.l10n_latam_manual_document_number)
+ if without_number:
+ raise ValidationError(_(
+ 'Please set the document number on the following invoices %s.',
+ without_number.ids
+ ))
+
+ @api.constrains('move_type', 'l10n_latam_document_type_id')
+ def _check_invoice_type_document_type(self):
+ for rec in self.filtered('l10n_latam_document_type_id.internal_type'):
+ internal_type = rec.l10n_latam_document_type_id.internal_type
+ invoice_type = rec.move_type
+ if internal_type in ['debit_note', 'invoice'] and invoice_type in ['out_refund', 'in_refund']:
+ raise ValidationError(_('You can not use a %s document type with a refund invoice', internal_type))
+ elif internal_type == 'credit_note' and invoice_type in ['out_invoice', 'in_invoice']:
+ raise ValidationError(_('You can not use a %s document type with a invoice', internal_type))
+
+ def _get_l10n_latam_documents_domain(self):
+ self.ensure_one()
+ if self.move_type in ['out_refund', 'in_refund']:
+ internal_types = ['credit_note']
+ else:
+ internal_types = ['invoice', 'debit_note']
+ return [('internal_type', 'in', internal_types), ('country_id', '=', self.company_id.country_id.id)]
+
+ @api.depends('journal_id', 'partner_id', 'company_id', 'move_type')
+ def _compute_l10n_latam_available_document_types(self):
+ self.l10n_latam_available_document_type_ids = False
+ for rec in self.filtered(lambda x: x.journal_id and x.l10n_latam_use_documents and x.partner_id):
+ rec.l10n_latam_available_document_type_ids = self.env['l10n_latam.document.type'].search(rec._get_l10n_latam_documents_domain())
+
+ @api.depends('l10n_latam_available_document_type_ids', 'debit_origin_id')
+ def _compute_l10n_latam_document_type(self):
+ debit_note = self.debit_origin_id
+ for rec in self.filtered(lambda x: x.state == 'draft'):
+ document_types = rec.l10n_latam_available_document_type_ids._origin
+ document_types = debit_note and document_types.filtered(lambda x: x.internal_type == 'debit_note') or document_types
+ rec.l10n_latam_document_type_id = document_types and document_types[0].id
+
+ def _compute_invoice_taxes_by_group(self):
+ report_or_portal_view = 'commit_assetsbundle' in self.env.context or \
+ not self.env.context.get('params', {}).get('view_type') == 'form'
+ if not report_or_portal_view:
+ return super()._compute_invoice_taxes_by_group()
+
+ move_with_doc_type = self.filtered('l10n_latam_document_type_id')
+ for move in move_with_doc_type:
+ lang_env = move.with_context(lang=move.partner_id.lang).env
+ tax_lines = move.l10n_latam_tax_ids
+ tax_balance_multiplicator = -1 if move.is_inbound(True) else 1
+ res = {}
+ # There are as many tax line as there are repartition lines
+ done_taxes = set()
+ for line in tax_lines:
+ res.setdefault(line.tax_line_id.tax_group_id, {'base': 0.0, 'amount': 0.0})
+ res[line.tax_line_id.tax_group_id]['amount'] += tax_balance_multiplicator * (line.amount_currency if line.currency_id else line.balance)
+ tax_key_add_base = tuple(move._get_tax_key_for_group_add_base(line))
+ if tax_key_add_base not in done_taxes:
+ if line.currency_id and line.company_currency_id and line.currency_id != line.company_currency_id:
+ amount = line.company_currency_id._convert(line.tax_base_amount, line.currency_id, line.company_id, line.date or fields.Date.today())
+ else:
+ amount = line.tax_base_amount
+ res[line.tax_line_id.tax_group_id]['base'] += amount
+ # The base should be added ONCE
+ done_taxes.add(tax_key_add_base)
+
+ # At this point we only want to keep the taxes with a zero amount since they do not
+ # generate a tax line.
+ zero_taxes = set()
+ for line in move.line_ids:
+ for tax in line.l10n_latam_tax_ids.flatten_taxes_hierarchy():
+ if tax.tax_group_id not in res or tax.id in zero_taxes:
+ res.setdefault(tax.tax_group_id, {'base': 0.0, 'amount': 0.0})
+ res[tax.tax_group_id]['base'] += tax_balance_multiplicator * (line.amount_currency if line.currency_id else line.balance)
+ zero_taxes.add(tax.id)
+
+ res = sorted(res.items(), key=lambda l: l[0].sequence)
+ move.amount_by_group = [(
+ group.name, amounts['amount'],
+ amounts['base'],
+ formatLang(lang_env, amounts['amount'], currency_obj=move.currency_id),
+ formatLang(lang_env, amounts['base'], currency_obj=move.currency_id),
+ len(res),
+ group.id
+ ) for group, amounts in res]
+ super(AccountMove, self - move_with_doc_type)._compute_invoice_taxes_by_group()
+
+ @api.constrains('name', 'partner_id', 'company_id', 'posted_before')
+ def _check_unique_vendor_number(self):
+ """ The constraint _check_unique_sequence_number is valid for customer bills but not valid for us on vendor
+ bills because the uniqueness must be per partner """
+ for rec in self.filtered(
+ lambda x: x.name and x.name != '/' and x.is_purchase_document() and x.l10n_latam_use_documents
+ and x.commercial_partner_id):
+ domain = [
+ ('move_type', '=', rec.move_type),
+ # by validating name we validate l10n_latam_document_type_id
+ ('name', '=', rec.name),
+ ('company_id', '=', rec.company_id.id),
+ ('id', '!=', rec.id),
+ ('commercial_partner_id', '=', rec.commercial_partner_id.id),
+ # allow to have to equal if they are cancelled
+ ('state', '!=', 'cancel'),
+ ]
+ if rec.search(domain):
+ raise ValidationError(_('Vendor bill number must be unique per vendor and company.'))
diff --git a/addons/l10n_latam_invoice_document/models/account_move_line.py b/addons/l10n_latam_invoice_document/models/account_move_line.py
new file mode 100644
index 00000000..b873dccd
--- /dev/null
+++ b/addons/l10n_latam_invoice_document/models/account_move_line.py
@@ -0,0 +1,55 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models, api, fields
+from odoo.tools.sql import column_exists, create_column
+
+
+class AccountMoveLine(models.Model):
+
+ _inherit = 'account.move.line'
+
+ def _auto_init(self):
+ # Skip the computation of the field `l10n_latam_document_type_id` at the module installation
+ # See `_auto_init` in `l10n_latam_invoice_document/models/account_move.py` for more information
+ if not column_exists(self.env.cr, "account_move_line", "l10n_latam_document_type_id"):
+ create_column(self.env.cr, "account_move_line", "l10n_latam_document_type_id", "int4")
+ return super()._auto_init()
+
+ l10n_latam_document_type_id = fields.Many2one(
+ related='move_id.l10n_latam_document_type_id', auto_join=True, store=True, index=True)
+ l10n_latam_price_unit = fields.Float(compute='compute_l10n_latam_prices_and_taxes', digits='Product Price')
+ l10n_latam_price_subtotal = fields.Monetary(compute='compute_l10n_latam_prices_and_taxes')
+ l10n_latam_price_net = fields.Float(compute='compute_l10n_latam_prices_and_taxes', digits='Product Price')
+ l10n_latam_tax_ids = fields.One2many(compute="compute_l10n_latam_prices_and_taxes", comodel_name='account.tax')
+
+ @api.depends('price_unit', 'price_subtotal', 'move_id.l10n_latam_document_type_id')
+ def compute_l10n_latam_prices_and_taxes(self):
+ for line in self:
+ invoice = line.move_id
+ included_taxes = \
+ invoice.l10n_latam_document_type_id and invoice.l10n_latam_document_type_id._filter_taxes_included(
+ line.tax_ids)
+ # For the unit price, we need the number rounded based on the product price precision.
+ # The method compute_all uses the accuracy of the currency so, we multiply and divide for 10^(decimal accuracy of product price) to get the price correctly rounded.
+ price_digits = 10**self.env['decimal.precision'].precision_get('Product Price')
+ if not included_taxes:
+ price_unit = line.tax_ids.with_context(round=False, force_sign=invoice._get_tax_force_sign()).compute_all(
+ line.price_unit * price_digits, invoice.currency_id, 1.0, line.product_id, invoice.partner_id)
+ l10n_latam_price_unit = price_unit['total_excluded'] / price_digits
+ l10n_latam_price_subtotal = line.price_subtotal
+ not_included_taxes = line.tax_ids
+ l10n_latam_price_net = l10n_latam_price_unit * (1 - (line.discount or 0.0) / 100.0)
+ else:
+ not_included_taxes = line.tax_ids - included_taxes
+ l10n_latam_price_unit = included_taxes.with_context(force_sign=invoice._get_tax_force_sign()).compute_all(
+ line.price_unit * price_digits, invoice.currency_id, 1.0, line.product_id, invoice.partner_id)['total_included'] / price_digits
+ l10n_latam_price_net = l10n_latam_price_unit * (1 - (line.discount or 0.0) / 100.0)
+ price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
+ l10n_latam_price_subtotal = included_taxes.with_context(force_sign=invoice._get_tax_force_sign()).compute_all(
+ price, invoice.currency_id, line.quantity, line.product_id,
+ invoice.partner_id)['total_included']
+
+ line.l10n_latam_price_subtotal = l10n_latam_price_subtotal
+ line.l10n_latam_price_unit = l10n_latam_price_unit
+ line.l10n_latam_price_net = l10n_latam_price_net
+ line.l10n_latam_tax_ids = not_included_taxes
diff --git a/addons/l10n_latam_invoice_document/models/ir_sequence.py b/addons/l10n_latam_invoice_document/models/ir_sequence.py
new file mode 100644
index 00000000..314a8597
--- /dev/null
+++ b/addons/l10n_latam_invoice_document/models/ir_sequence.py
@@ -0,0 +1,9 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo import fields, models
+
+
+class IrSequence(models.Model):
+
+ _inherit = 'ir.sequence'
+
+ l10n_latam_document_type_id = fields.Many2one('l10n_latam.document.type', 'Document Type') #still needed for l10n_cl until next saas
diff --git a/addons/l10n_latam_invoice_document/models/l10n_latam_document_type.py b/addons/l10n_latam_invoice_document/models/l10n_latam_document_type.py
new file mode 100644
index 00000000..3eb6bd04
--- /dev/null
+++ b/addons/l10n_latam_invoice_document/models/l10n_latam_document_type.py
@@ -0,0 +1,60 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo import fields, models, api
+from odoo.osv import expression
+
+
+class L10nLatamDocumentType(models.Model):
+
+ _name = 'l10n_latam.document.type'
+ _description = 'Latam Document Type'
+ _order = 'sequence, id'
+
+ active = fields.Boolean(default=True)
+ sequence = fields.Integer(
+ default=10, required=True, help='To set in which order show the documents type taking into account the most'
+ ' commonly used first')
+ country_id = fields.Many2one(
+ 'res.country', required=True, index=True, help='Country in which this type of document is valid')
+ name = fields.Char(required=True, index=True, help='The document name')
+ doc_code_prefix = fields.Char(
+ 'Document Code Prefix', help="Prefix for Documents Codes on Invoices and Account Moves. For eg. 'FA ' will"
+ " build 'FA 0001-0000001' Document Number")
+ code = fields.Char(help='Code used by different localizations')
+ report_name = fields.Char('Name on Reports', help='Name that will be printed in reports, for example "CREDIT NOTE"')
+ internal_type = fields.Selection(
+ [('invoice', 'Invoices'), ('debit_note', 'Debit Notes'), ('credit_note', 'Credit Notes')], index=True,
+ help='Analog to odoo account.move.move_type but with more options allowing to identify the kind of document we are'
+ ' working with. (not only related to account.move, could be for documents of other models like stock.picking)')
+
+ def _format_document_number(self, document_number):
+ """ Method to be inherited by different localizations. The purpose of this method is to allow:
+ * making validations on the document_number. If it is wrong it should raise an exception
+ * format the document_number against a pattern and return it
+ """
+ self.ensure_one()
+ return document_number
+
+ def name_get(self):
+ result = []
+ for rec in self:
+ name = rec.name
+ if rec.code:
+ name = '(%s) %s' % (rec.code, name)
+ result.append((rec.id, name))
+ return result
+
+ @api.model
+ def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
+ args = args or []
+ if operator == 'ilike' and not (name or '').strip():
+ domain = []
+ else:
+ domain = ['|', ('name', 'ilike', name), ('code', 'ilike', name)]
+ return self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
+
+ def _filter_taxes_included(self, taxes):
+ """ This method is to be inherited by different localizations and must return filter the given taxes recordset
+ returning the taxes to be included on reports of this document type. All taxes are going to be discriminated
+ except the one returned by this method. """
+ self.ensure_one()
+ return self.env['account.tax']
diff --git a/addons/l10n_latam_invoice_document/models/res_company.py b/addons/l10n_latam_invoice_document/models/res_company.py
new file mode 100644
index 00000000..b37c24ef
--- /dev/null
+++ b/addons/l10n_latam_invoice_document/models/res_company.py
@@ -0,0 +1,12 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo import fields, models
+
+
+class ResCompany(models.Model):
+
+ _inherit = "res.company"
+
+ def _localization_use_documents(self):
+ """ This method is to be inherited by localizations and return True if localization use documents """
+ self.ensure_one()
+ return False