Skip to content

Commit c67b93f

Browse files
committed
[FIX] l10n_ar_wsmtxca_ws: discounts
1 parent 5bf7af0 commit c67b93f

3 files changed

Lines changed: 121 additions & 85 deletions

File tree

l10n_ar_wsmtxca_ws/README.rst

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
.. |company| replace:: ADHOC SA
2+
3+
.. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png
4+
:alt: ADHOC SA
5+
:target: https://www.adhoc.com.ar
6+
7+
.. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png
8+
9+
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
10+
:target: https://www.gnu.org/licenses/agpl
11+
:alt: License: AGPL-3
12+
13+
=====================================================
14+
Argentinean Electronic Invoicing - WSMTXCA Webservice
15+
=====================================================
16+
17+
Extends the Argentinean Electronic Invoicing (l10n_ar_edi) to add support for the WSMTXCA webservice (RG2904 - Codificación de producto).
18+
19+
Installation
20+
============
21+
22+
To install this module, you need to:
23+
24+
#. Install the module from the Apps menu.
25+
26+
Configuration
27+
=============
28+
29+
To configure this module, you need to:
30+
31+
#. Configure your AFIP certificate in *Accounting > Configuration > Settings* (inherited from l10n_ar_edi).
32+
#. Create or edit a Sales Journal and set **AFIP POS System** to ``Codificación de producto - Web Service (WSMTXCAWS)``.
33+
#. Set the **AFIP POS Number** according to your AFIP portal configuration.
34+
35+
Usage
36+
=====
37+
38+
Once configured, the module handles WSMTXCA electronic invoicing automatically when validating invoices
39+
from a journal with the ``WSMTXCAWS`` POS system:
40+
41+
#. Create an invoice in a journal configured with the WSMTXCAWS POS system.
42+
#. Validate the invoice. The module will contact the WSMTXCA webservice and request a CAE.
43+
#. To consult a previously authorized invoice, use *Accounting > Reporting > AFIP WS Consult*.
44+
#. To check available AFIP POS numbers for the webservice, use the **Check Available AFIP PoS** button on the journal (production mode only).
45+
#. To consult the exchange rate for a currency via WSMTXCA, use the currency rate consultation feature on the currency record.
46+
47+
Technical
48+
=========
49+
50+
The module implements the following model extensions:
51+
52+
* ``account.move``: overrides ``_l10n_ar_do_afip_ws_request_cae`` to handle WSMTXCA-specific CAE requests (``autorizarComprobante``), response parsing, and tribute/tax formatting.
53+
* ``account.journal``: adds the ``WSMTXCAWS`` POS system option, maps it to the ``wsmtxca`` webservice, implements ``_wsmtxca_convert_auth`` for WSMTXCA authentication, and overrides ``l10n_ar_check_afip_pos_number`` and ``_l10n_ar_get_afip_last_invoice_number`` for WSMTXCA.
54+
* ``res.currency``: overrides ``_l10n_ar_get_afip_ws_currency_rate`` to query exchange rates via the WSMTXCA ``consultarCotizacionMoneda`` service.
55+
* ``l10n_ar.afipws.connection``: overrides ``_l10n_ar_get_afip_ws_url`` to register the WSMTXCA WSDL endpoints for production and testing environments.
56+
* ``l10n_ar_afip.ws.consult`` (wizard): extends the invoice consultation wizard to support the ``WSMTXCAWS`` POS system, delegating to ``consultarComprobante`` on the WSMTXCA service.
57+
58+
WSMTXCA endpoints:
59+
60+
* Production: ``https://serviciosjava.afip.gob.ar/wsmtxca/services/MTXCAService?wsdl``
61+
* Testing: ``https://fwshomo.afip.gov.ar/wsmtxca/services/MTXCAService?wsdl``
62+
63+
For official AFIP documentation see: https://www.afip.gob.ar/ws/documentacion/ws-mtxca.asp
64+
65+
Credits
66+
=======
67+
68+
Images
69+
------
70+
71+
* |company| |icon|
72+
73+
Contributors
74+
------------
75+
76+
Maintainer
77+
----------
78+
79+
|company_logo|
80+
81+
This module is maintained by the |company|.
82+
83+
To contribute to this module, please visit https://www.adhoc.com.ar.

l10n_ar_wsmtxca_ws/__manifest__.py

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -25,56 +25,7 @@
2525
"author": "ADHOC SA",
2626
"website": "www.adhoc.com.ar",
2727
"license": "AGPL-3",
28-
"summary": """
29-
Argentinean Electronic Invoicing - WSMTXCA Webservice
30-
======================================================
31-
32-
This module extends the Argentinean Electronic Invoicing (l10n_ar_edi) to add support for the WSMTXCA webservice.
33-
34-
Functional
35-
----------
36-
37-
WSMTXCA - "Mercado de Cambio Electrónico" (Electronic Exchange Market)
38-
39-
This webservice is used for electronic invoicing in the context of foreign exchange operations in Argentina.
40-
It allows generating and validating invoices with specific requirements for:
41-
42-
* Foreign currency operations
43-
* Exchange market transactions
44-
* Invoices with special tax treatment for international operations
45-
46-
The module inherits and extends the base electronic invoicing functionality to handle:
47-
48-
* Specific WSMTXCA authentication conversion
49-
* WSMTXCA-specific request data format
50-
* Product MTX codes (barcode validation for GTIN 8/12/13)
51-
* Special handling for invoice line details
52-
* Tributes and taxes in WSMTXCA format
53-
* Related invoice data conversion
54-
* Currency exchange rate validations specific to WSMTXCA
55-
56-
Configuration
57-
-------------
58-
59-
1. Install the module
60-
2. Configure your AFIP certificate in the Accounting Settings (inherited from l10n_ar_edi)
61-
3. Create a Sales Journal with AFIP POS System set to "WSMTXCA - Mercado de Cambio Electrónico"
62-
4. Configure the AFIP POS Number according to your AFIP portal configuration
63-
64-
Technical
65-
---------
66-
67-
The module implements:
68-
69-
* account.move inheritance for WSMTXCA-specific CAE request handling
70-
* account.journal inheritance for WSMTXCA authentication conversion
71-
* res.currency inheritance for WSMTXCA-specific currency rate handling
72-
* l10n_ar_afipws.connection inheritance for WSMTXCA webservice connection
73-
* l10n_ar_afip.ws.consult wizard inheritance for WSMTXCA invoice consultation
74-
75-
For more information about WSMTXCA go to:
76-
https://www.afip.gob.ar/ws/documentacion/ws-mtxca.asp
77-
""",
28+
"summary": "",
7829
"depends": [
7930
"l10n_ar_edi",
8031
],

l10n_ar_wsmtxca_ws/models/account_move.py

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,11 @@ def _get_line_details(self, base_lines=None):
149149
return details
150150

151151
details = []
152-
# WSMTXCA requiere 6 decimales en precioUnitario e importeBonificacion
153-
price_precision_digits = min(self.env["decimal.precision"].precision_get("Product Price"), 6)
152+
price_precision_digits = min(self.env["decimal.precision"].precision_get("Product Price"), 3)
154153

155154
for base_line in base_lines:
156155
line = base_line["record"]
157156

158-
# _get_rounded_base_and_tax_lines ya excluye section/note, pero lo dejamos por seguridad
159157
if line.display_type in ("line_section", "line_note"):
160158
continue
161159

@@ -165,50 +163,55 @@ def _get_line_details(self, base_lines=None):
165163
Pro_umed = line.product_uom_id.l10n_ar_afip_code if line.product_id else "00"
166164
quantity = base_line["quantity"]
167165

168-
# Ajuste de uom especial para señas/bonificaciones
169166
if Pro_umed not in ("97", "99", "00"):
170167
if line._get_downpayment_lines():
171168
Pro_umed = "97"
172169
elif line.price_unit < 0:
173170
Pro_umed = "99"
174171

175172
is_senia_or_discount = Pro_umed in ("97", "99")
173+
is_letter_b = self.l10n_latam_document_type_id.code in ("6", "7", "8")
176174

177-
# --- Descuento (bonus) usando el engine de impuestos ---
178-
# Misma lógica que el método base de Odoo:
179-
# precio_truncado * qty - base_neta_sin_redondear = importe del descuento
180-
unit_price_truncated = float_repr(line.price_unit, precision_digits=price_precision_digits)
181-
discount_amount = (
182-
base_line["discount"]
183-
and (
184-
float(unit_price_truncated) * quantity
185-
- base_line["tax_details"]["raw_total_excluded_currency"]
186-
)
187-
or 0.0
188-
)
189-
175+
unit_price_net = float_repr(line.price_unit, precision_digits=price_precision_digits)
190176
vat_tax = line.tax_ids.filtered(lambda x: x.tax_group_id.l10n_ar_vat_afip_code)
191-
is_letter_b = self.l10n_latam_document_type_id.code in ("6", "7", "8")
192177

193-
# --- importeItem: total de la línea con impuestos incluidos ---
194-
# total_included_currency ya tiene el redondeo correcto del engine
195-
importeItem = base_line["tax_details"]["total_included_currency"]
178+
# IVA amount from tax details
179+
importeIVA_taxes = sum(
180+
td["tax_amount_currency"]
181+
for td in base_line["tax_details"]["taxes_data"]
182+
if td["tax"].tax_group_id.l10n_ar_vat_afip_code
183+
)
196184

197185
if is_letter_b:
198-
# Factura B: el IVA va "adentro" del precio, no se informa por separado
186+
# Letter B: precioUnitario includes IVA, importeIVA is not reported separately
187+
vat_rate = vat_tax.amount / 100.0 if vat_tax else 0.0
188+
precioUnitario = float_repr(
189+
line.price_unit * (1 + vat_rate),
190+
precision_digits=price_precision_digits,
191+
)
192+
total_with_iva = base_line["tax_details"]["raw_total_included_currency"]
193+
discount_amount = base_line["discount"] and (float(precioUnitario) * quantity - total_with_iva) or 0.0
199194
importeIVA = 0.0
200-
# precioUnitario incluye IVA para letra B
201-
precioUnitario = line.price_unit * (1 + (vat_tax.amount / 100.0)) if vat_tax else line.price_unit
195+
importeItem = (
196+
float(precioUnitario) * quantity - discount_amount if not is_senia_or_discount else total_with_iva
197+
)
202198
else:
203-
# Factura A/M: importeIVA = suma de impuestos IVA de esta línea
204-
# Filtramos tax_details para tomar solo los impuestos con código VAT AFIP
205-
# (excluye tributos/percepciones que también pueden estar en tax_details)
206-
importeIVA = sum(
207-
td["tax_amount_currency"]
208-
for td in base_line["tax_details"]["tax_details"].values()
209-
if td["tax"].tax_group_id.l10n_ar_vat_afip_code
199+
# Letter A (and others): precioUnitario is NET (sin IVA), importeIVA reported separately
200+
precioUnitario = unit_price_net
201+
# Descuento NETO (sin IVA): precio_truncado * qty - base_neta_cruda
202+
discount_amount = (
203+
base_line["discount"]
204+
and (float(unit_price_net) * quantity - base_line["tax_details"]["raw_total_excluded_currency"])
205+
or 0.0
206+
)
207+
importeIVA = importeIVA_taxes
208+
# importeItem = base_neta + importeIVA (fórmula AFIP, error 519)
209+
base_neta = (
210+
float(unit_price_net) * quantity - discount_amount
211+
if not is_senia_or_discount
212+
else base_line["tax_details"]["raw_total_excluded_currency"]
210213
)
211-
precioUnitario = line.price_unit
214+
importeItem = base_neta + importeIVA
212215

213216
values = {
214217
"unidadesMtx": "1",
@@ -217,11 +220,10 @@ def _get_line_details(self, base_lines=None):
217220
"descripcion": line.name,
218221
"codigoUnidadMedida": int(Pro_umed) or 7,
219222
"cantidad": quantity if not is_senia_or_discount else None,
220-
"precioUnitario": float_repr(precioUnitario, precision_digits=6) if not is_senia_or_discount else None,
223+
"precioUnitario": precioUnitario if not is_senia_or_discount else None,
221224
"codigoCondicionIVA": vat_tax.tax_group_id.l10n_ar_vat_afip_code,
222225
"importeItem": float_repr(importeItem, precision_digits=2),
223-
# importeIVA: solo se envía si hay IVA (no-letra B) o cuando IVA es 0 pero no es letra B
224-
"importeIVA": float_repr(importeIVA, precision_digits=2) if (importeIVA or not is_letter_b) else None,
226+
"importeIVA": float_repr(importeIVA, precision_digits=2) if importeIVA else None,
225227
"importeBonificacion": float_repr(discount_amount, precision_digits=6) if discount_amount else None,
226228
}
227229
details.append(values)

0 commit comments

Comments
 (0)