-
Notifications
You must be signed in to change notification settings - Fork 132
[WIP] #851
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] #851
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,18 @@ class ExchangeDifferenceWizard(models.TransientModel): | |||||||||||||||||||||||||||||||
| domain=[("type", "=", "sale")], | ||||||||||||||||||||||||||||||||
| check_company=True, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| fiscal_position = fields.Selection( | ||||||||||||||||||||||||||||||||
| [("automatic", "Automatic"), ("manual", "Manual")], | ||||||||||||||||||||||||||||||||
| default="automatic", | ||||||||||||||||||||||||||||||||
| string="Fiscal Position Mode", | ||||||||||||||||||||||||||||||||
| required=True, | ||||||||||||||||||||||||||||||||
| help="If automatic, fiscal position will be auto detected for each customer, if manual you can force one or none fiscal position.", | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| fiscal_position_id = fields.Many2one( | ||||||||||||||||||||||||||||||||
| "account.fiscal.position", | ||||||||||||||||||||||||||||||||
| string="Fiscal Position", | ||||||||||||||||||||||||||||||||
| help="Fiscal position to use for all the customers.", | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @api.model | ||||||||||||||||||||||||||||||||
| def default_get(self, fields): | ||||||||||||||||||||||||||||||||
|
|
@@ -36,7 +48,10 @@ def default_get(self, fields): | |||||||||||||||||||||||||||||||
| # Recuperamos las líneas de movimiento | ||||||||||||||||||||||||||||||||
| # por ahora directamente filtramos los que ya se procesaron | ||||||||||||||||||||||||||||||||
| move_lines = self.env["account.move.line"].search( | ||||||||||||||||||||||||||||||||
| [("move_id.exchange_reversal_id", "=", False), ("move_id.exchange_reversed_move_ids", "=", False)] | ||||||||||||||||||||||||||||||||
| [ | ||||||||||||||||||||||||||||||||
| ("move_id.exchange_reversal_id", "=", False), | ||||||||||||||||||||||||||||||||
| ("move_id.exchange_reversed_move_ids", "=", False), | ||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||
| + [("id", "in", move_line_ids)] | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
@@ -110,6 +125,7 @@ def _create_invoice_and_reconcile(self, journal): | |||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| exch_moves = partner_to_moves.get(rec.partner_id.id, self.env["account.move"]) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| exch_moves.write({"exchange_reversal_id": move.id}) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| move.action_post() | ||||||||||||||||||||||||||||||||
|
|
@@ -120,7 +136,7 @@ def _create_invoice_and_reconcile(self, journal): | |||||||||||||||||||||||||||||||
| debit_credit_note = ( | ||||||||||||||||||||||||||||||||
| self.env["account.move"] | ||||||||||||||||||||||||||||||||
| .with_context(exchange_diff_account_receivable_id=rec_account.id) | ||||||||||||||||||||||||||||||||
| .create(rec._prepare_debit_credit_note(journal=journal)) | ||||||||||||||||||||||||||||||||
| .create(rec._prepare_debit_credit_note(exch_moves, journal)) | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| if debit_credit_note.currency_id.round(debit_credit_note.amount_total) < 0: | ||||||||||||||||||||||||||||||||
| # switch to credit note if the amount is negative | ||||||||||||||||||||||||||||||||
|
|
@@ -221,15 +237,141 @@ def _get_receivable_account(self): | |||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return rec_account | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def _prepare_debit_credit_note(self, journal): | ||||||||||||||||||||||||||||||||
| def _prepare_invoice_lines_by_taxes(self, invoice_lines, account, amount): | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| Agrupa las líneas de factura por combinación de impuestos y prorratea el balance. | ||||||||||||||||||||||||||||||||
| Retorna una lista de tuplas (0, 0, vals) para crear las líneas de la nota de débito/crédito. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| self.ensure_one() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| product = self.env.company.exchange_difference_product | ||||||||||||||||||||||||||||||||
| partner = self.partner_id | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Si no hay líneas de factura, retornamos una única línea con el balance total | ||||||||||||||||||||||||||||||||
| if not invoice_lines: | ||||||||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| "account_id": account.id, | ||||||||||||||||||||||||||||||||
| "quantity": 1.0, | ||||||||||||||||||||||||||||||||
| "price_unit": self.balance, | ||||||||||||||||||||||||||||||||
| "partner_id": partner.id, | ||||||||||||||||||||||||||||||||
| "product_id": product.id, | ||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # En contexto de localización argentina, filtramos los impuestos que no son IVA | ||||||||||||||||||||||||||||||||
| # (aquellos cuyo tax_group_id no tiene l10n_ar_vat_afip_code seteado) | ||||||||||||||||||||||||||||||||
| is_argentina = self.env["account.tax.group"]._fields.get("l10n_ar_vat_afip_code") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| tax_groups = {} | ||||||||||||||||||||||||||||||||
| for line in invoice_lines: | ||||||||||||||||||||||||||||||||
| if is_argentina: | ||||||||||||||||||||||||||||||||
| # Solo consideramos impuestos de IVA (con l10n_ar_vat_afip_code seteado) | ||||||||||||||||||||||||||||||||
| taxes = line.tax_ids.filtered("tax_group_id.l10n_ar_vat_afip_code") | ||||||||||||||||||||||||||||||||
| other_taxes = line.tax_ids - taxes | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| taxes = line.tax_ids | ||||||||||||||||||||||||||||||||
| tax_key = frozenset(taxes.ids) | ||||||||||||||||||||||||||||||||
| if tax_key not in tax_groups: | ||||||||||||||||||||||||||||||||
| tax_groups[tax_key] = { | ||||||||||||||||||||||||||||||||
| "tax_ids": taxes, | ||||||||||||||||||||||||||||||||
| "subtotal": 0.0, | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if not self.wizard_id.fiscal_position_id: | ||||||||||||||||||||||||||||||||
| tax_groups[tax_key]["subtotal"] += line.price_total | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| tax_groups[tax_key]["subtotal"] += line.price_total - sum(other_taxes.mapped("amount")) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Calcular el total de subtotales para el prorrateo | ||||||||||||||||||||||||||||||||
| total_subtotal = sum(group["subtotal"] for group in tax_groups.values()) | ||||||||||||||||||||||||||||||||
| # total_subtotal = sum(invoice_lines.mapped("price_total")) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Si el total es cero, distribuimos equitativamente | ||||||||||||||||||||||||||||||||
| if total_subtotal == 0: | ||||||||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| "account_id": account.id, | ||||||||||||||||||||||||||||||||
| "quantity": 1.0, | ||||||||||||||||||||||||||||||||
| "price_unit": amount, | ||||||||||||||||||||||||||||||||
| "partner_id": partner.id, | ||||||||||||||||||||||||||||||||
| "product_id": product.id, | ||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Crear las líneas prorrateadas | ||||||||||||||||||||||||||||||||
| invoice_line_vals = [] | ||||||||||||||||||||||||||||||||
| remaining_balance = amount | ||||||||||||||||||||||||||||||||
| groups_list = list(tax_groups.values()) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| for i, group in enumerate(groups_list): | ||||||||||||||||||||||||||||||||
| if i == len(groups_list) - 1: | ||||||||||||||||||||||||||||||||
| # Última línea: usar el saldo restante para evitar diferencias de redondeo | ||||||||||||||||||||||||||||||||
| price_unit = remaining_balance | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| # Prorratear según el peso del subtotal | ||||||||||||||||||||||||||||||||
| proportion = group["subtotal"] / total_subtotal | ||||||||||||||||||||||||||||||||
| price_unit = self.wizard_id.company_id.currency_id.round(amount * proportion) | ||||||||||||||||||||||||||||||||
| remaining_balance -= price_unit | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| invoice_line_vals.append( | ||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| "account_id": account.id, | ||||||||||||||||||||||||||||||||
| "quantity": 1.0, | ||||||||||||||||||||||||||||||||
| "price_unit": price_unit, | ||||||||||||||||||||||||||||||||
| "partner_id": partner.id, | ||||||||||||||||||||||||||||||||
| "product_id": product.id, | ||||||||||||||||||||||||||||||||
| "tax_ids": [(6, 0, group["tax_ids"].ids)], | ||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return invoice_line_vals | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def _prepare_debit_credit_note(self, exch_moves, journal): | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| Retorna un diccionario con los datos para crear la nota de débito/crédito | ||||||||||||||||||||||||||||||||
| Prorratea el balance entre las diferentes combinaciones de impuestos presentes en las líneas de factura | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| self.ensure_one() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| company = self.wizard_id.company_id | ||||||||||||||||||||||||||||||||
| partner = self.partner_id | ||||||||||||||||||||||||||||||||
| account = self.env["account.move.line"]._get_exchange_account(company, self.balance) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # approach si hacemos una sola linea | ||||||||||||||||||||||||||||||||
| # partial_reconciles = self.env["account.partial.reconcile"].search( | ||||||||||||||||||||||||||||||||
| # [("exchange_move_id", "in", exch_moves.ids)] | ||||||||||||||||||||||||||||||||
| # ) | ||||||||||||||||||||||||||||||||
| # invoice_lines = (partial_reconciles.mapped("debit_move_id") + partial_reconciles.mapped("credit_move_id")).mapped("move_id").filtered(lambda move: move.is_sale_document()).mapped("invoice_line_ids") | ||||||||||||||||||||||||||||||||
| # invoice_line_vals = self._prepare_invoice_lines_by_taxes(invoice_lines, account, self.balance) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+352
to
+357
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # en este approach iteramos sobre cada exhcange diff y generamos lineas para cada grupo de impuestos | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
| # en este approach iteramos sobre cada exhcange diff y generamos lineas para cada grupo de impuestos | |
| # en este approach iteramos sobre cada exchange diff y generamos lineas para cada grupo de impuestos |
Copilot
AI
Dec 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
El operador de comparación está incorrecto. Se utiliza "=" (igualdad) cuando debería usarse "in" (pertenencia) ya que exchange_move.ids es una lista. Esto causará un error en tiempo de ejecución porque el dominio esperaría comparar un campo con un valor escalar, no con una lista.
| [("exchange_move_id", "=", exchange_move.ids)] | |
| [("exchange_move_id", "in", exchange_move.ids)] |
Copilot
AI
Dec 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Se está ejecutando una búsqueda (search) dentro de un bucle for, lo cual es ineficiente. Esto genera N consultas a la base de datos (una por cada exchange_move). Sería más eficiente realizar una única búsqueda con el dominio [("exchange_move_id", "in", exch_moves.ids)] antes del bucle y luego iterar sobre los resultados agrupados, o usar un grouped/mapped si es posible.
| for exchange_move in exch_moves: | |
| partial_reconcile = self.env["account.partial.reconcile"].search( | |
| [("exchange_move_id", "=", exchange_move.ids)] | |
| ) | |
| # Buscar todos los partial_reconcile de una sola vez | |
| all_partial_reconciles = self.env["account.partial.reconcile"].search( | |
| [("exchange_move_id", "in", exch_moves.ids)] | |
| ) | |
| # Agrupar por exchange_move_id | |
| partials_by_exchange = {} | |
| for pr in all_partial_reconciles: | |
| exchange_id = pr.exchange_move_id.id | |
| partials_by_exchange.setdefault(exchange_id, self.env["account.partial.reconcile"].browse()). |= pr | |
| for exchange_move in exch_moves: | |
| partial_reconcile = partials_by_exchange.get(exchange_move.id, self.env["account.partial.reconcile"].browse()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,8 @@ | |
| <form> | ||
| <group> | ||
| <field name="journal_id"/> | ||
| <field name="fiscal_position"/> | ||
|
||
| <field name="fiscal_position_id" invisible="fiscal_position != 'manual'"/> | ||
| <field name="line_ids"> | ||
| <list editable="bottom" create="false" edit="false"> | ||
| <field name="partner_id"/> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Este código comentado debe eliminarse antes del merge. Si se consideró este cálculo alternativo, la decisión de usar el enfoque actual debería estar documentada en un comentario conciso o en el historial de commits.