Skip to content
Open
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 l10n_ar_payment_bundle/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from . import models
from . import wizard
from . import demo


def post_init_hook(env):
Expand Down
2 changes: 1 addition & 1 deletion l10n_ar_payment_bundle/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
"installable": True,
"auto_install": False,
"application": False,
"demo": [],
"demo": ["demo/l10n_ar_payment_bundle_demo.xml"],
"post_init_hook": "post_init_hook",
}
1 change: 1 addition & 0 deletions l10n_ar_payment_bundle/demo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import l10n_ar_payment_bundle_demo
339 changes: 339 additions & 0 deletions l10n_ar_payment_bundle/demo/l10n_ar_payment_bundle_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
import logging

from odoo import Command, api, fields, models

_logger = logging.getLogger(__name__)


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

@api.model
def _install_l10n_ar_payment_bundle_demo(self, companies):
"""Crea data demo para argentina"""

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

# Tipo de write-off: cuenta "Rounding adjustment" (base_ajuste_por_redondeo)
write_off_account = self.env.ref(f"account.{company.id}_base_ajuste_por_redondeo", raise_if_not_found=False)
if not write_off_account:
continue

write_off_type = self.env["account.write_off.type"].search(
[("account_id", "=", write_off_account.id), ("name", "=", "Diferencia de cambio / ajuste")],
limit=1,
)
if not write_off_type:
write_off_type = self.env["account.write_off.type"].create(
{
"name": "Diferencia de cambio / ajuste",
"account_id": write_off_account.id,
}
)

# Factura de proveedor en ARS para cancelar con bundle + write-off
doc_type = self.env.ref("l10n_ar.dc_c_f") # Factura C
partner_adhoc = self.env.ref("l10n_ar.res_partner_adhoc")
invoice = self.env["account.move"].search(
[
("move_type", "=", "in_invoice"),
("company_id", "=", company.id),
("name", "like", "00001-00000099"),
("partner_id", "=", partner_adhoc.id),
Comment on lines +40 to +44
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

La búsqueda de la factura usa name like '00001-00000099', pero al crearla se setea l10n_latam_document_number='0001-00000099' (4 dígitos). Así la búsqueda no va a matchear y el instalador puede duplicar la factura. Sugerencia: buscar por l10n_latam_document_number (y/o l10n_latam_document_type_id) usando exactamente el mismo número que se crea, en lugar de name con un patrón distinto.

Copilot uses AI. Check for mistakes.
],
limit=1,
)
if not invoice:
invoice = (
self.env["account.move"]
.with_context(skip_pdf_attachment_generation=True, skip_readonly_check=True)
.create(
{
"move_type": "in_invoice",
"partner_id": partner_adhoc.id,
"currency_id": self.env.ref("base.ARS").id,
"invoice_date": today,
"l10n_latam_document_type_id": doc_type.id,
"l10n_latam_document_number": "0001-00000099",
"invoice_line_ids": [
Command.create(
{
"product_id": self.env.ref("product.product_product_2").id,
"quantity": 1,
"price_unit": 10000,
"tax_ids": [
Command.set(
[
self.env.ref(
f"account.{company.id}_ri_tax_vat_no_corresponde_ventas"
).id
]
)
],
}
)
],
"company_id": company.id,
}
)
)
if invoice.state == "draft":
invoice.action_post()

# Asegurar que el journal bundle existe (puede no haberse creado si use_payment_pro
# se activó después de la instalación del chart template)
company._create_payment_bundle_journal_if_needed()

# Bundle journal y payment method line (misma lógica que los tests)
bundle_journal = self.env["account.journal"].search(
[
("outbound_payment_method_line_ids.payment_method_id.code", "=", "payment_bundle"),
("company_id", "=", company.id),
],
limit=1,
)
bundle_pml = bundle_journal.outbound_payment_method_line_ids.filtered(
lambda l: l.payment_method_id.code == "payment_bundle"
)
bank_journal = self.env["account.journal"].search(
[("type", "=", "bank"), ("company_id", "=", company.id)], limit=1
)

if not bundle_pml or not bank_journal:
_logger.warning(
"l10n_ar_payment_bundle demo: bundle_journal=%s bundle_pml=%s bank_journal=%s — skipping company %s",
bundle_journal,
bundle_pml,
bank_journal,
company.name,
)
continue

# Línea de deuda del proveedor (solo si tiene saldo pendiente)
invoice.flush_recordset()
debt_line = invoice.line_ids.filtered(
lambda l: l.account_id.account_type == "liability_payable" and l.amount_residual != 0
)
if not debt_line:
continue

# Pago principal del bundle (amount=0, concentra deuda y write-off)
main_payment = self.env["account.payment"].create(
{
"payment_type": "outbound",
"partner_type": "supplier",
"partner_id": partner_adhoc.id,
Comment on lines +123 to +127
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

El instalador crea siempre los account.payment del bundle sin un search-before-create/clave estable. Si el <function> se re-ejecuta (p.ej. upgrade con demo, o ejecución manual), esto va a duplicar pagos/bundles. Sugerencia: hacer el instalador idempotente también para los pagos (por ejemplo, usando _load_data con XML IDs, o buscando un main payment existente por partner_id + payment_type + date + algún ref/marca explícita antes de crear).

Copilot uses AI. Check for mistakes.
"amount": 0,
"journal_id": bundle_journal.id,
"payment_method_line_id": bundle_pml.id,
"date": today,
"to_pay_move_line_ids": [Command.set(debt_line.ids)],
"write_off_amount": 100,
"write_off_type_id": write_off_type.id,
"company_id": company.id,
}
)

# Pago vinculado: tranferencia bancaria cubre el resto (deuda - write-off)
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

Typo en comentario: “tranferencia” debería ser “transferencia”.

Suggested change
# Pago vinculado: tranferencia bancaria cubre el resto (deuda - write-off)
# Pago vinculado: transferencia bancaria cubre el resto (deuda - write-off)

Copilot uses AI. Check for mistakes.
self.env["account.payment"].with_context(
default_counterpart_currency_id=main_payment.counterpart_currency_id.id
).create(
{
"payment_type": "outbound",
"partner_type": "supplier",
"partner_id": partner_adhoc.id,
"amount": 9900,
"journal_id": bank_journal.id,
"date": today,
"main_payment_id": main_payment.id,
"company_id": company.id,
}
)

if main_payment.state == "draft":
main_payment.action_post()

# ----------------------------------------------------------------
# Bundle 2: dos pagos vinculados (sin write-off ni retenciones)
# ----------------------------------------------------------------
partner_gritti = self.env.ref("l10n_ar.res_partner_gritti_agrimensura")
invoice2 = self.env["account.move"].search(
[
("move_type", "=", "in_invoice"),
("company_id", "=", company.id),
("name", "like", "00001-00000100"),
("partner_id", "=", partner_gritti.id),
],
Comment on lines +164 to +168
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

Mismo problema de idempotencia: se busca name like '00001-00000100' pero se crea con l10n_latam_document_number='0001-00000100'. Esto impide reutilizar la factura existente y puede duplicar datos demo. Sugerencia: alinear el criterio de búsqueda con el campo l10n_latam_document_number/tipo de documento.

Copilot uses AI. Check for mistakes.
limit=1,
)
if not invoice2:
invoice2 = (
self.env["account.move"]
.with_context(skip_pdf_attachment_generation=True, skip_readonly_check=True)
.create(
{
"move_type": "in_invoice",
"partner_id": partner_gritti.id,
"currency_id": self.env.ref("base.ARS").id,
"invoice_date": today,
"l10n_latam_document_type_id": doc_type.id,
"l10n_latam_document_number": "0001-00000100",
"invoice_line_ids": [
Command.create(
{
"product_id": self.env.ref("product.product_product_2").id,
"quantity": 1,
"price_unit": 10000,
"tax_ids": [
Command.set(
[
self.env.ref(
f"account.{company.id}_ri_tax_vat_no_corresponde_ventas"
).id
]
)
],
}
)
],
"company_id": company.id,
}
)
)
if invoice2.state == "draft":
invoice2.action_post()

invoice2.flush_recordset()
debt_line2 = invoice2.line_ids.filtered(
lambda l: l.account_id.account_type == "liability_payable" and l.amount_residual != 0
)

if debt_line2:
main_payment2 = self.env["account.payment"].create(
{
"payment_type": "outbound",
"partner_type": "supplier",
"partner_id": partner_gritti.id,
"amount": 0,
"journal_id": bundle_journal.id,
"payment_method_line_id": bundle_pml.id,
"date": today,
"to_pay_move_line_ids": [Command.set(debt_line2.ids)],
"company_id": company.id,
}
)

# Dos pagos vinculados: 6000 + 4000 = 10000 ARS
for linked_amount in [6000, 4000]:
self.env["account.payment"].with_context(
default_counterpart_currency_id=main_payment2.counterpart_currency_id.id
).create(
{
"payment_type": "outbound",
"partner_type": "supplier",
"partner_id": partner_gritti.id,
"amount": linked_amount,
"journal_id": bank_journal.id,
"date": today,
"main_payment_id": main_payment2.id,
"company_id": company.id,
}
)

if main_payment2.state == "draft":
main_payment2.action_post()

# ----------------------------------------------------------------
# Bundle 3: bundle de cobro (inbound, cliente) con 2 pagos vinculados
# ----------------------------------------------------------------
bundle_pml_in = bundle_journal.inbound_payment_method_line_ids.filtered(
lambda l: l.payment_method_id.code == "payment_bundle"
)
if not bundle_pml_in:
continue

doc_type_b = self.env.ref("l10n_ar.dc_b_f", raise_if_not_found=False) # Factura B
customer_invoice = self.env["account.move"].search(
[
("move_type", "=", "out_invoice"),
("company_id", "=", company.id),
("name", "like", "00002-00000001"),
("partner_id", "=", partner_gritti.id),
],
Comment on lines +260 to +264
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

La búsqueda de la factura de cliente usa name like '00002-00000001', pero al crearla se setea l10n_latam_document_number='0002-00000001'. Con ese desfase, en re-ejecuciones del instalador no va a encontrar la existente y puede duplicar la factura demo. Sugerencia: buscar por l10n_latam_document_number (y mantener el mismo formato de número).

Copilot uses AI. Check for mistakes.
limit=1,
)
if not customer_invoice:
inv_vals = {
"move_type": "out_invoice",
"partner_id": partner_gritti.id,
"currency_id": self.env.ref("base.ARS").id,
"invoice_date": today,
"invoice_line_ids": [
Command.create(
{
"product_id": self.env.ref("product.product_product_2").id,
"quantity": 1,
"price_unit": 15000,
"tax_ids": [
Command.set(
[self.env.ref(f"account.{company.id}_ri_tax_vat_no_corresponde_ventas").id]
)
],
}
)
],
"company_id": company.id,
}
if doc_type_b:
inv_vals["l10n_latam_document_type_id"] = doc_type_b.id
inv_vals["l10n_latam_document_number"] = "0002-00000001"
customer_invoice = (
self.env["account.move"]
.with_context(skip_pdf_attachment_generation=True, skip_readonly_check=True)
.create(inv_vals)
)
if customer_invoice.state == "draft":
customer_invoice.action_post()

customer_invoice.flush_recordset()
receivable_line = customer_invoice.line_ids.filtered(
lambda l: l.account_id.account_type == "asset_receivable" and l.amount_residual != 0
)
if not receivable_line:
continue

main_payment3 = self.env["account.payment"].create(
{
"payment_type": "inbound",
"partner_type": "customer",
"partner_id": partner_gritti.id,
"amount": 0,
"journal_id": bundle_journal.id,
"payment_method_line_id": bundle_pml_in.id,
"date": today,
"to_pay_move_line_ids": [Command.set(receivable_line.ids)],
"company_id": company.id,
}
)

# Dos pagos vinculados de cobro: 10000 + 5000 = 15000 ARS
for linked_amount in [10000, 5000]:
self.env["account.payment"].with_context(
default_counterpart_currency_id=main_payment3.counterpart_currency_id.id
).create(
{
"payment_type": "inbound",
"partner_type": "customer",
"partner_id": partner_gritti.id,
"amount": linked_amount,
"journal_id": bank_journal.id,
"date": today,
"main_payment_id": main_payment3.id,
"company_id": company.id,
}
)

if main_payment3.state == "draft":
main_payment3.action_post()
6 changes: 6 additions & 0 deletions l10n_ar_payment_bundle/demo/l10n_ar_payment_bundle_demo.xml
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_l10n_ar_payment_bundle_demo">
<value model="res.company" eval="obj().env.ref('base.company_ri')"/>
Comment on lines +2 to +4
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

En los demos similares del repo se marca el root como <odoo noupdate="1"> y se pasa una lista de compañías (eval="[obj().env.ref('base.company_ri')]"). Acá falta noupdate y el eval devuelve un record suelto; conviene alinear para evitar re-ejecuciones inesperadas en updates y mantener consistencia con el resto de demos.

Suggested change
<odoo>
<function model="account.chart.template" name="_install_l10n_ar_payment_bundle_demo">
<value model="res.company" eval="obj().env.ref('base.company_ri')"/>
<odoo noupdate="1">
<function model="account.chart.template" name="_install_l10n_ar_payment_bundle_demo">
<value model="res.company" eval="[obj().env.ref('base.company_ri')]"/>

Copilot uses AI. Check for mistakes.
</function>
</odoo>
Loading