Coverage for ingadhoc-account-payment / account_payment_pro / models / account_payment.py: 52%
279 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 19:54 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 19:54 +0000
1from odoo import Command, _, api, fields, models
2from odoo.exceptions import ValidationError
5class AccountPayment(models.Model):
6 _inherit = "account.payment"
8 # TODO. por ahora por simplicidad y para mantener lo que teniamos hasta 16 hacemos que todos los campos de deuda y demas
9 # sean en moneda de la compañia (antes era en moneda del pay group que siempre era la moneda de la cia), currency_field='company_currency_id',
10 # desde account_payment_group, modelo account.payment
11 amount_company_currency = fields.Monetary(
12 string="Amount on Company Currency",
13 compute="_compute_amount_company_currency",
14 inverse="_inverse_amount_company_currency",
15 currency_field="company_currency_id",
16 )
17 counterpart_currency_amount = fields.Monetary(
18 currency_field="counterpart_currency_id",
19 compute="_compute_counterpart_currency_amount",
20 )
21 counterpart_currency_id = fields.Many2one(
22 "res.currency",
23 compute="_compute_counterpart_currency_id",
24 store=True,
25 readonly=False,
26 precompute=True,
27 )
28 counterpart_exchange_rate = fields.Float(
29 readonly=False,
30 compute="_compute_counterpart_exchange_rate",
31 store=True,
32 precompute=True,
33 copy=False,
34 digits=0,
35 )
36 other_currency = fields.Boolean(
37 compute="_compute_other_currency",
38 )
39 journal_currency_id = fields.Many2one(related="journal_id.currency_id", string="Journal Currency")
40 destination_journal_currency_id = fields.Many2one(
41 related="destination_journal_id.currency_id",
42 string="Destination Journal Currency",
43 )
44 force_amount_company_currency = fields.Monetary(
45 string="Forced Amount on Company Currency",
46 currency_field="company_currency_id",
47 copy=False,
48 )
49 exchange_rate = fields.Float(
50 compute="_compute_exchange_rate",
51 # readonly=False,
52 # inverse='_inverse_exchange_rate',
53 # digits=(16, 4),
54 )
55 commercial_partner_id = fields.Many2one(related="partner_id.commercial_partner_id")
56 # TODO deberiamos ver de borrar esto. el tema es que los campos nativos de odoo no refelajn importe en moenda de cia
57 # hasta en tanto se guarde el payment (en parte porque vienen heredados desde el move)
58 # no solo eso si no que tmb viene pisado en los payments y computa solo si hay liquidity lines pero no cuentas de
59 # outstanding
60 # TODO de hecho tenemos que analizar si queremos mantener todo lo de matched y demas en moneda de cia o moneda de
61 # pago
62 amount_company_currency_signed_pro = fields.Monetary(
63 currency_field="company_currency_id",
64 compute="_compute_amount_company_currency_signed_pro",
65 )
66 payment_total = fields.Monetary(
67 compute="_compute_payment_total",
68 tracking=True,
69 currency_field="company_currency_id",
70 )
71 available_journal_ids = fields.Many2many(comodel_name="account.journal", compute="_compute_available_journal_ids")
72 # desde account_payment_group, modelo account.payment.group
73 matched_amount = fields.Monetary(
74 compute="_compute_matched_amounts",
75 currency_field="company_currency_id",
76 )
77 unmatched_amount = fields.Monetary(
78 compute="_compute_matched_amounts",
79 currency_field="company_currency_id",
80 )
81 selected_debt = fields.Monetary(
82 compute="_compute_selected_debt",
83 currency_field="company_currency_id",
84 )
85 unreconciled_amount = fields.Monetary(
86 string="Adjustment / Advance",
87 currency_field="company_currency_id",
88 )
89 # reconciled_amount = fields.Monetary(compute='_compute_amounts')
90 to_pay_amount = fields.Monetary(
91 compute="_compute_to_pay_amount",
92 inverse="_inverse_to_pay_amount",
93 readonly=True,
94 tracking=True,
95 currency_field="company_currency_id",
96 )
97 has_outstanding = fields.Boolean(
98 compute="_compute_has_outstanding",
99 )
100 to_pay_move_line_ids = fields.Many2many(
101 "account.move.line",
102 "account_move_line_payment_to_pay_rel",
103 "payment_id",
104 "to_pay_line_id",
105 string="To Pay Lines",
106 compute="_compute_to_pay_move_lines",
107 store=True,
108 help="This lines are the ones the user has selected to be paid.",
109 copy=False,
110 readonly=False,
111 check_company=True,
112 )
113 matched_move_line_ids = fields.Many2many(
114 "account.move.line",
115 compute="_compute_matched_move_line_ids",
116 help="Lines that has been matched to payments, only available after payment validation",
117 )
118 write_off_type_id = fields.Many2one(
119 "account.write_off.type",
120 check_company=True,
121 )
122 write_off_amount = fields.Monetary(
123 currency_field="company_currency_id",
124 )
125 payment_difference = fields.Monetary(
126 compute="_compute_payment_difference",
127 string="Payments Difference",
128 currency_field="company_currency_id",
129 help="Difference between 'To Pay Amount' and 'Payment Total'",
130 )
131 write_off_available = fields.Boolean(compute="_compute_write_off_available")
132 use_payment_pro = fields.Boolean(compute="_compute_use_payment_pro")
134 open_move_line_ids = fields.One2many(related="move_id.open_move_line_ids")
136 @api.depends("journal_id")
137 def _compute_counterpart_currency_id(self):
138 self.filtered(lambda x: x.journal_id.currency_id == x.counterpart_currency_id).counterpart_currency_id = False
140 @api.depends("company_id", "outstanding_account_id")
141 def _compute_use_payment_pro(self):
142 payment_with_pro = self.filtered(lambda x: x.company_id.use_payment_pro and x.outstanding_account_id)
143 payment_with_pro.use_payment_pro = True
144 (self - payment_with_pro).use_payment_pro = False
146 @api.depends("company_id")
147 def _compute_write_off_available(self):
148 for rec in self:
149 rec.write_off_available = bool(
150 rec.env["account.write_off.type"].search([("company_ids", "=", rec.company_id.id)], limit=1)
151 )
153 @api.constrains("to_pay_move_line_ids", "state")
154 def _check_to_pay_lines_account(self):
155 """TODO ver si esto tmb lo llevamos a la UI y lo mostramos como un warning.
156 tmb podemos dar mas info al usuario en el error"""
157 for rec in self.filtered(lambda x: x.partner_id and x.state != "draft"):
158 accounts = rec.to_pay_move_line_ids.mapped("account_id")
159 if len(accounts) > 1: 159 ↛ 160line 159 didn't jump to line 160 because the condition on line 159 was never true
160 raise ValidationError(_("To Pay Lines must be of the same account!"))
162 def action_draft(self):
163 # Seteamos posted_before en true para que nos permita pasar a borrador el pago y poder realizar cambio sobre el mismo
164 # Nos salteamos la siguente validacion
165 # https://github.com/odoo/odoo/blob/b6b90636938ae961c339807ea893cabdede9f549/addons/account/models/account_move.py#L2474
166 if self.company_id.use_payment_pro: 166 ↛ 168line 166 didn't jump to line 168 because the condition on line 166 was always true
167 self.move_id.posted_before = False
168 super().action_draft()
170 def write(self, vals):
171 for rec in self:
172 if rec.company_id.use_payment_pro or ( 172 ↛ 171line 172 didn't jump to line 171 because the condition on line 172 was always true
173 "company_id" in vals and rec.env["res.company"].browse(vals["company_id"]).use_payment_pro
174 ):
175 # Lo siguiente lo evaluamos para evitar la validacion de odoo de
176 # https://github.com/odoo/odoo/blob/b6b90636938ae961c339807ea893cabdede9f549/addons/account/models/account_move.py#L2476
177 # y permitirnos realizar la modificacion del journal.
178 if "journal_id" in vals and rec.journal_id.id != vals["journal_id"]: 178 ↛ 183line 178 didn't jump to line 183 because the condition on line 178 was never true
179 # Lo agregamos a este cambio por el siguiente campo agregado en
180 # https://github.com/odoo/odoo/commit/da49c9268b3876a0482a5593379c02418e806b61
181 # De esta forma evitamos el error de asignar un sequence_number de forma random que ademas se estaba recomputando nuevamente,
182 # volviendo a su valor original.
183 rec.move_id.quick_edit_mode = True
185 # Lo siguiente lo agregamos para primero obligarnos a cambiar el journal_id y no la company_id. Una vez cambiado el journal_id
186 # la company_id se cambia correctamente.
187 if "company_id" in vals and "journal_id" in vals: 187 ↛ 188line 187 didn't jump to line 188 because the condition on line 187 was never true
188 rec.move_id.journal_id = vals["journal_id"]
189 return super().write(vals)
191 ##############################
192 # desde modelo account.payment
193 ##############################
195 # TODO re-evaluar. tal vez mejor esto en un modulo multicompany?
196 # @api.depends('payment_type')
197 # def _compute_available_journal_ids(self):
198 # """
199 # Este metodo odoo lo agrega en v16
200 # Igualmente nosotros lo modificamos acá para que funcione con esta logica:
201 # a) desde transferencias permitir elegir cualquier diario ya que no se selecciona compañía
202 # b) desde grupos de pagos solo permitir elegir diarios de la misma compañía
203 # NOTA: como ademas estamos mandando en el contexto del company_id, tal vez podriamos evitar pisar este metodo
204 # y ande bien en v16 para que las lineas de pago de un payment group usen la compañia correspondiente, pero
205 # lo que faltaria es hacer posible en las transferencias seleccionar una compañia distinta a la por defecto
206 # """
207 # journals = self.env['account.journal'].search([
208 # ('company_id', 'in', self.env.companies.ids), ('type', 'in', ('bank', 'cash'))
209 # ])
210 # for pay in self:
211 # filtered_domain = [('inbound_payment_method_line_ids', '!=', False)] if \
212 # pay.payment_type == 'inbound' else [('outbound_payment_method_line_ids', '!=', False)]
213 # pay.available_journal_ids = journals.filtered_domain(filtered_domain)
215 # agreamos depends de company para que re calcule los diarios disponibles
216 @api.depends("company_id")
217 def _compute_available_journal_ids(self):
218 super()._compute_available_journal_ids()
220 @api.depends(
221 "currency_id",
222 "company_currency_id",
223 "is_internal_transfer",
224 "destination_journal_currency_id",
225 )
226 def _compute_other_currency(self):
227 for rec in self:
228 company_currency = rec.company_currency_id
229 rec.other_currency = bool(
230 (company_currency and rec.currency_id and company_currency != rec.currency_id)
231 or (
232 rec.is_internal_transfer
233 and company_currency
234 and rec.destination_journal_currency_id
235 and company_currency != rec.destination_journal_currency_id
236 )
237 )
239 @api.depends("amount", "other_currency", "amount_company_currency")
240 def _compute_exchange_rate(self):
241 for rec in self:
242 if rec.other_currency: 242 ↛ 245line 242 didn't jump to line 245 because the condition on line 242 was always true
243 rec.exchange_rate = rec.amount and (rec.amount_company_currency / rec.amount) or 0.0
244 else:
245 rec.exchange_rate = False
247 @api.depends("payment_total", "counterpart_exchange_rate")
248 def _compute_counterpart_currency_amount(self):
249 for rec in self:
250 if rec.counterpart_currency_id and rec.counterpart_exchange_rate:
251 rec.counterpart_currency_amount = rec.payment_total / rec.counterpart_exchange_rate
252 else:
253 rec.counterpart_currency_amount = False
255 @api.depends("counterpart_currency_id", "company_id", "date")
256 def _compute_counterpart_exchange_rate(self):
257 for rec in self:
258 if rec.counterpart_currency_id: 258 ↛ 259line 258 didn't jump to line 259 because the condition on line 258 was never true
259 rate = self.env["res.currency"]._get_conversion_rate(
260 from_currency=rec.company_currency_id,
261 to_currency=rec.counterpart_currency_id,
262 company=rec.company_id,
263 date=rec.date,
264 )
265 rec.counterpart_exchange_rate = 1 / rate if rate else False
266 else:
267 rec.counterpart_exchange_rate = False
269 # this onchange is necesary because odoo, sometimes, re-compute
270 # and overwrites amount_company_currency. That happends due to an issue
271 # with rounding of amount field (amount field is not change but due to
272 # rouding odoo believes amount has changed)
273 @api.onchange("amount_company_currency")
274 def _inverse_amount_company_currency(self):
275 for rec in self:
276 if rec.other_currency and rec.amount_company_currency != rec.currency_id._convert( 276 ↛ 279line 276 didn't jump to line 279 because the condition on line 276 was never true
277 rec.amount, rec.company_id.currency_id, rec.company_id, rec.date
278 ):
279 force_amount_company_currency = rec.amount_company_currency
280 else:
281 force_amount_company_currency = False
282 rec.force_amount_company_currency = force_amount_company_currency
284 @api.onchange("company_id")
285 def _onchange_company_id(self):
286 if self._origin.company_id and self.company_id != self._origin.company_id and self.state == "draft":
287 self.remove_all()
289 @api.depends("amount", "other_currency", "force_amount_company_currency")
290 def _compute_amount_company_currency(self):
291 """
292 * Si las monedas son iguales devuelve 1
293 * si no, si hay force_amount_company_currency, devuelve ese valor
294 * sino, devuelve el amount convertido a la moneda de la cia
295 """
296 for rec in self:
297 if not rec.other_currency: 297 ↛ 298line 297 didn't jump to line 298 because the condition on line 297 was never true
298 amount_company_currency = rec.amount
299 elif rec.force_amount_company_currency: 299 ↛ 300line 299 didn't jump to line 300 because the condition on line 299 was never true
300 amount_company_currency = rec.force_amount_company_currency
301 else:
302 amount_company_currency = rec.currency_id._convert(
303 rec.amount, rec.company_id.currency_id, rec.company_id, rec.date
304 )
305 rec.amount_company_currency = amount_company_currency
307 @api.depends("to_pay_move_line_ids")
308 def _compute_destination_account_id(self):
309 """
310 If we are paying a payment gorup with paylines, we use account
311 of lines that are going to be paid
312 """
313 for rec in self:
314 to_pay_account = rec.to_pay_move_line_ids.mapped("account_id")
315 if to_pay_account: 315 ↛ 323line 315 didn't jump to line 323 because the condition on line 315 was always true
316 # tomamos la primer si hay mas de una, luego en el post si la deuda se intenta conciliar odoo
317 # devuelve error. No lo protegemos acá por estas razones:
318 # 1. el boton add all no se podria usar porque ya hace un write y el usuario deberia elegir a mano los apuntes
319 # 2. le vamos a dar error al usuario en algunos casos sin que sea necesario ya que luego, si el importe es menor
320 # no llega a intentar conciliarse con est epago
321 rec.destination_account_id = to_pay_account[0]
322 else:
323 super(AccountPayment, rec)._compute_destination_account_id()
325 def _prepare_move_lines_per_type(self, write_off_line_vals=None, force_balance=None):
326 # TODO: elimino los write_off_line_vals porque los regenero tanto aca
327 # como en retenciones. esto puede generar problemas
328 if self.company_id.use_payment_pro: 328 ↛ 345line 328 didn't jump to line 345 because the condition on line 328 was always true
329 write_off_line_vals = []
330 if self.write_off_amount: 330 ↛ 331line 330 didn't jump to line 331 because the condition on line 330 was never true
331 amount = self.write_off_amount if self.payment_type == "inbound" else -self.write_off_amount
332 write_off_line_vals.append(
333 {
334 "name": self.write_off_type_id.label or self.write_off_type_id.name,
335 "account_id": self.write_off_type_id.account_id.id,
336 "partner_id": self.partner_id.id,
337 "currency_id": self.currency_id.id,
338 "amount_currency": amount,
339 "balance": self.currency_id._convert(
340 amount, self.company_id.currency_id, self.company_id, self.date
341 ),
342 }
343 )
345 res = super()._prepare_move_lines_per_type(write_off_line_vals=write_off_line_vals, force_balance=force_balance)
347 if self.company_id.use_payment_pro and write_off_line_vals and not res.get("write_off_lines"): 347 ↛ 348line 347 didn't jump to line 348 because the condition on line 347 was never true
348 res["write_off_lines"] = write_off_line_vals
349 w_balance = sum(line["balance"] for line in write_off_line_vals)
350 w_amount_currency = sum(line["amount_currency"] for line in write_off_line_vals)
351 if res.get("counterpart_lines"):
352 res["counterpart_lines"][0]["balance"] -= w_balance
353 res["counterpart_lines"][0]["amount_currency"] -= w_amount_currency
355 if not self.company_id.use_payment_pro and not self.is_internal_transfer: 355 ↛ 356line 355 didn't jump to line 356 because the condition on line 355 was never true
356 return res
358 liquidity_lines = res.get("liquidity_lines", [])
359 counterpart_lines = res.get("counterpart_lines", [])
361 if self.force_amount_company_currency and liquidity_lines and counterpart_lines: 361 ↛ 362line 361 didn't jump to line 362 because the condition on line 361 was never true
362 sign = 1 if liquidity_lines[0]["balance"] > 0 else -1
363 new_balance = sign * self.force_amount_company_currency
364 difference = new_balance - liquidity_lines[0]["balance"]
365 liquidity_lines[0]["balance"] = new_balance
366 counterpart_lines[0]["balance"] -= difference
368 if self._use_counterpart_currency() and counterpart_lines: 368 ↛ 369line 368 didn't jump to line 369 because the condition on line 368 was never true
369 sign = 1 if counterpart_lines[0].get("amount_currency", 1) >= 0 else -1
370 counterpart_lines[0].update(
371 {
372 "currency_id": self.counterpart_currency_id.id,
373 "amount_currency": sign * abs(self.counterpart_currency_amount),
374 }
375 )
376 return res
378 def _use_counterpart_currency(self):
379 self.ensure_one()
380 return (
381 self.counterpart_currency_id
382 and self.currency_id == self.company_id.currency_id
383 and self.counterpart_currency_id != self.currency_id
384 )
386 @api.model
387 def _get_trigger_fields_to_synchronize(self):
388 res = super()._get_trigger_fields_to_synchronize()
389 # si bien es un metodo api.model usamos este hack para chequear si es la creacion de un payment que termina
390 # triggereando un write y luego llamando a este metodo y dando error, por ahora no encontramos una mejor forma
391 # esto esta ligado de alguna manera a un llamado que se hace dos veces por "culpa" del método
392 # "_inverse_amount_company_currency". Si bien no es elegante para todas las pruebas que hicimos funcionó bien.
393 if self.mapped("move_id"): 393 ↛ 399line 393 didn't jump to line 399 because the condition on line 393 was always true
394 res = res + (
395 "force_amount_company_currency",
396 "counterpart_exchange_rate",
397 "counterpart_currency_id",
398 )
399 return res + (
400 "write_off_amount",
401 "write_off_type_id",
402 )
404 def _create_paired_internal_transfer_payment(self):
405 for rec in self: 405 ↛ 406line 405 didn't jump to line 406 because the loop on line 405 never started
406 super(
407 AccountPayment,
408 rec.with_context(default_force_amount_company_currency=rec.force_amount_company_currency),
409 )._create_paired_internal_transfer_payment()
411 ####################################
412 # desde modelo account.payment.group
413 ####################################
415 @api.depends("move_id.line_ids")
416 def _compute_matched_move_line_ids(self):
417 """
418 Las partial reconcile vinculan dos apuntes con credit_move_id y
419 debit_move_id.
420 Buscamos primeros todas las que tienen en credit_move_id algun apunte
421 de los que se genero con un pago, etnonces la contrapartida
422 (debit_move_id), son cosas que se pagaron con este pago. Repetimos
423 al revz (debit_move_id vs credit_move_id)
424 El depends en account de odoo para casos similares usa
425 @api.depends('move_id.line_ids.matched_debit_ids', 'move_id.line_ids.matched_credit_ids')
426 Aca preferimos mantener move_id.line_ids por cuestiones de performace.
427 Si _compute_matched_move_line_ids fuera stored cambiariamos el depend
428 TODO v18, ver si podemos reutilizar reconciled_invoice_ids y/o reconciled_bill_ids
429 al menos podremos re-usar codigo sql para optimizar performance
430 Por ahora no lo estamos usando porque el actual código de odoo solo muestra facturas o algo así (por ej. si hay
431 conciliacion de deuda de un asiento normal no lo muestra)
432 """
433 stored_payments = self.filtered("id")
434 for rec in stored_payments:
435 payment_lines = rec.move_id.line_ids.filtered(
436 lambda x: x.account_type in self._get_valid_payment_account_types()
437 )
438 debit_moves = payment_lines.mapped("matched_debit_ids.debit_move_id")
439 credit_moves = payment_lines.mapped("matched_credit_ids.credit_move_id")
440 debit_lines_sorted = debit_moves.filtered(lambda x: x.date_maturity != False).sorted(
441 key=lambda x: (x.date_maturity, x.move_id.name)
442 )
443 credit_lines_sorted = credit_moves.filtered(lambda x: x.date_maturity != False).sorted(
444 key=lambda x: (x.date_maturity, x.move_id.name)
445 )
446 debit_lines_without_date_maturity = debit_moves - debit_lines_sorted
447 credit_lines_without_date_maturity = credit_moves - credit_lines_sorted
448 rec.matched_move_line_ids = (
449 (debit_lines_sorted + debit_lines_without_date_maturity)
450 | (credit_lines_sorted + credit_lines_without_date_maturity)
451 ) - payment_lines
453 (self - stored_payments).matched_move_line_ids = False
455 @api.depends(
456 "state",
457 "amount_company_currency_signed_pro",
458 )
459 def _compute_matched_amounts(self):
460 for rec in self:
461 rec.matched_amount = 0.0
462 rec.unmatched_amount = 0.0
463 if rec.state == "draft":
464 continue
465 # damos vuelta signo porque el payments_amount tmb lo da vuelta,
466 # en realidad porque siempre es positivo y se define en funcion
467 # a si es pago entrante o saliente
468 sign = rec.payment_type == "outbound" and -1.0 or 1.0
469 rec.matched_amount = sign * sum(
470 rec.matched_move_line_ids.with_context(matched_payment_ids=rec.ids).mapped("payment_matched_amount")
471 )
472 rec.unmatched_amount = abs(rec.payment_total) - rec.matched_amount
474 @api.depends("to_pay_move_line_ids")
475 def _compute_has_outstanding(self):
476 for rec in self:
477 rec.has_outstanding = False
478 if rec.state != "draft":
479 continue
480 if rec.partner_type == "supplier":
481 lines = rec.to_pay_move_line_ids.filtered(lambda x: x.amount_residual > 0.0)
482 else:
483 lines = rec.to_pay_move_line_ids.filtered(lambda x: x.amount_residual < 0.0)
484 if len(lines) != 0:
485 rec.has_outstanding = True
487 @api.depends("amount_company_currency_signed_pro", "write_off_amount")
488 def _compute_payment_total(self):
489 for rec in self:
490 rec.payment_total = rec.amount_company_currency_signed_pro + rec.write_off_amount
492 @api.depends("amount_company_currency", "payment_type")
493 def _compute_amount_company_currency_signed_pro(self):
494 """new field similar to amount_company_currency_signed but:
495 1. is positive for payments to suppliers
496 2. we use the new field amount_company_currency instead of amount_total_signed, because amount_total_signed is
497 computed only after saving
498 We use l10n_ar prefix because this is a pseudo backport of future l10n_ar_withholding module"""
499 for payment in self:
500 if (
501 payment.payment_type == "outbound"
502 and payment.partner_type == "customer"
503 or payment.payment_type == "inbound"
504 and payment.partner_type == "supplier"
505 ):
506 payment.amount_company_currency_signed_pro = -payment.amount_company_currency
507 else:
508 payment.amount_company_currency_signed_pro = payment.amount_company_currency
510 # TODO revisar depends
511 @api.depends("payment_total", "to_pay_amount", "amount_company_currency_signed_pro")
512 def _compute_payment_difference(self):
513 for rec in self:
514 rec.payment_difference = rec.to_pay_amount - rec.payment_total
516 # En el pasado se contaba con to_pay_move_line_ids.amount_residual dentro de los depends, y no deberiamos por cuestiones de performance, ya que ademas no era necesario
517 @api.depends("to_pay_move_line_ids")
518 def _compute_selected_debt(self):
519 for rec in self:
520 # factor = 1
521 amount_residual = sum(rec.to_pay_move_line_ids._origin.mapped("amount_residual"))
522 if self.env.context.get("pay_now") and amount_residual != sum(
523 rec.to_pay_move_line_ids._origin.mapped("amount_residual_currency")
524 ):
525 amount_residual = sum(rec.to_pay_move_line_ids._origin.mapped("amount_residual_currency"))
526 rec.selected_debt = amount_residual * (-1.0 if rec.partner_type == "supplier" else 1.0)
528 # TODO error en la creacion de un payment desde el menu?
529 # if rec.payment_type == 'outbound' and rec.partner_type == 'customer' or \
530 # rec.payment_type == 'inbound' and rec.partner_type == 'supplier':
531 # factor = -1
532 # rec.selected_debt = sum(rec.to_pay_move_line_ids._origin.mapped('amount_residual')) * factor
534 @api.depends("selected_debt", "unreconciled_amount")
535 def _compute_to_pay_amount(self):
536 for rec in self:
537 rec.to_pay_amount = rec.selected_debt + rec.unreconciled_amount
539 @api.onchange("to_pay_amount")
540 def _inverse_to_pay_amount(self):
541 for rec in self:
542 # agregamos este chequeo porque cuando estamos creando un pago nuevo se llama este inverse siempre
543 # y si el monto no cambio no queremos que trigeree re computo de retenciones
544 # (por el depends de _compute_base_amount)
545 if rec.currency_id and not rec.currency_id.is_zero(
546 rec.unreconciled_amount - (rec.to_pay_amount - rec.selected_debt)
547 ):
548 rec.unreconciled_amount = rec.to_pay_amount - rec.selected_debt
550 @api.onchange("company_id")
551 def _onchange_company_id(self):
552 if self._origin.company_id and self.company_id != self._origin.company_id and self.state == "draft":
553 self.remove_all()
555 # We dont set 'is_internal_transfer' as a dependencies as it could leed to recompute to_pay_move_line_ids
556 @api.depends("partner_id", "partner_type", "company_id")
557 def _compute_to_pay_move_lines(self):
558 # TODO ?
559 # # if payment group is being created from a payment we dont want to compute to_pay_move_lines
560 # if self.env.context.get('created_automatically'):
561 # return
563 # Se recomputan las lienas solo si la deuda que esta seleccionada solo si
564 # cambio el partner, compania o partner_type
565 records = self.filtered(lambda x: x.state == "draft")
566 internal_transfers = records.filtered(lambda x: x.is_internal_transfer)
568 with_payment_pro = self._get_filter_payments(records, ["direct_debit_mandate_id"])
570 if internal_transfers or not self.env.context.get("pay_now"):
571 ((internal_transfers or self) - with_payment_pro).to_pay_move_line_ids = [Command.clear()]
572 for rec in with_payment_pro:
573 rec._add_all()
575 def _get_filter_payments(self, records, extra_fields):
576 records = records.filtered(
577 lambda x: x.company_id.use_payment_pro and not x.is_internal_transfer and not x.payment_transaction_id
578 )
580 for field in extra_fields:
581 if records._fields.get(field):
582 records = records.filtered(lambda x, f=field: not getattr(x, f))
584 return records
586 def _get_to_pay_move_lines_domain(self):
587 self.ensure_one()
588 domain = [
589 ("partner_id", "=", self.partner_id.commercial_partner_id.id),
590 ("company_id", "=", self.company_id.id),
591 ("move_id.state", "=", "posted"),
592 ("account_id.reconcile", "=", True),
593 ("reconciled", "=", False),
594 ("full_reconcile_id", "=", False),
595 (
596 "account_id.account_type",
597 "=",
598 "asset_receivable" if self.partner_type == "customer" else "liability_payable",
599 ),
600 ]
601 return domain
603 def _add_all(self):
604 for rec in self:
605 rec.to_pay_move_line_ids = [
606 Command.clear(),
607 Command.set(self.env["account.move.line"].search(rec._get_to_pay_move_lines_domain()).ids),
608 ]
610 def action_add_all(self):
611 self.with_context(active_ids=False)._add_all()
613 def remove_all(self):
614 self.to_pay_move_line_ids = False
616 @api.constrains("partner_id", "to_pay_move_line_ids")
617 def check_to_pay_lines(self):
618 for rec in self:
619 to_pay_partners = rec.to_pay_move_line_ids.mapped("partner_id")
620 if len(to_pay_partners) > 1: 620 ↛ 621line 620 didn't jump to line 621 because the condition on line 620 was never true
621 raise ValidationError(_("All to pay lines must be of the same partner"))
622 if len(rec.to_pay_move_line_ids.mapped("company_id")) > 1: 622 ↛ 623line 622 didn't jump to line 623 because the condition on line 622 was never true
623 raise ValidationError(_("You can't create payments for entries belonging to different companies."))
624 if to_pay_partners and to_pay_partners != rec.partner_id.commercial_partner_id: 624 ↛ 625line 624 didn't jump to line 625 because the condition on line 624 was never true
625 raise ValidationError(
626 _("Payment is for partner %s but payment lines are of partner %s")
627 % (rec.partner_id.name, to_pay_partners.name)
628 )
630 def _reconcile_after_post(self):
631 for rec in self.filtered(lambda x: x.company_id.use_payment_pro and not x.is_internal_transfer):
632 counterpart_aml = rec.mapped("move_id.line_ids").filtered(
633 lambda r: not r.reconciled and r.account_id.account_type in self._get_valid_payment_account_types()
634 )
635 debt_aml = rec.to_pay_move_line_ids.filtered(lambda r: not r.reconciled)
636 if counterpart_aml and debt_aml: 636 ↛ 631line 636 didn't jump to line 631 because the condition on line 636 was always true
637 (counterpart_aml + (debt_aml)).reconcile()
638 # Lo sacamos ya que no es correcto de odoo cuando se deslinkea el pago
639 # o se linkea por otro lado el pago no lo suma. Decidimos dejarlo por si surge la necesidad
640 # Si surge la necesidad habria que tratar de que lo de odoo nativo funcione
641 # if rec.company_id.use_payment_pro:
642 # for invoices in (rec.reconciled_invoice_ids + rec.reconciled_bill_ids):
643 # invoices.matched_payment_ids += rec
645 def action_post(self):
646 res = super().action_post()
647 self._reconcile_after_post()
648 return res
650 def _get_mached_payment(self):
651 return self.ids
653 # --- ORM METHODS--- #
654 def web_read(self, specification):
655 fields_to_read = list(specification) or ["id"]
656 if "matched_move_line_ids" in fields_to_read and "context" in specification["matched_move_line_ids"]:
657 specification["matched_move_line_ids"]["context"].update(
658 {"matched_payment_ids": self._get_mached_payment()}
659 )
660 return super().web_read(specification)
662 @api.depends("journal_id")
663 def _compute_available_partner_bank_ids(self):
664 super()._compute_available_partner_bank_ids()