Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions account_exchange_difference_invoice/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from . import models
from . import wizards
from . import demo
10 changes: 4 additions & 6 deletions account_exchange_difference_invoice/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,17 @@
"website": "www.adhoc.com.ar",
"author": "ADHOC SA",
"license": "AGPL-3",
"depends": [
"account_debit_note",
],
"depends": ["account_debit_note", "l10n_ar"],
"data": [
"security/ir.model.access.csv",
"views/account_move_line_views.xml",
"wizards/exchange_difference_wizard_views.xml",
"views/res_config_settings.xml",
],
"demo": [
"demo/account_exchange_demo.xml",
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El archivo demo está siendo agregado en la sección 'data' en lugar de en la sección 'demo'. Los archivos de datos de demostración deben estar en la sección 'demo' del manifest para que solo se carguen cuando se instale el módulo con demo data habilitada.

Copilot uses AI. Check for mistakes.
],
"installable": True,
"auto_install": False,
"application": False,
"demo": [
"demo/demo_data.xml",
],
}
1 change: 1 addition & 0 deletions account_exchange_difference_invoice/demo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import account_exchange_demo
315 changes: 315 additions & 0 deletions account_exchange_difference_invoice/demo/account_exchange_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
import logging
from datetime import date

from dateutil.relativedelta import relativedelta
from odoo import api, models

_logger = logging.getLogger(__name__)


class AccountChartTemplate(models.AbstractModel):
_inherit = "account.chart.template"

@api.model
def _install_exchange_diff_demo(self, companies):
# Just ARG companies for now
if not companies:
_logger.info("No companies provided for exchange demo data creation, skipping.")
return

_logger.info("Creating exchange demo data for companies: %s", companies.mapped("name"))

for company in companies.filtered(lambda x: x.country_code == "AR"):
self = self.with_company(company)

# Skip if demo data already exists to avoid duplicates on reinstall
# Check for a specific XML ID created by this module's demo data
demo_marker = self.env.ref(
"account_exchange_difference_invoice.demo_exchange_diff_installed_%s" % company.id,
raise_if_not_found=False,
)
if demo_marker:
_logger.info("Demo data already exists for company %s, skipping creation", company.name)
continue

self._create_exchange_rate_demo_data()
invoices = self._create_exchange_difference_demo_invoices()
self._create_exchange_difference_demo_payment(invoices)

# Create marker to indicate demo data has been installed for this company
self.env["ir.model.data"].create(
{
"name": "demo_exchange_diff_installed_%s" % company.id,
"module": "account_exchange_difference_invoice",
"model": "res.company",
"res_id": company.id,
}
)

def _create_exchange_rate_demo_data(self):
"""Create some USD exchange rates for demo purposes for today and the past 3 months."""
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El docstring dice "Create some USD exchange rates for demo purposes for today and the past 3 months" pero el código genera solo 4 tasas (hoy y 3 meses hacia atrás con intervalos de 1 mes), no tasas para todos los días de los últimos 3 meses. El docstring debería ser más preciso: "Create USD exchange rates for today and 1, 2, and 3 months ago".

Suggested change
"""Create some USD exchange rates for demo purposes for today and the past 3 months."""
"""Create USD exchange rates for demo purposes for today and 1, 2, and 3 months ago."""

Copilot uses AI. Check for mistakes.
currency_usd = self.env.ref("base.USD")
company = self.env.company
exchange_rate_model = self.env["res.currency.rate"]
today = date.today()
rates = [1300, 1200, 1100, 1000] # today, 1 month ago, 2 months ago, 3 months ago
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Los números 1300, 1200, 1100, 1000 son valores mágicos sin explicación clara. Deberían definirse como constantes con nombres descriptivos al inicio del método o del archivo para mejorar la mantenibilidad y claridad del código.

Copilot uses AI. Check for mistakes.
for months_ago, rate_value in enumerate(rates):
date_value = today + relativedelta(months=-months_ago)

# Check if rate already exists for this date
existing_rate = exchange_rate_model.search(
[
("name", "=", date_value.isoformat()),
("currency_id", "=", currency_usd.id),
("company_id", "=", company.id),
],
limit=1,
)

if not existing_rate:
exchange_rate_model.create(
{
"name": date_value.isoformat(),
"rate": 1 / rate_value,
"currency_id": currency_usd.id,
"company_id": company.id,
}
)
_logger.info("Created exchange rate for %s: %s", date_value, rate_value)
else:
_logger.info("Exchange rate already exists for %s, skipping", date_value)

def _get_demo_partner(self):
"""Return a partner for demo invoices."""
partner = self.env.ref("base.res_partner_12", raise_if_not_found=False)
if partner:
return partner
return self.env["res.partner"].search([("company_id", "=", self.env.company.id)], limit=1) or self.env[
"res.partner"
].search([], limit=1)

def _get_caba_fiscal_position(self):
"""Return fiscal position for 'Percepciones CABA' if it exists."""
return self.env["account.fiscal.position"].search(
[("name", "ilike", "Percepciones CABA"), ("company_id", "=", self.env.company.id)],
limit=1,
)

def _create_exchange_difference_demo_invoices(self):
"""Create demo invoices in USD with specific amounts and tax breakdown."""
currency_usd = self.env.ref("base.USD", raise_if_not_found=False)

# Get partner
partner = self.env.ref("base.res_partner_12", raise_if_not_found=False)
if not partner:
partner = self.env["res.partner"].search([("company_id", "=", self.env.company.id)], limit=1)
Comment on lines +103 to +105
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El método _get_demo_partner() en la línea 77-84 duplica la lógica que ya existe en las líneas 98-100. Esta búsqueda de partner debería reutilizarse llamando al método _get_demo_partner() en lugar de duplicar el código.

Suggested change
partner = self.env.ref("base.res_partner_12", raise_if_not_found=False)
if not partner:
partner = self.env["res.partner"].search([("company_id", "=", self.env.company.id)], limit=1)
partner = self._get_demo_partner()

Copilot uses AI. Check for mistakes.

# Get different partner for invoice3
partner2 = self.env.ref("base.res_partner_3", raise_if_not_found=False)
if not partner2:
partner2 = self.env["res.partner"].search([("company_id", "=", self.env.company.id)], limit=1, offset=1)
if not partner2:
partner2 = partner

# Get fiscal position
fiscal_position = self.env["account.fiscal.position"].search(
[("name", "ilike", "Percepciones CABA"), ("company_id", "=", self.env.company.id)],
limit=1,
)
Comment on lines +115 to +118
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El método _get_caba_fiscal_position() en la línea 86-91 duplica la lógica que ya existe en las líneas 103-106. Esta búsqueda de posición fiscal debería reutilizarse llamando al método _get_caba_fiscal_position() en lugar de duplicar el código.

Suggested change
fiscal_position = self.env["account.fiscal.position"].search(
[("name", "ilike", "Percepciones CABA"), ("company_id", "=", self.env.company.id)],
limit=1,
)
fiscal_position = self._get_caba_fiscal_position()

Copilot uses AI. Check for mistakes.

# Get document type for Argentina (Factura A)
document_type = self.env["l10n_latam.document.type"].search(
[("code", "=", "1"), ("country_id.code", "=", "AR")],
limit=1,
)

# Get taxes
vat_21 = self.env["account.tax"].search(
[("company_id", "=", self.env.company.id), ("type_tax_use", "=", "sale"), ("amount", "=", 21)],
limit=1,
)
vat_10_5 = self.env["account.tax"].search(
[("company_id", "=", self.env.company.id), ("type_tax_use", "=", "sale"), ("amount", "=", 10.5)],
limit=1,
)
percep_3 = self.env["account.tax"].search(
[("company_id", "=", self.env.company.id), ("type_tax_use", "=", "sale"), ("amount", "=", 3)],
limit=1,
)

# Invoice 1: Neto 21% (100) + IVA 21% (21) + Neto 10.5% (100) + IVA 10.5% (10.5) + Percep 3% (6) = 237.5 USD @ TC 1000
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El comentario indica "# Invoice 1: ... @ TC 1000" pero no hay código que garantice que la tasa de cambio sea 1000 en la fecha de hoy. Aunque el método _create_exchange_rate_demo_data() crea una tasa de 1300 para hoy, el comentario menciona 1000. Esto genera confusión sobre cuál es la tasa esperada.

Suggested change
# Invoice 1: Neto 21% (100) + IVA 21% (21) + Neto 10.5% (100) + IVA 10.5% (10.5) + Percep 3% (6) = 237.5 USD @ TC 1000
# Invoice 1: Neto 21% (100) + IVA 21% (21) + Neto 10.5% (100) + IVA 10.5% (10.5) + Percep 3% (6) = 237.5 USD

Copilot uses AI. Check for mistakes.
invoice1 = self.env["account.move"].create(
{
"move_type": "out_invoice",
"partner_id": partner.id,
"currency_id": currency_usd.id if currency_usd else self.env.company.currency_id.id,
"invoice_date": date.today(),
"fiscal_position_id": fiscal_position.id if fiscal_position else False,
"l10n_latam_document_type_id": document_type.id if document_type else False,
"invoice_line_ids": [
(
0,
0,
{
"name": "Product with VAT 21% + Percep 3%",
"quantity": 1,
"price_unit": 100.0,
"tax_ids": [(6, 0, (vat_21 + percep_3).ids if vat_21 and percep_3 else vat_21.ids)],
},
),
(
0,
0,
{
"name": "Product with VAT 10.5% + Percep 3%",
"quantity": 1,
"price_unit": 100.0,
"tax_ids": [(6, 0, (vat_10_5 + percep_3).ids if vat_10_5 and percep_3 else vat_10_5.ids)],
},
),
],
}
)
# Set exchange rate to 1000 before posting
if currency_usd:
invoice1.write({"currency_id": currency_usd.id})
Comment on lines +173 to +175
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Se hace write() de currency_id inmediatamente después de crear la factura con currency_id ya asignado. Esta operación es redundante porque el campo ya se estableció en el create(). Si el propósito era forzar algún recompute, debería documentarse; de lo contrario, puede eliminarse.

Suggested change
# Set exchange rate to 1000 before posting
if currency_usd:
invoice1.write({"currency_id": currency_usd.id})

Copilot uses AI. Check for mistakes.
invoice1.action_post()

# Invoice 2: Neto 21% (150) + IVA 21% (31.5) + Neto 10.5% (500) + IVA 10.5% (52.5) + Percep 3% (19.5) = 753.5 USD @ TC 1100
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El comentario indica "# Invoice 2: ... @ TC 1100" pero no hay código que establezca o valide que la factura use la tasa de cambio de 1100. Aunque se crea una tasa de 1100 para hace 2 meses, la factura se crea con fecha de hoy (date.today()), por lo que usaría la tasa del día actual (1300). Esto genera confusión sobre el propósito del demo.

Copilot uses AI. Check for mistakes.
invoice2 = self.env["account.move"].create(
{
"move_type": "out_invoice",
"partner_id": partner.id,
"currency_id": currency_usd.id if currency_usd else self.env.company.currency_id.id,
"invoice_date": date.today(),
"fiscal_position_id": fiscal_position.id if fiscal_position else False,
"l10n_latam_document_type_id": document_type.id if document_type else False,
"invoice_line_ids": [
(
0,
0,
{
"name": "Product with VAT 21% + Percep 3%",
"quantity": 1,
"price_unit": 150.0,
"tax_ids": [(6, 0, (vat_21 + percep_3).ids if vat_21 and percep_3 else vat_21.ids)],
},
),
(
0,
0,
{
"name": "Product with VAT 10.5% + Percep 3%",
"quantity": 1,
"price_unit": 500.0,
"tax_ids": [(6, 0, (vat_10_5 + percep_3).ids if vat_10_5 and percep_3 else vat_10_5.ids)],
},
),
],
}
)
if currency_usd:
invoice2.write({"currency_id": currency_usd.id})
Comment on lines +211 to +212
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Se hace write() de currency_id inmediatamente después de crear la factura con currency_id ya asignado. Esta operación es redundante porque el campo ya se estableció en el create(). Si el propósito era forzar algún recompute, debería documentarse; de lo contrario, puede eliminarse.

Suggested change
if currency_usd:
invoice2.write({"currency_id": currency_usd.id})

Copilot uses AI. Check for mistakes.
invoice2.action_post()

# Invoice 3: Neto 21% (100) + IVA 21% (21) = 121 USD @ TC 1000
invoice3 = self.env["account.move"].create(
{
"move_type": "out_invoice",
"partner_id": partner2.id,
"currency_id": currency_usd.id if currency_usd else self.env.company.currency_id.id,
"invoice_date": date.today(),
"fiscal_position_id": fiscal_position.id if fiscal_position else False,
"l10n_latam_document_type_id": document_type.id if document_type else False,
"invoice_line_ids": [
(
0,
0,
{
"name": "Product with VAT 21%",
"quantity": 1,
"price_unit": 100.0,
"tax_ids": [(6, 0, vat_21.ids if vat_21 else [])],
},
),
],
}
)
if currency_usd:
invoice3.write({"currency_id": currency_usd.id})
invoice3.action_post()

_logger.info(
"Created demo invoices: %s (237.5 USD @ TC 1000), %s (753.5 USD @ TC 1100), %s (121 USD @ TC 1000)",
invoice1.name,
invoice2.name,
invoice3.name,
)

return invoice1 + invoice2 + invoice3

def _create_exchange_difference_demo_payment(self, invoices):
"""Create a payment that pays the demo invoices."""
if not invoices:
return

# Get bank journal
bank_journal = self.env["account.journal"].search(
[
("type", "=", "bank"),
("company_id", "=", self.env.company.id),
],
limit=1,
)

# Calculate amounts: 991 USD at rate 1200 = 1,189,200 ARS
usd_currency = self.env.ref("base.USD")
counterpart_amount_usd = 991.0
exchange_rate = 1200.0
Comment on lines +267 to +268
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Los valores 991.0 y 1200.0 son números mágicos sin contexto. Deberían definirse como constantes con nombres descriptivos que expliquen por qué se usan esos valores específicos para el demo.

Copilot uses AI. Check for mistakes.
amount_ars = counterpart_amount_usd * exchange_rate
Comment on lines +265 to +269
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El comentario dice "Calculate amounts: 991 USD at rate 1200 = 1,189,200 ARS" pero el cálculo real en la línea 229 es correcto (991 * 1200 = 1,189,200). Sin embargo, no queda claro por qué se usa una tasa de 1200 en el código cuando las facturas usan la tasa del día (que sería 1300 según _create_exchange_rate_demo_data). Esto parece inconsistente con el propósito de generar diferencias de cambio.

Suggested change
# Calculate amounts: 991 USD at rate 1200 = 1,189,200 ARS
usd_currency = self.env.ref("base.USD")
counterpart_amount_usd = 991.0
exchange_rate = 1200.0
amount_ars = counterpart_amount_usd * exchange_rate
# Calculate amounts for 991 USD using the current exchange rate
usd_currency = self.env.ref("base.USD")
counterpart_amount_usd = 991.0
amount_ars = usd_currency._convert(
counterpart_amount_usd,
self.env.company.currency_id,
self.env.company,
date.today(),
)

Copilot uses AI. Check for mistakes.

# Create payment
payment = self.env["account.payment"].create(
{
"payment_type": "inbound",
"partner_type": "customer",
"partner_id": invoices[0].partner_id.id,
"amount": amount_ars,
"currency_id": self.env.company.currency_id.id,
"date": date.today(),
"journal_id": bank_journal.id,
Comment on lines +257 to +280
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Si no se encuentra un journal de tipo 'bank', el método fallará con un error al intentar acceder a bank_journal.id cuando bank_journal es un recordset vacío. Se debería validar que se encontró un journal y, en caso contrario, registrar un warning o crear/usar un journal por defecto.

Copilot uses AI. Check for mistakes.
"counterpart_exchange_rate": exchange_rate,
"counterpart_currency_id": usd_currency.id,
"counterpart_currency_amount": counterpart_amount_usd,
}
)

payment.action_post()

_logger.info("Created demo payment: %s", payment.name)
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Falta el return statement. El método debería retornar el pago creado para mantener consistencia con _create_exchange_difference_demo_invoices() que retorna las facturas, y para facilitar posibles extensiones o debugging.

Suggested change
_logger.info("Created demo payment: %s", payment.name)
_logger.info("Created demo payment: %s", payment.name)
return payment

Copilot uses AI. Check for mistakes.

# Create payment for invoice3: 121 USD at rate 1100
if len(invoices) > 2:
invoice3 = invoices[2]
counterpart_amount_usd_inv3 = 121.0
exchange_rate_inv3 = 1100.0
amount_ars_inv3 = counterpart_amount_usd_inv3 * exchange_rate_inv3

payment3 = self.env["account.payment"].create(
{
"payment_type": "inbound",
"partner_type": "customer",
"partner_id": invoice3.partner_id.id,
"amount": amount_ars_inv3,
"currency_id": self.env.company.currency_id.id,
"date": date.today(),
"journal_id": bank_journal.id,
"counterpart_exchange_rate": exchange_rate_inv3,
"counterpart_currency_id": usd_currency.id,
"counterpart_currency_amount": counterpart_amount_usd_inv3,
}
)

payment3.action_post()

_logger.info("Created demo payment for invoice3: %s", payment3.name)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<function model="account.chart.template" name="_install_exchange_diff_demo">
<value model="res.company" eval="obj().search([('chart_template', '=', 'ar_ri')])"/>
</function>
</odoo>