account_payment_register.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. # -*- coding: utf-8 -*-
  2. from collections import defaultdict
  3. from odoo import models, fields, api, _
  4. from odoo.exceptions import UserError
  5. from odoo.tools import frozendict
  6. class AccountPaymentRegister(models.TransientModel):
  7. _name = 'account.payment.register'
  8. _description = 'Register Payment'
  9. # == Business fields ==
  10. payment_date = fields.Date(string="Payment Date", required=True,
  11. default=fields.Date.context_today)
  12. amount = fields.Monetary(currency_field='currency_id', store=True, readonly=False,
  13. compute='_compute_amount')
  14. hide_writeoff_section = fields.Boolean(compute="_compute_hide_writeoff_section")
  15. communication = fields.Char(string="Memo", store=True, readonly=False,
  16. compute='_compute_communication')
  17. group_payment = fields.Boolean(string="Group Payments", store=True, readonly=False,
  18. compute='_compute_group_payment',
  19. help="Only one payment will be created by partner (bank), instead of one per bill.")
  20. early_payment_discount_mode = fields.Boolean(compute='_compute_early_payment_discount_mode')
  21. currency_id = fields.Many2one(
  22. comodel_name='res.currency',
  23. string='Currency',
  24. compute='_compute_currency_id', store=True, readonly=False, precompute=True,
  25. help="The payment's currency.")
  26. journal_id = fields.Many2one(
  27. comodel_name='account.journal',
  28. compute='_compute_journal_id', store=True, readonly=False, precompute=True,
  29. domain="[('id', 'in', available_journal_ids)]")
  30. available_journal_ids = fields.Many2many(
  31. comodel_name='account.journal',
  32. compute='_compute_available_journal_ids'
  33. )
  34. available_partner_bank_ids = fields.Many2many(
  35. comodel_name='res.partner.bank',
  36. compute='_compute_available_partner_bank_ids',
  37. )
  38. partner_bank_id = fields.Many2one(
  39. comodel_name='res.partner.bank',
  40. string="Recipient Bank Account",
  41. readonly=False,
  42. store=True,
  43. compute='_compute_partner_bank_id',
  44. domain="[('id', 'in', available_partner_bank_ids)]",
  45. )
  46. company_currency_id = fields.Many2one('res.currency', string="Company Currency",
  47. related='company_id.currency_id')
  48. # == Fields given through the context ==
  49. line_ids = fields.Many2many('account.move.line', 'account_payment_register_move_line_rel', 'wizard_id', 'line_id',
  50. string="Journal items", readonly=True, copy=False,)
  51. payment_type = fields.Selection([
  52. ('outbound', 'Send Money'),
  53. ('inbound', 'Receive Money'),
  54. ], string='Payment Type', store=True, copy=False,
  55. compute='_compute_from_lines')
  56. partner_type = fields.Selection([
  57. ('customer', 'Customer'),
  58. ('supplier', 'Vendor'),
  59. ], store=True, copy=False,
  60. compute='_compute_from_lines')
  61. source_amount = fields.Monetary(
  62. string="Amount to Pay (company currency)", store=True, copy=False,
  63. currency_field='company_currency_id',
  64. compute='_compute_from_lines')
  65. source_amount_currency = fields.Monetary(
  66. string="Amount to Pay (foreign currency)", store=True, copy=False,
  67. currency_field='source_currency_id',
  68. compute='_compute_from_lines')
  69. source_currency_id = fields.Many2one('res.currency',
  70. string='Source Currency', store=True, copy=False,
  71. compute='_compute_from_lines')
  72. can_edit_wizard = fields.Boolean(store=True, copy=False,
  73. compute='_compute_from_lines') # used to check if user can edit info such as the amount
  74. can_group_payments = fields.Boolean(store=True, copy=False,
  75. compute='_compute_from_lines') # can the user see the 'group_payments' box
  76. company_id = fields.Many2one('res.company', store=True, copy=False,
  77. compute='_compute_from_lines')
  78. partner_id = fields.Many2one('res.partner',
  79. string="Customer/Vendor", store=True, copy=False, ondelete='restrict',
  80. compute='_compute_from_lines')
  81. # == Payment methods fields ==
  82. payment_method_line_id = fields.Many2one('account.payment.method.line', string='Payment Method',
  83. readonly=False, store=True,
  84. compute='_compute_payment_method_line_id',
  85. domain="[('id', 'in', available_payment_method_line_ids)]",
  86. help="Manual: Pay or Get paid by any method outside of Odoo.\n"
  87. "Payment Providers: Each payment provider has its own Payment Method. Request a transaction on/to a card thanks to a payment token saved by the partner when buying or subscribing online.\n"
  88. "Check: Pay bills by check and print it from Odoo.\n"
  89. "Batch Deposit: Collect several customer checks at once generating and submitting a batch deposit to your bank. Module account_batch_payment is necessary.\n"
  90. "SEPA Credit Transfer: Pay in the SEPA zone by submitting a SEPA Credit Transfer file to your bank. Module account_sepa is necessary.\n"
  91. "SEPA Direct Debit: Get paid in the SEPA zone thanks to a mandate your partner will have granted to you. Module account_sepa is necessary.\n")
  92. available_payment_method_line_ids = fields.Many2many('account.payment.method.line', compute='_compute_payment_method_line_fields')
  93. # == Payment difference fields ==
  94. payment_difference = fields.Monetary(
  95. compute='_compute_payment_difference')
  96. payment_difference_handling = fields.Selection(
  97. string="Payment Difference Handling",
  98. selection=[('open', 'Keep open'), ('reconcile', 'Mark as fully paid')],
  99. compute='_compute_payment_difference_handling',
  100. store=True,
  101. readonly=False,
  102. )
  103. writeoff_account_id = fields.Many2one(
  104. comodel_name='account.account',
  105. string="Difference Account",
  106. copy=False,
  107. domain="[('deprecated', '=', False), ('company_id', '=', company_id)]",
  108. compute='_compute_writeoff_account_id',
  109. store=True,
  110. readonly=False,
  111. )
  112. writeoff_label = fields.Char(string='Journal Item Label', default='Write-Off',
  113. help='Change label of the counterpart that will hold the payment difference')
  114. # == Display purpose fields ==
  115. show_partner_bank_account = fields.Boolean(
  116. compute='_compute_show_require_partner_bank') # Used to know whether the field `partner_bank_id` should be displayed
  117. require_partner_bank_account = fields.Boolean(
  118. compute='_compute_show_require_partner_bank') # used to know whether the field `partner_bank_id` should be required
  119. country_code = fields.Char(related='company_id.account_fiscal_country_id.code', readonly=True)
  120. # -------------------------------------------------------------------------
  121. # HELPERS
  122. # -------------------------------------------------------------------------
  123. @api.model
  124. def _get_batch_communication(self, batch_result):
  125. ''' Helper to compute the communication based on the batch.
  126. :param batch_result: A batch returned by '_get_batches'.
  127. :return: A string representing a communication to be set on payment.
  128. '''
  129. labels = set(line.name or line.move_id.ref or line.move_id.name for line in batch_result['lines'])
  130. return ' '.join(sorted(labels))
  131. @api.model
  132. def _get_batch_available_journals(self, batch_result):
  133. """ Helper to compute the available journals based on the batch.
  134. :param batch_result: A batch returned by '_get_batches'.
  135. :return: A recordset of account.journal.
  136. """
  137. payment_type = batch_result['payment_values']['payment_type']
  138. company = batch_result['lines'].company_id
  139. journals = self.env['account.journal'].search([('company_id', '=', company.id), ('type', 'in', ('bank', 'cash'))])
  140. if payment_type == 'inbound':
  141. return journals.filtered('inbound_payment_method_line_ids')
  142. else:
  143. return journals.filtered('outbound_payment_method_line_ids')
  144. @api.model
  145. def _get_batch_journal(self, batch_result):
  146. """ Helper to compute the journal based on the batch.
  147. :param batch_result: A batch returned by '_get_batches'.
  148. :return: An account.journal record.
  149. """
  150. payment_values = batch_result['payment_values']
  151. foreign_currency_id = payment_values['currency_id']
  152. partner_bank_id = payment_values['partner_bank_id']
  153. currency_domain = [('currency_id', '=', foreign_currency_id)]
  154. partner_bank_domain = [('bank_account_id', '=', partner_bank_id)]
  155. default_domain = [
  156. ('type', 'in', ('bank', 'cash')),
  157. ('company_id', '=', batch_result['lines'].company_id.id),
  158. ('id', 'in', self.available_journal_ids.ids)
  159. ]
  160. if partner_bank_id:
  161. extra_domains = (
  162. currency_domain + partner_bank_domain,
  163. partner_bank_domain,
  164. currency_domain,
  165. [],
  166. )
  167. else:
  168. extra_domains = (
  169. currency_domain,
  170. [],
  171. )
  172. for extra_domain in extra_domains:
  173. journal = self.env['account.journal'].search(default_domain + extra_domain, limit=1)
  174. if journal:
  175. return journal
  176. return self.env['account.journal']
  177. @api.model
  178. def _get_batch_available_partner_banks(self, batch_result, journal):
  179. payment_values = batch_result['payment_values']
  180. company = batch_result['lines'].company_id
  181. # A specific bank account is set on the journal. The user must use this one.
  182. if payment_values['payment_type'] == 'inbound':
  183. # Receiving money on a bank account linked to the journal.
  184. return journal.bank_account_id
  185. else:
  186. # Sending money to a bank account owned by a partner.
  187. return batch_result['lines'].partner_id.bank_ids.filtered(lambda x: x.company_id.id in (False, company.id))._origin
  188. @api.model
  189. def _get_line_batch_key(self, line):
  190. ''' Turn the line passed as parameter to a dictionary defining on which way the lines
  191. will be grouped together.
  192. :return: A python dictionary.
  193. '''
  194. move = line.move_id
  195. partner_bank_account = self.env['res.partner.bank']
  196. if move.is_invoice(include_receipts=True):
  197. partner_bank_account = move.partner_bank_id._origin
  198. return {
  199. 'partner_id': line.partner_id.id,
  200. 'account_id': line.account_id.id,
  201. 'currency_id': line.currency_id.id,
  202. 'partner_bank_id': partner_bank_account.id,
  203. 'partner_type': 'customer' if line.account_type == 'asset_receivable' else 'supplier',
  204. }
  205. def _get_batches(self):
  206. ''' Group the account.move.line linked to the wizard together.
  207. Lines are grouped if they share 'partner_id','account_id','currency_id' & 'partner_type' and if
  208. 0 or 1 partner_bank_id can be determined for the group.
  209. :return: A list of batches, each one containing:
  210. * payment_values: A dictionary of payment values.
  211. * moves: An account.move recordset.
  212. '''
  213. self.ensure_one()
  214. lines = self.line_ids._origin
  215. if len(lines.company_id) > 1:
  216. raise UserError(_("You can't create payments for entries belonging to different companies."))
  217. if not lines:
  218. raise UserError(_("You can't open the register payment wizard without at least one receivable/payable line."))
  219. batches = defaultdict(lambda: {'lines': self.env['account.move.line']})
  220. banks_per_partner = defaultdict(lambda: {'inbound': set(), 'outbound': set()})
  221. for line in lines:
  222. batch_key = self._get_line_batch_key(line)
  223. vals = batches[frozendict(batch_key)]
  224. vals['payment_values'] = batch_key
  225. vals['lines'] += line
  226. banks_per_partner[batch_key['partner_id']]['inbound' if line.balance > 0.0 else 'outbound'].add(
  227. batch_key['partner_bank_id']
  228. )
  229. partner_unique_inbound = {p for p, b in banks_per_partner.items() if len(b['inbound']) == 1}
  230. partner_unique_outbound = {p for p, b in banks_per_partner.items() if len(b['outbound']) == 1}
  231. # Compute 'payment_type'.
  232. batch_vals = []
  233. seen_keys = set()
  234. for i, key in enumerate(list(batches)):
  235. if key in seen_keys:
  236. continue
  237. vals = batches[key]
  238. lines = vals['lines']
  239. merge = (
  240. batch_key['partner_id'] in partner_unique_inbound
  241. and batch_key['partner_id'] in partner_unique_outbound
  242. )
  243. if merge:
  244. for other_key in list(batches)[i+1:]:
  245. if other_key in seen_keys:
  246. continue
  247. other_vals = batches[other_key]
  248. if all(
  249. other_vals['payment_values'][k] == v
  250. for k, v in vals['payment_values'].items()
  251. if k not in ('partner_bank_id', 'payment_type')
  252. ):
  253. # add the lines in this batch and mark as seen
  254. lines += other_vals['lines']
  255. seen_keys.add(other_key)
  256. balance = sum(lines.mapped('balance'))
  257. vals['payment_values']['payment_type'] = 'inbound' if balance > 0.0 else 'outbound'
  258. if merge:
  259. partner_banks = banks_per_partner[batch_key['partner_id']]
  260. vals['partner_bank_id'] = partner_banks[vals['payment_values']['payment_type']]
  261. vals['lines'] = lines
  262. batch_vals.append(vals)
  263. return batch_vals
  264. @api.model
  265. def _get_wizard_values_from_batch(self, batch_result):
  266. ''' Extract values from the batch passed as parameter (see '_get_batches')
  267. to be mounted in the wizard view.
  268. :param batch_result: A batch returned by '_get_batches'.
  269. :return: A dictionary containing valid fields
  270. '''
  271. payment_values = batch_result['payment_values']
  272. lines = batch_result['lines']
  273. company = lines[0].company_id
  274. source_amount = abs(sum(lines.mapped('amount_residual')))
  275. if payment_values['currency_id'] == company.currency_id.id:
  276. source_amount_currency = source_amount
  277. else:
  278. source_amount_currency = abs(sum(lines.mapped('amount_residual_currency')))
  279. return {
  280. 'company_id': company.id,
  281. 'partner_id': payment_values['partner_id'],
  282. 'partner_type': payment_values['partner_type'],
  283. 'payment_type': payment_values['payment_type'],
  284. 'source_currency_id': payment_values['currency_id'],
  285. 'source_amount': source_amount,
  286. 'source_amount_currency': source_amount_currency,
  287. }
  288. # -------------------------------------------------------------------------
  289. # COMPUTE METHODS
  290. # -------------------------------------------------------------------------
  291. @api.depends('line_ids')
  292. def _compute_from_lines(self):
  293. ''' Load initial values from the account.moves passed through the context. '''
  294. for wizard in self:
  295. batches = wizard._get_batches()
  296. batch_result = batches[0]
  297. wizard_values_from_batch = wizard._get_wizard_values_from_batch(batch_result)
  298. if len(batches) == 1:
  299. # == Single batch to be mounted on the view ==
  300. wizard.update(wizard_values_from_batch)
  301. wizard.can_edit_wizard = True
  302. wizard.can_group_payments = len(batch_result['lines']) != 1
  303. else:
  304. # == Multiple batches: The wizard is not editable ==
  305. wizard.update({
  306. 'company_id': batches[0]['lines'][0].company_id.id,
  307. 'partner_id': False,
  308. 'partner_type': False,
  309. 'payment_type': wizard_values_from_batch['payment_type'],
  310. 'source_currency_id': False,
  311. 'source_amount': False,
  312. 'source_amount_currency': False,
  313. })
  314. wizard.can_edit_wizard = False
  315. wizard.can_group_payments = any(len(batch_result['lines']) != 1 for batch_result in batches)
  316. @api.depends('can_edit_wizard')
  317. def _compute_communication(self):
  318. # The communication can't be computed in '_compute_from_lines' because
  319. # it's a compute editable field and then, should be computed in a separated method.
  320. for wizard in self:
  321. if wizard.can_edit_wizard:
  322. batches = wizard._get_batches()
  323. wizard.communication = wizard._get_batch_communication(batches[0])
  324. else:
  325. wizard.communication = False
  326. @api.depends('can_edit_wizard')
  327. def _compute_group_payment(self):
  328. for wizard in self:
  329. if wizard.can_edit_wizard:
  330. batches = wizard._get_batches()
  331. wizard.group_payment = len(batches[0]['lines'].move_id) == 1
  332. else:
  333. wizard.group_payment = False
  334. @api.depends('journal_id')
  335. def _compute_currency_id(self):
  336. for wizard in self:
  337. wizard.currency_id = wizard.journal_id.currency_id or wizard.source_currency_id or wizard.company_id.currency_id
  338. @api.depends('payment_type', 'company_id', 'can_edit_wizard')
  339. def _compute_available_journal_ids(self):
  340. for wizard in self:
  341. if wizard.can_edit_wizard:
  342. batch = wizard._get_batches()[0]
  343. wizard.available_journal_ids = wizard._get_batch_available_journals(batch)
  344. else:
  345. wizard.available_journal_ids = self.env['account.journal'].search([
  346. ('company_id', '=', wizard.company_id.id),
  347. ('type', 'in', ('bank', 'cash')),
  348. ])
  349. @api.depends('available_journal_ids')
  350. def _compute_journal_id(self):
  351. for wizard in self:
  352. if wizard.can_edit_wizard:
  353. batch = wizard._get_batches()[0]
  354. wizard.journal_id = wizard._get_batch_journal(batch)
  355. else:
  356. wizard.journal_id = self.env['account.journal'].search([
  357. ('type', 'in', ('bank', 'cash')),
  358. ('company_id', '=', wizard.company_id.id),
  359. ('id', 'in', self.available_journal_ids.ids)
  360. ], limit=1)
  361. @api.depends('can_edit_wizard', 'journal_id')
  362. def _compute_available_partner_bank_ids(self):
  363. for wizard in self:
  364. if wizard.can_edit_wizard:
  365. batch = wizard._get_batches()[0]
  366. wizard.available_partner_bank_ids = wizard._get_batch_available_partner_banks(batch, wizard.journal_id)
  367. else:
  368. wizard.available_partner_bank_ids = None
  369. @api.depends('journal_id', 'available_partner_bank_ids')
  370. def _compute_partner_bank_id(self):
  371. for wizard in self:
  372. if wizard.can_edit_wizard:
  373. batch = wizard._get_batches()[0]
  374. partner_bank_id = batch['payment_values']['partner_bank_id']
  375. available_partner_banks = wizard.available_partner_bank_ids._origin
  376. if partner_bank_id and partner_bank_id in available_partner_banks.ids:
  377. wizard.partner_bank_id = self.env['res.partner.bank'].browse(partner_bank_id)
  378. else:
  379. wizard.partner_bank_id = available_partner_banks[:1]
  380. else:
  381. wizard.partner_bank_id = None
  382. @api.depends('payment_type', 'journal_id', 'currency_id')
  383. def _compute_payment_method_line_fields(self):
  384. for wizard in self:
  385. if wizard.journal_id:
  386. wizard.available_payment_method_line_ids = wizard.journal_id._get_available_payment_method_lines(wizard.payment_type)
  387. else:
  388. wizard.available_payment_method_line_ids = False
  389. @api.depends('payment_type', 'journal_id')
  390. def _compute_payment_method_line_id(self):
  391. for wizard in self:
  392. if wizard.journal_id:
  393. available_payment_method_lines = wizard.journal_id._get_available_payment_method_lines(wizard.payment_type)
  394. else:
  395. available_payment_method_lines = False
  396. # Select the first available one by default.
  397. if available_payment_method_lines:
  398. wizard.payment_method_line_id = available_payment_method_lines[0]._origin
  399. else:
  400. wizard.payment_method_line_id = False
  401. @api.depends('payment_method_line_id')
  402. def _compute_show_require_partner_bank(self):
  403. """ Computes if the destination bank account must be displayed in the payment form view. By default, it
  404. won't be displayed but some modules might change that, depending on the payment type."""
  405. for wizard in self:
  406. if wizard.journal_id.type == 'cash':
  407. wizard.show_partner_bank_account = False
  408. else:
  409. wizard.show_partner_bank_account = wizard.payment_method_line_id.code in self.env['account.payment']._get_method_codes_using_bank_account()
  410. wizard.require_partner_bank_account = wizard.payment_method_line_id.code in self.env['account.payment']._get_method_codes_needing_bank_account()
  411. def _get_total_amount_using_same_currency(self, batch_result, early_payment_discount=True):
  412. self.ensure_one()
  413. amount = 0.0
  414. mode = False
  415. for aml in batch_result['lines']:
  416. if early_payment_discount and aml._is_eligible_for_early_payment_discount(aml.currency_id, self.payment_date):
  417. amount += aml.discount_amount_currency
  418. mode = 'early_payment'
  419. else:
  420. amount += aml.amount_residual_currency
  421. return abs(amount), mode
  422. def _get_total_amount_in_wizard_currency_to_full_reconcile(self, batch_result, early_payment_discount=True):
  423. """ Compute the total amount needed in the currency of the wizard to fully reconcile the batch of journal
  424. items passed as parameter.
  425. :param batch_result: A batch returned by '_get_batches'.
  426. :return: An amount in the currency of the wizard.
  427. """
  428. self.ensure_one()
  429. comp_curr = self.company_id.currency_id
  430. if self.source_currency_id == self.currency_id:
  431. # Same currency (manage the early payment discount).
  432. return self._get_total_amount_using_same_currency(batch_result, early_payment_discount=early_payment_discount)
  433. elif self.source_currency_id != comp_curr and self.currency_id == comp_curr:
  434. # Foreign currency on source line but the company currency one on the opposite line.
  435. return self.source_currency_id._convert(
  436. self.source_amount_currency,
  437. comp_curr,
  438. self.company_id,
  439. self.payment_date,
  440. ), False
  441. elif self.source_currency_id == comp_curr and self.currency_id != comp_curr:
  442. # Company currency on source line but a foreign currency one on the opposite line.
  443. residual_amount = 0.0
  444. for aml in batch_result['lines']:
  445. if not aml.move_id.payment_id and not aml.move_id.statement_line_id:
  446. conversion_date = self.payment_date
  447. else:
  448. conversion_date = aml.date
  449. residual_amount += comp_curr._convert(
  450. aml.amount_residual,
  451. self.currency_id,
  452. self.company_id,
  453. conversion_date,
  454. )
  455. return abs(residual_amount), False
  456. else:
  457. # Foreign currency on payment different than the one set on the journal entries.
  458. return comp_curr._convert(
  459. self.source_amount,
  460. self.currency_id,
  461. self.company_id,
  462. self.payment_date,
  463. ), False
  464. @api.depends('can_edit_wizard', 'source_amount', 'source_amount_currency', 'source_currency_id', 'company_id', 'currency_id', 'payment_date')
  465. def _compute_amount(self):
  466. for wizard in self:
  467. if wizard.source_currency_id and wizard.can_edit_wizard:
  468. batch_result = wizard._get_batches()[0]
  469. wizard.amount = wizard._get_total_amount_in_wizard_currency_to_full_reconcile(batch_result)[0]
  470. else:
  471. # The wizard is not editable so no partial payment allowed and then, 'amount' is not used.
  472. wizard.amount = None
  473. @api.depends('can_edit_wizard', 'payment_date', 'currency_id', 'amount')
  474. def _compute_early_payment_discount_mode(self):
  475. for wizard in self:
  476. if wizard.can_edit_wizard and wizard.currency_id:
  477. batch_result = wizard._get_batches()[0]
  478. total_amount_residual_in_wizard_currency, mode = wizard._get_total_amount_in_wizard_currency_to_full_reconcile(batch_result)
  479. wizard.early_payment_discount_mode = \
  480. wizard.currency_id.compare_amounts(wizard.amount, total_amount_residual_in_wizard_currency) == 0 \
  481. and mode == 'early_payment'
  482. else:
  483. wizard.early_payment_discount_mode = False
  484. @api.depends('can_edit_wizard', 'amount')
  485. def _compute_payment_difference(self):
  486. for wizard in self:
  487. if wizard.can_edit_wizard:
  488. batch_result = wizard._get_batches()[0]
  489. total_amount_residual_in_wizard_currency = wizard\
  490. ._get_total_amount_in_wizard_currency_to_full_reconcile(batch_result, early_payment_discount=False)[0]
  491. wizard.payment_difference = total_amount_residual_in_wizard_currency - wizard.amount
  492. else:
  493. wizard.payment_difference = 0.0
  494. @api.depends('early_payment_discount_mode')
  495. def _compute_payment_difference_handling(self):
  496. for wizard in self:
  497. if wizard.can_edit_wizard:
  498. wizard.payment_difference_handling = 'reconcile' if wizard.early_payment_discount_mode else 'open'
  499. else:
  500. wizard.payment_difference_handling = False
  501. @api.depends('early_payment_discount_mode')
  502. def _compute_hide_writeoff_section(self):
  503. for wizard in self:
  504. wizard.hide_writeoff_section = wizard.early_payment_discount_mode
  505. # -------------------------------------------------------------------------
  506. # LOW-LEVEL METHODS
  507. # -------------------------------------------------------------------------
  508. @api.model
  509. def default_get(self, fields_list):
  510. # OVERRIDE
  511. res = super().default_get(fields_list)
  512. if 'line_ids' in fields_list and 'line_ids' not in res:
  513. # Retrieve moves to pay from the context.
  514. if self._context.get('active_model') == 'account.move':
  515. lines = self.env['account.move'].browse(self._context.get('active_ids', [])).line_ids
  516. elif self._context.get('active_model') == 'account.move.line':
  517. lines = self.env['account.move.line'].browse(self._context.get('active_ids', []))
  518. else:
  519. raise UserError(_(
  520. "The register payment wizard should only be called on account.move or account.move.line records."
  521. ))
  522. if 'journal_id' in res and not self.env['account.journal'].browse(res['journal_id'])\
  523. .filtered_domain([('company_id', '=', lines.company_id.id), ('type', 'in', ('bank', 'cash'))]):
  524. # default can be inherited from the list view, should be computed instead
  525. del res['journal_id']
  526. # Keep lines having a residual amount to pay.
  527. available_lines = self.env['account.move.line']
  528. for line in lines:
  529. if line.move_id.state != 'posted':
  530. raise UserError(_("You can only register payment for posted journal entries."))
  531. if line.account_type not in ('asset_receivable', 'liability_payable'):
  532. continue
  533. if line.currency_id:
  534. if line.currency_id.is_zero(line.amount_residual_currency):
  535. continue
  536. else:
  537. if line.company_currency_id.is_zero(line.amount_residual):
  538. continue
  539. available_lines |= line
  540. # Check.
  541. if not available_lines:
  542. raise UserError(_("You can't register a payment because there is nothing left to pay on the selected journal items."))
  543. if len(lines.company_id) > 1:
  544. raise UserError(_("You can't create payments for entries belonging to different companies."))
  545. if len(set(available_lines.mapped('account_type'))) > 1:
  546. raise UserError(_("You can't register payments for journal items being either all inbound, either all outbound."))
  547. res['line_ids'] = [(6, 0, available_lines.ids)]
  548. return res
  549. # -------------------------------------------------------------------------
  550. # BUSINESS METHODS
  551. # -------------------------------------------------------------------------
  552. def _create_payment_vals_from_wizard(self, batch_result):
  553. payment_vals = {
  554. 'date': self.payment_date,
  555. 'amount': self.amount,
  556. 'payment_type': self.payment_type,
  557. 'partner_type': self.partner_type,
  558. 'ref': self.communication,
  559. 'journal_id': self.journal_id.id,
  560. 'currency_id': self.currency_id.id,
  561. 'partner_id': self.partner_id.id,
  562. 'partner_bank_id': self.partner_bank_id.id,
  563. 'payment_method_line_id': self.payment_method_line_id.id,
  564. 'destination_account_id': self.line_ids[0].account_id.id,
  565. 'write_off_line_vals': [],
  566. }
  567. conversion_rate = self.env['res.currency']._get_conversion_rate(
  568. self.currency_id,
  569. self.company_id.currency_id,
  570. self.company_id,
  571. self.payment_date,
  572. )
  573. if self.payment_difference_handling == 'reconcile':
  574. if self.early_payment_discount_mode:
  575. epd_aml_values_list = []
  576. for aml in batch_result['lines']:
  577. if aml._is_eligible_for_early_payment_discount(self.currency_id, self.payment_date):
  578. epd_aml_values_list.append({
  579. 'aml': aml,
  580. 'amount_currency': -aml.amount_residual_currency,
  581. 'balance': aml.company_currency_id.round(-aml.amount_residual_currency * conversion_rate),
  582. })
  583. open_amount_currency = self.payment_difference * (-1 if self.payment_type == 'outbound' else 1)
  584. open_balance = self.company_id.currency_id.round(open_amount_currency * conversion_rate)
  585. early_payment_values = self.env['account.move']._get_invoice_counterpart_amls_for_early_payment_discount(epd_aml_values_list, open_balance)
  586. for aml_values_list in early_payment_values.values():
  587. payment_vals['write_off_line_vals'] += aml_values_list
  588. elif not self.currency_id.is_zero(self.payment_difference):
  589. if self.payment_type == 'inbound':
  590. # Receive money.
  591. write_off_amount_currency = self.payment_difference
  592. else: # if self.payment_type == 'outbound':
  593. # Send money.
  594. write_off_amount_currency = -self.payment_difference
  595. write_off_balance = self.company_id.currency_id.round(write_off_amount_currency * conversion_rate)
  596. payment_vals['write_off_line_vals'].append({
  597. 'name': self.writeoff_label,
  598. 'account_id': self.writeoff_account_id.id,
  599. 'partner_id': self.partner_id.id,
  600. 'currency_id': self.currency_id.id,
  601. 'amount_currency': write_off_amount_currency,
  602. 'balance': write_off_balance,
  603. })
  604. return payment_vals
  605. def _create_payment_vals_from_batch(self, batch_result):
  606. batch_values = self._get_wizard_values_from_batch(batch_result)
  607. if batch_values['payment_type'] == 'inbound':
  608. partner_bank_id = self.journal_id.bank_account_id.id
  609. else:
  610. partner_bank_id = batch_result['payment_values']['partner_bank_id']
  611. payment_method_line = self.payment_method_line_id
  612. if batch_values['payment_type'] != payment_method_line.payment_type:
  613. payment_method_line = self.journal_id._get_available_payment_method_lines(batch_values['payment_type'])[:1]
  614. payment_vals = {
  615. 'date': self.payment_date,
  616. 'amount': batch_values['source_amount_currency'],
  617. 'payment_type': batch_values['payment_type'],
  618. 'partner_type': batch_values['partner_type'],
  619. 'ref': self._get_batch_communication(batch_result),
  620. 'journal_id': self.journal_id.id,
  621. 'currency_id': batch_values['source_currency_id'],
  622. 'partner_id': batch_values['partner_id'],
  623. 'partner_bank_id': partner_bank_id,
  624. 'payment_method_line_id': payment_method_line.id,
  625. 'destination_account_id': batch_result['lines'][0].account_id.id,
  626. 'write_off_line_vals': [],
  627. }
  628. total_amount, mode = self._get_total_amount_using_same_currency(batch_result)
  629. currency = self.env['res.currency'].browse(batch_values['source_currency_id'])
  630. if mode == 'early_payment':
  631. payment_vals['amount'] = total_amount
  632. conversion_rate = self.env['res.currency']._get_conversion_rate(
  633. currency,
  634. self.company_id.currency_id,
  635. self.company_id,
  636. self.payment_date,
  637. )
  638. epd_aml_values_list = []
  639. for aml in batch_result['lines']:
  640. if aml._is_eligible_for_early_payment_discount(currency, self.payment_date):
  641. epd_aml_values_list.append({
  642. 'aml': aml,
  643. 'amount_currency': -aml.amount_residual_currency,
  644. 'balance': aml.company_currency_id.round(-aml.amount_residual_currency * conversion_rate),
  645. })
  646. open_amount_currency = (batch_values['source_amount_currency'] - total_amount) * (-1 if batch_values['payment_type'] == 'outbound' else 1)
  647. open_balance = self.company_id.currency_id.round(open_amount_currency * conversion_rate)
  648. early_payment_values = self.env['account.move']\
  649. ._get_invoice_counterpart_amls_for_early_payment_discount(epd_aml_values_list, open_balance)
  650. for aml_values_list in early_payment_values.values():
  651. payment_vals['write_off_line_vals'] += aml_values_list
  652. return payment_vals
  653. def _init_payments(self, to_process, edit_mode=False):
  654. """ Create the payments.
  655. :param to_process: A list of python dictionary, one for each payment to create, containing:
  656. * create_vals: The values used for the 'create' method.
  657. * to_reconcile: The journal items to perform the reconciliation.
  658. * batch: A python dict containing everything you want about the source journal items
  659. to which a payment will be created (see '_get_batches').
  660. :param edit_mode: Is the wizard in edition mode.
  661. """
  662. payments = self.env['account.payment']\
  663. .with_context(skip_invoice_sync=True)\
  664. .create([x['create_vals'] for x in to_process])
  665. for payment, vals in zip(payments, to_process):
  666. vals['payment'] = payment
  667. # If payments are made using a currency different than the source one, ensure the balance match exactly in
  668. # order to fully paid the source journal items.
  669. # For example, suppose a new currency B having a rate 100:1 regarding the company currency A.
  670. # If you try to pay 12.15A using 0.12B, the computed balance will be 12.00A for the payment instead of 12.15A.
  671. if edit_mode:
  672. lines = vals['to_reconcile']
  673. # Batches are made using the same currency so making 'lines.currency_id' is ok.
  674. if payment.currency_id != lines.currency_id:
  675. liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines()
  676. source_balance = abs(sum(lines.mapped('amount_residual')))
  677. if liquidity_lines[0].balance:
  678. payment_rate = liquidity_lines[0].amount_currency / liquidity_lines[0].balance
  679. else:
  680. payment_rate = 0.0
  681. source_balance_converted = abs(source_balance) * payment_rate
  682. # Translate the balance into the payment currency is order to be able to compare them.
  683. # In case in both have the same value (12.15 * 0.01 ~= 0.12 in our example), it means the user
  684. # attempt to fully paid the source lines and then, we need to manually fix them to get a perfect
  685. # match.
  686. payment_balance = abs(sum(counterpart_lines.mapped('balance')))
  687. payment_amount_currency = abs(sum(counterpart_lines.mapped('amount_currency')))
  688. if not payment.currency_id.is_zero(source_balance_converted - payment_amount_currency):
  689. continue
  690. delta_balance = source_balance - payment_balance
  691. # Balance are already the same.
  692. if self.company_currency_id.is_zero(delta_balance):
  693. continue
  694. # Fix the balance but make sure to peek the liquidity and counterpart lines first.
  695. debit_lines = (liquidity_lines + counterpart_lines).filtered('debit')
  696. credit_lines = (liquidity_lines + counterpart_lines).filtered('credit')
  697. if debit_lines and credit_lines:
  698. payment.move_id.write({'line_ids': [
  699. (1, debit_lines[0].id, {'debit': debit_lines[0].debit + delta_balance}),
  700. (1, credit_lines[0].id, {'credit': credit_lines[0].credit + delta_balance}),
  701. ]})
  702. return payments
  703. def _post_payments(self, to_process, edit_mode=False):
  704. """ Post the newly created payments.
  705. :param to_process: A list of python dictionary, one for each payment to create, containing:
  706. * create_vals: The values used for the 'create' method.
  707. * to_reconcile: The journal items to perform the reconciliation.
  708. * batch: A python dict containing everything you want about the source journal items
  709. to which a payment will be created (see '_get_batches').
  710. :param edit_mode: Is the wizard in edition mode.
  711. """
  712. payments = self.env['account.payment']
  713. for vals in to_process:
  714. payments |= vals['payment']
  715. payments.action_post()
  716. def _reconcile_payments(self, to_process, edit_mode=False):
  717. """ Reconcile the payments.
  718. :param to_process: A list of python dictionary, one for each payment to create, containing:
  719. * create_vals: The values used for the 'create' method.
  720. * to_reconcile: The journal items to perform the reconciliation.
  721. * batch: A python dict containing everything you want about the source journal items
  722. to which a payment will be created (see '_get_batches').
  723. :param edit_mode: Is the wizard in edition mode.
  724. """
  725. domain = [
  726. ('parent_state', '=', 'posted'),
  727. ('account_type', 'in', ('asset_receivable', 'liability_payable')),
  728. ('reconciled', '=', False),
  729. ]
  730. for vals in to_process:
  731. payment_lines = vals['payment'].line_ids.filtered_domain(domain)
  732. lines = vals['to_reconcile']
  733. for account in payment_lines.account_id:
  734. (payment_lines + lines)\
  735. .filtered_domain([('account_id', '=', account.id), ('reconciled', '=', False)])\
  736. .reconcile()
  737. def _create_payments(self):
  738. self.ensure_one()
  739. batches = self._get_batches()
  740. first_batch_result = batches[0]
  741. edit_mode = self.can_edit_wizard and (len(first_batch_result['lines']) == 1 or self.group_payment)
  742. to_process = []
  743. if edit_mode:
  744. payment_vals = self._create_payment_vals_from_wizard(first_batch_result)
  745. to_process.append({
  746. 'create_vals': payment_vals,
  747. 'to_reconcile': first_batch_result['lines'],
  748. 'batch': first_batch_result,
  749. })
  750. else:
  751. # Don't group payments: Create one batch per move.
  752. if not self.group_payment:
  753. new_batches = []
  754. for batch_result in batches:
  755. for line in batch_result['lines']:
  756. new_batches.append({
  757. **batch_result,
  758. 'payment_values': {
  759. **batch_result['payment_values'],
  760. 'payment_type': 'inbound' if line.balance > 0 else 'outbound'
  761. },
  762. 'lines': line,
  763. })
  764. batches = new_batches
  765. for batch_result in batches:
  766. to_process.append({
  767. 'create_vals': self._create_payment_vals_from_batch(batch_result),
  768. 'to_reconcile': batch_result['lines'],
  769. 'batch': batch_result,
  770. })
  771. payments = self._init_payments(to_process, edit_mode=edit_mode)
  772. self._post_payments(to_process, edit_mode=edit_mode)
  773. self._reconcile_payments(to_process, edit_mode=edit_mode)
  774. return payments
  775. def action_create_payments(self):
  776. payments = self._create_payments()
  777. if self._context.get('dont_redirect_to_payments'):
  778. return True
  779. action = {
  780. 'name': _('Payments'),
  781. 'type': 'ir.actions.act_window',
  782. 'res_model': 'account.payment',
  783. 'context': {'create': False},
  784. }
  785. if len(payments) == 1:
  786. action.update({
  787. 'view_mode': 'form',
  788. 'res_id': payments.id,
  789. })
  790. else:
  791. action.update({
  792. 'view_mode': 'tree,form',
  793. 'domain': [('id', 'in', payments.ids)],
  794. })
  795. return action