diff --git a/addons/l10n_ar/models/account_journal.py b/addons/l10n_ar/models/account_journal.py
index b0af352981b..e15eab7b0ff 100644
--- a/addons/l10n_ar/models/account_journal.py
+++ b/addons/l10n_ar/models/account_journal.py
@@ -9,7 +9,8 @@ class AccountJournal(models.Model):
_inherit = "account.journal"
l10n_ar_afip_pos_system = fields.Selection(
- selection='_get_l10n_ar_afip_pos_types_selection', string='AFIP POS System')
+ selection='_get_l10n_ar_afip_pos_types_selection', string='AFIP POS System',
+ compute='_compute_l10n_ar_afip_pos_system', store=True, readonly=False)
l10n_ar_afip_pos_number = fields.Integer(
'AFIP POS Number', help='This is the point of sale number assigned by AFIP in order to generate invoices')
company_partner = fields.Many2one('res.partner', related='company_id.partner_id')
@@ -17,6 +18,18 @@ class AccountJournal(models.Model):
'res.partner', 'AFIP POS Address', help='This is the address used for invoice reports of this POS',
domain="['|', ('id', '=', company_partner), '&', ('id', 'child_of', company_partner), ('type', '!=', 'contact')]"
)
+ l10n_ar_is_pos = fields.Boolean(
+ compute="_compute_l10n_ar_is_pos", store=True, readonly=False, string="Is AFIP POS?")
+
+ @api.depends('country_code', 'type', 'l10n_latam_use_documents')
+ def _compute_l10n_ar_is_pos(self):
+ for journal in self:
+ journal.l10n_ar_is_pos = journal.country_code == 'AR' and journal.type == 'sale' and journal.l10n_latam_use_documents
+
+ @api.depends('l10n_ar_is_pos')
+ def _compute_l10n_ar_afip_pos_system(self):
+ for journal in self:
+ journal.l10n_ar_afip_pos_system = journal.l10n_ar_is_pos and journal.l10n_ar_afip_pos_system
def _get_l10n_ar_afip_pos_types_selection(self):
""" Return the list of values of the selection field. """
@@ -65,18 +78,16 @@ class AccountJournal(models.Model):
msg = _('Can not create chart of account until you configure your company AFIP Responsibility and VAT.')
raise RedirectWarning(msg, action.id, _('Go to Companies'))
- letters = letters_data['issued' if self.type == 'sale' else 'received'][
+ letters = letters_data['issued' if self.l10n_ar_is_pos else 'received'][
self.company_id.l10n_ar_afip_responsibility_type_id.code]
if counterpart_partner:
- counterpart_letters = letters_data['issued' if self.type == 'purchase' else 'received'].get(
+ counterpart_letters = letters_data['issued' if not self.l10n_ar_is_pos else 'received'].get(
counterpart_partner.l10n_ar_afip_responsibility_type_id.code, [])
letters = list(set(letters) & set(counterpart_letters))
return letters
- def _get_journal_codes(self):
+ def _get_journal_codes_domain(self):
self.ensure_one()
- if self.type != 'sale':
- return []
return self._get_codes_per_journal_type(self.l10n_ar_afip_pos_system)
@api.model
@@ -87,20 +98,32 @@ class AccountJournal(models.Model):
receipt_m_code = ['54']
receipt_codes = ['4', '9', '15']
expo_codes = ['19', '20', '21']
- if afip_pos_system == 'II_IM':
+ codes_issuer_is_supplier = [
+ '23', '24', '25', '26', '27', '28', '33', '43', '45', '46', '48', '58', '60', '61', '150', '151', '157',
+ '158', '161', '162', '164', '166', '167', '171', '172', '180', '182', '186', '188', '332']
+ codes = []
+ if (self.type == 'sale' and not self.l10n_ar_is_pos) or (self.type == 'purchase' and afip_pos_system in ['II_IM', 'RLI_RLM']):
+ codes = codes_issuer_is_supplier
+ elif self.type == 'purchase' and afip_pos_system == 'RAW_MAW':
+ # electronic invoices (wsfev1) (intersection between available docs on ws and codes_issuer_is_supplier)
+ codes = ['60', '61']
+ elif self.type == 'purchase':
+ return [('code', 'not in', codes_issuer_is_supplier)]
+ elif afip_pos_system == 'II_IM':
# pre-printed invoice
- return usual_codes + receipt_codes + expo_codes + invoice_m_code + receipt_m_code
+ codes = usual_codes + receipt_codes + expo_codes + invoice_m_code + receipt_m_code
elif afip_pos_system in ['RAW_MAW', 'RLI_RLM']:
# electronic/online invoice
- return usual_codes + receipt_codes + invoice_m_code + receipt_m_code + mipyme_codes
+ codes = usual_codes + receipt_codes + invoice_m_code + receipt_m_code + mipyme_codes
elif afip_pos_system in ['CPERCEL', 'CPEWS']:
# invoice with detail
- return usual_codes + invoice_m_code
+ codes = usual_codes + invoice_m_code
elif afip_pos_system in ['BFERCEL', 'BFEWS']:
# Bonds invoice
- return usual_codes + mipyme_codes
+ codes = usual_codes + mipyme_codes
elif afip_pos_system in ['FEERCEL', 'FEEWS', 'FEERCELP']:
- return expo_codes
+ codes = expo_codes
+ return [('code', 'in', codes)]
@api.constrains('type', 'l10n_ar_afip_pos_system', 'l10n_ar_afip_pos_number', 'l10n_latam_use_documents')
def _check_afip_configurations(self):
@@ -112,16 +135,22 @@ class AccountJournal(models.Model):
_("You can not change the journal's configuration if it already has validated invoices") + ' ('
+ ', '.join(invoices.mapped('journal_id').mapped('name')) + ')')
+ @api.constrains('l10n_ar_afip_pos_system')
+ def _check_afip_pos_system(self):
+ journals = self.filtered(
+ lambda j: j.l10n_ar_is_pos and j.type == 'purchase' and
+ j.l10n_ar_afip_pos_system not in ['II_IM', 'RLI_RLM', 'RAW_MAW'])
+ if journals:
+ raise ValidationError("\n".join([_(
+ "The pos system %s can not be used on a purchase journal (id %s)"
+ ) % (x.l10n_ar_afip_pos_system, x.id) for x in journals]))
+
@api.constrains('l10n_ar_afip_pos_number')
def _check_afip_pos_number(self):
- to_review = self.filtered(
- lambda x: x.type == 'sale' and x.l10n_latam_use_documents and
- x.company_id.account_fiscal_country_id.code == "AR")
-
- if to_review.filtered(lambda x: x.l10n_ar_afip_pos_number == 0):
+ if self.filtered(lambda j: j.l10n_ar_is_pos and j.l10n_ar_afip_pos_number == 0):
raise ValidationError(_('Please define an AFIP POS number'))
- if to_review.filtered(lambda x: x.l10n_ar_afip_pos_number > 99999):
+ if self.filtered(lambda j: j.l10n_ar_is_pos and j.l10n_ar_afip_pos_number > 99999):
raise ValidationError(_('Please define a valid AFIP POS number (5 digits max)'))
@api.onchange('l10n_ar_afip_pos_number', 'type')
diff --git a/addons/l10n_ar/models/account_move.py b/addons/l10n_ar/models/account_move.py
index ea292383f90..656da797106 100644
--- a/addons/l10n_ar/models/account_move.py
+++ b/addons/l10n_ar/models/account_move.py
@@ -1,5 +1,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api, _
+from odoo.osv import expression
from odoo.exceptions import UserError, RedirectWarning, ValidationError
from dateutil.relativedelta import relativedelta
import logging
@@ -34,6 +35,23 @@ class AccountMove(models.Model):
l10n_ar_afip_service_start = fields.Date(string='AFIP Service Start Date', readonly=True, states={'draft': [('readonly', False)]})
l10n_ar_afip_service_end = fields.Date(string='AFIP Service End Date', readonly=True, states={'draft': [('readonly', False)]})
+ def _is_manual_document_number(self):
+ """ Document number should be manual input by user when the journal use documents and
+
+ * if sales journal and not a AFIP pos (liquido producto case)
+ * if purchase journal and not a AFIP pos (regular case of vendor bills)
+
+ All the other cases the number should be automatic set, wiht only one exception, for pre-printed/online AFIP
+ POS type, the first numeber will be always set manually by the user and then will be computed automatically
+ from there """
+ if self.country_code != 'AR':
+ return super()._is_manual_document_number()
+
+ # NOTE: There is a corner case where 2 sales documents can have the same number for the same DOC from a
+ # different vendor, in that case, the user can create a new Sales Liquido Producto Journal
+ return self.l10n_latam_use_documents and self.journal_id.type in ['purchase', 'sale'] and \
+ not self.journal_id.l10n_ar_is_pos
+
@api.constrains('move_type', 'journal_id')
def _check_moves_use_documents(self):
""" Do not let to create not invoices entries in journals that use documents """
@@ -99,10 +117,11 @@ class AccountMove(models.Model):
if self.journal_id.company_id.account_fiscal_country_id.code == "AR":
letters = self.journal_id._get_journal_letter(counterpart_partner=self.partner_id.commercial_partner_id)
domain += ['|', ('l10n_ar_letter', '=', False), ('l10n_ar_letter', 'in', letters)]
- codes = self.journal_id._get_journal_codes()
- if codes:
- domain.append(('code', 'in', codes))
- if self.move_type == 'in_refund':
+ domain = expression.AND([
+ domain or [],
+ self.journal_id._get_journal_codes_domain(),
+ ])
+ if self.move_type in ['out_refund', 'in_refund']:
domain = ['|', ('code', 'in', self._get_l10n_ar_codes_used_for_inv_and_ref())] + domain
return domain
@@ -209,7 +228,7 @@ class AccountMove(models.Model):
super()._inverse_l10n_latam_document_number()
to_review = self.filtered(lambda x: (
- x.journal_id.type == 'sale'
+ x.journal_id.l10n_ar_is_pos
and x.l10n_latam_document_type_id
and x.l10n_latam_document_number
and (x.l10n_latam_manual_document_number or not x.highest_name)
@@ -308,7 +327,7 @@ class AccountMove(models.Model):
# Report vat 0%
vat_base_0 = sum(self.invoice_line_ids.filtered(lambda x: x.tax_ids.filtered(lambda y: y.tax_group_id.l10n_ar_vat_afip_code == '3')).mapped('price_subtotal'))
if vat_base_0:
- res += [{'Id': '3', 'BaseImp': vat_base_0, 'Importe': 0.0}]
+ res += [{'Id': '3', 'BaseImp': sign * vat_base_0, 'Importe': 0.0}]
return res if res else []
diff --git a/addons/l10n_ar/tests/common.py b/addons/l10n_ar/tests/common.py
index 07a139a534e..04b9196d35f 100644
--- a/addons/l10n_ar/tests/common.py
+++ b/addons/l10n_ar/tests/common.py
@@ -628,7 +628,7 @@ class TestAr(AccountTestInvoicingCommon):
pos_number = data.get('l10n_ar_afip_pos_number')
values = {'name': '%s %s' % (afip_ws.replace('WS', ''), pos_number),
'type': 'sale',
- 'code': afip_ws,
+ 'code': pos_number,
'l10n_ar_afip_pos_system': self._get_afip_pos_system_real_name().get(afip_ws),
'l10n_ar_afip_pos_number': pos_number,
'l10n_latam_use_documents': True,
diff --git a/addons/l10n_ar/tests/test_manual.py b/addons/l10n_ar/tests/test_manual.py
index faaa61a0bae..d8104eee041 100644
--- a/addons/l10n_ar/tests/test_manual.py
+++ b/addons/l10n_ar/tests/test_manual.py
@@ -1,6 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import common
from odoo.tests import tagged
+from odoo.tests.common import Form
@tagged('post_install_l10n', '-at_install', 'post_install')
@@ -97,3 +98,78 @@ class TestManual(common.TestAr):
""" Responsable Inscripto" in USD and VAT 21 """
self._prepare_multicurrency_values()
self._post(self.demo_invoices['test_invoice_10'])
+
+ def test_15_liquido_producto_sales(self):
+ """ Manual Numbering: Sales and not POS (Liquido Producto) """
+
+ # Verify that the default sales journals ara created as is AFIP POS
+ self.assertTrue(self.journal.l10n_ar_is_pos)
+
+ # If we create an invoice it will not use manual numbering
+ invoice = self._create_invoice({'partner': self.partner})
+ self.assertFalse(invoice.l10n_latam_manual_document_number)
+
+ # Create a new sale journal that is not AFIP POS
+ self.journal = self._create_journal('preprinted', data={'l10n_ar_is_pos': False})
+ self.assertFalse(self.journal.l10n_ar_is_pos)
+
+ doc_27_lu_a = self.env.ref('l10n_ar.dc_liq_uci_a')
+ payment_term_id = self.env.ref("account.account_payment_term_end_following_month")
+
+ # 60, 61, 27, 28, 45, 46
+ # In this case manual numbering should be True and the latam document numer should be required
+ with self.assertRaisesRegex(AssertionError, 'l10n_latam_document_number is a required field'):
+ with Form(self.env['account.move'].with_context(default_move_type='out_invoice')) as invoice_form:
+ invoice_form.ref = "demo_liquido_producto_1: Vendor bill liquido producto (DOC 186)"
+ invoice_form.partner_id = self.res_partner_adhoc
+ invoice_form.invoice_payment_term_id = payment_term_id
+ invoice_form.journal_id = self.journal
+ invoice_form.l10n_latam_document_type_id = doc_27_lu_a
+ invoice = invoice_form.save()
+
+ # Adding the document number will let us to save and validate the number without any problems
+ with Form(self.env['account.move'].with_context(default_move_type='out_invoice')) as invoice_form:
+ invoice_form.ref = "demo_liquido_producto_1: Vendor bill liquido producto (DOC 186)"
+ invoice_form.partner_id = self.res_partner_adhoc
+ invoice_form.invoice_payment_term_id = payment_term_id
+ invoice_form.journal_id = self.journal
+ invoice_form.l10n_latam_document_type_id = doc_27_lu_a
+ invoice_form.l10n_latam_document_number = "00077-00000077"
+ invoice = invoice_form.save()
+
+ def test_16_liquido_producto_purchase(self):
+ """ Manual Numbering: Purchase POS/ NOT POS (Liquido Producto) """
+
+ # By default purchase journals ar not AFIP POS journal
+ purchase_not_pos_journal = self.env["account.journal"].search([
+ ('type', '=', 'purchase'), ('company_id', '=', self.env.company.id), ('l10n_latam_use_documents', '=', True)])
+ self.assertFalse(purchase_not_pos_journal.l10n_ar_is_pos)
+
+ doc_60_lp_a = self.env.ref('l10n_ar.dc_a_cvl')
+ payment_term_id = self.env.ref("account.account_payment_term_end_following_month")
+
+ with self.assertRaisesRegex(AssertionError, 'l10n_latam_document_number is a required field'):
+ with Form(self.env['account.move'].with_context(default_move_type='in_invoice')) as bill_form:
+ bill_form.ref = "demo_liquido_producto_1: Vendor bill liquido producto (DOC 186)"
+ bill_form.partner_id = self.res_partner_adhoc
+ bill_form.invoice_payment_term_id = payment_term_id
+ bill_form.invoice_date = '2023-02-09'
+ bill_form.journal_id = purchase_not_pos_journal
+ bill_form.l10n_latam_document_type_id = doc_60_lp_a
+ bill = bill_form.save()
+
+ # Create a new journal that is an AFIP POS
+ purchase_pos_journal = self._create_journal('preprinted', data={'type': 'purchase', 'l10n_ar_is_pos': True})
+
+ with Form(self.env['account.move'].with_context(default_move_type='in_invoice')) as bill_form:
+ bill_form.ref = "demo_liquido_producto_1: Vendor bill liquido producto (DOC 186)"
+ bill_form.partner_id = self.res_partner_adhoc
+ bill_form.invoice_payment_term_id = payment_term_id
+ bill_form.invoice_date = '2023-02-09'
+ bill_form.journal_id = purchase_pos_journal
+ bill_form.l10n_latam_document_type_id = doc_60_lp_a
+ bill_form.l10n_latam_document_number = "00077-00000077"
+ bill = bill_form.save()
+
+ # If we create an invoice it will not use manual numbering
+ self.assertFalse(bill.l10n_latam_manual_document_number)
diff --git a/addons/l10n_ar/views/account_journal_view.xml b/addons/l10n_ar/views/account_journal_view.xml
index 665cfa13558..3078ae5c082 100644
--- a/addons/l10n_ar/views/account_journal_view.xml
+++ b/addons/l10n_ar/views/account_journal_view.xml
@@ -7,10 +7,11 @@
+
-
-
-
+
+
+