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

1from odoo import Command, _, api, fields, models 

2from odoo.exceptions import ValidationError 

3 

4 

5class AccountPayment(models.Model): 

6 _inherit = "account.payment" 

7 

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") 

133 

134 open_move_line_ids = fields.One2many(related="move_id.open_move_line_ids") 

135 

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 

139 

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 

145 

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 ) 

152 

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!")) 

161 

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() 

169 

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 

184 

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) 

190 

191 ############################## 

192 # desde modelo account.payment 

193 ############################## 

194 

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) 

214 

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() 

219 

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 ) 

238 

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 

246 

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 

254 

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 

268 

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 

283 

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() 

288 

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 

306 

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() 

324 

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 ) 

344 

345 res = super()._prepare_move_lines_per_type(write_off_line_vals=write_off_line_vals, force_balance=force_balance) 

346 

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 

354 

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 

357 

358 liquidity_lines = res.get("liquidity_lines", []) 

359 counterpart_lines = res.get("counterpart_lines", []) 

360 

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 

367 

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 

377 

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 ) 

385 

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 ) 

403 

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() 

410 

411 #################################### 

412 # desde modelo account.payment.group 

413 #################################### 

414 

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 

452 

453 (self - stored_payments).matched_move_line_ids = False 

454 

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 

473 

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 

486 

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 

491 

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 

509 

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 

515 

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) 

527 

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 

533 

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 

538 

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 

549 

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() 

554 

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 

562 

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) 

567 

568 with_payment_pro = self._get_filter_payments(records, ["direct_debit_mandate_id"]) 

569 

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() 

574 

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 ) 

579 

580 for field in extra_fields: 

581 if records._fields.get(field): 

582 records = records.filtered(lambda x, f=field: not getattr(x, f)) 

583 

584 return records 

585 

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 

602 

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 ] 

609 

610 def action_add_all(self): 

611 self.with_context(active_ids=False)._add_all() 

612 

613 def remove_all(self): 

614 self.to_pay_move_line_ids = False 

615 

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 ) 

629 

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 

644 

645 def action_post(self): 

646 res = super().action_post() 

647 self._reconcile_after_post() 

648 return res 

649 

650 def _get_mached_payment(self): 

651 return self.ids 

652 

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) 

661 

662 @api.depends("journal_id") 

663 def _compute_available_partner_bank_ids(self): 

664 super()._compute_available_partner_bank_ids()