You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

267 lines
12 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 Carlos Dauden <carlos.dauden@tecnativa.com>
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  4. from datetime import datetime
  5. from dateutil.relativedelta import relativedelta
  6. from odoo import api, fields, models
  7. class ResPartner(models.Model):
  8. _inherit = 'res.partner'
  9. move_line_ids = fields.One2many(
  10. comodel_name='account.move.line',
  11. inverse_name='partner_id',
  12. string='Account Moves'
  13. )
  14. risk_invoice_draft_include = fields.Boolean(
  15. string='Include Draft Invoices', help='Full risk computation')
  16. risk_invoice_draft_limit = fields.Monetary(
  17. string='Limit In Draft Invoices', help='Set 0 if it is not locked')
  18. risk_invoice_draft = fields.Monetary(
  19. compute='_compute_risk_invoice', store=True,
  20. string='Total Draft Invoices',
  21. help='Total amount of invoices in Draft or Pro-forma state')
  22. risk_invoice_open_include = fields.Boolean(
  23. string='Include Open Invoices/Principal Balance',
  24. help='Full risk computation.\n'
  25. 'Residual amount of move lines not reconciled with the same '
  26. 'account that is set as partner receivable and date maturity '
  27. 'not exceeded, considering Due Margin set in account settings.',
  28. )
  29. risk_invoice_open_limit = fields.Monetary(
  30. string='Limit In Open Invoices/Principal Balance',
  31. help='Set 0 if it is not locked',
  32. )
  33. risk_invoice_open = fields.Monetary(
  34. compute='_compute_risk_account_amount', store=True,
  35. string='Total Open Invoices/Principal Balance',
  36. help='Residual amount of move lines not reconciled with the same '
  37. 'account that is set as partner receivable and date maturity '
  38. 'not exceeded, considering Due Margin set in account settings.',
  39. )
  40. risk_invoice_unpaid_include = fields.Boolean(
  41. string='Include Unpaid Invoices/Principal Balance',
  42. help='Full risk computation.\n'
  43. 'Residual amount of move lines not reconciled with the same '
  44. 'account that is set as partner receivable and date maturity '
  45. 'exceeded, considering Due Margin set in account settings.',
  46. )
  47. risk_invoice_unpaid_limit = fields.Monetary(
  48. string='Limit In Unpaid Invoices/Principal Balance',
  49. help='Set 0 if it is not locked',
  50. )
  51. risk_invoice_unpaid = fields.Monetary(
  52. compute='_compute_risk_account_amount', store=True,
  53. string='Total Unpaid Invoices/Principal Balance',
  54. help='Residual amount of move lines not reconciled with the same '
  55. 'account that is set as partner receivable and date maturity '
  56. 'exceeded, considering Due Margin set in account settings.',
  57. )
  58. risk_account_amount_include = fields.Boolean(
  59. string='Include Other Account Open Amount',
  60. help='Full risk computation.\n'
  61. 'Residual amount of move lines not reconciled with distinct '
  62. 'account that is set as partner receivable and date maturity '
  63. 'not exceeded, considering Due Margin set in account settings.',
  64. )
  65. risk_account_amount_limit = fields.Monetary(
  66. string='Limit Other Account Open Amount',
  67. help='Set 0 if it is not locked',
  68. )
  69. risk_account_amount = fields.Monetary(
  70. compute='_compute_risk_account_amount', store=True,
  71. string='Total Other Account Open Amount',
  72. help='Residual amount of move lines not reconciled with distinct '
  73. 'account that is set as partner receivable and date maturity '
  74. 'not exceeded, considering Due Margin set in account settings.',
  75. )
  76. risk_account_amount_unpaid_include = fields.Boolean(
  77. string='Include Other Account Unpaid Amount',
  78. help='Full risk computation.\n'
  79. 'Residual amount of move lines not reconciled with distinct '
  80. 'account that is set as partner receivable and date maturity '
  81. 'exceeded, considering Due Margin set in account settings.',
  82. )
  83. risk_account_amount_unpaid_limit = fields.Monetary(
  84. string='Limit Other Account Unpaid Amount',
  85. help='Set 0 if it is not locked',
  86. )
  87. risk_account_amount_unpaid = fields.Monetary(
  88. compute='_compute_risk_account_amount', store=True,
  89. string='Total Other Account Unpaid Amount',
  90. help='Residual amount of move lines not reconciled with distinct '
  91. 'account that is set as partner receivable and date maturity '
  92. 'exceeded, considering Due Margin set in account settings.',
  93. )
  94. risk_total = fields.Monetary(
  95. compute='_compute_risk_exception',
  96. string='Total Risk', help='Sum of total risk included')
  97. risk_exception = fields.Boolean(
  98. compute='_compute_risk_exception',
  99. string='Risk Exception',
  100. help='It Indicate if partner risk exceeded')
  101. credit_policy = fields.Char()
  102. risk_allow_edit = fields.Boolean(compute='_compute_risk_allow_edit')
  103. credit_limit = fields.Float(track_visibility='onchange')
  104. @api.multi
  105. def _compute_risk_allow_edit(self):
  106. is_editable = self.env.user.has_group('account.group_account_manager')
  107. for partner in self.filtered('customer'):
  108. partner.risk_allow_edit = is_editable
  109. @api.multi
  110. @api.depends(
  111. 'customer', 'invoice_ids', 'invoice_ids.state',
  112. 'invoice_ids.amount_total',
  113. 'child_ids.invoice_ids', 'child_ids.invoice_ids.state',
  114. 'child_ids.invoice_ids.amount_total')
  115. def _compute_risk_invoice(self):
  116. all_partners_and_children = {}
  117. all_partner_ids = []
  118. for partner in self.filtered('customer'):
  119. if not partner.id:
  120. continue
  121. all_partners_and_children[partner] = self.with_context(
  122. active_test=False).search([('id', 'child_of', partner.id)]).ids
  123. all_partner_ids += all_partners_and_children[partner]
  124. if not all_partner_ids:
  125. return
  126. total_group = self.env['account.invoice'].sudo().read_group(
  127. [('type', 'in', ['out_invoice', 'out_refund']),
  128. ('state', 'in', ['draft', 'proforma', 'proforma2']),
  129. ('partner_id', 'in', self.ids)],
  130. ['partner_id', 'amount_total'],
  131. ['partner_id'])
  132. for partner, child_ids in all_partners_and_children.items():
  133. partner.risk_invoice_draft = sum(
  134. x['amount_total']
  135. for x in total_group if x['partner_id'][0] in child_ids)
  136. @api.model
  137. def _risk_account_groups(self):
  138. max_date = self._max_risk_date_due()
  139. return {
  140. 'open': {
  141. 'domain': [('reconciled', '=', False),
  142. ('account_id.internal_type', '=', 'receivable'),
  143. ('date_maturity', '>=', max_date)],
  144. 'fields': ['partner_id', 'account_id', 'amount_residual'],
  145. 'group_by': ['partner_id', 'account_id']
  146. },
  147. 'unpaid': {
  148. 'domain': [('reconciled', '=', False),
  149. ('account_id.internal_type', '=', 'receivable'),
  150. ('date_maturity', '<', max_date)],
  151. 'fields': ['partner_id', 'account_id', 'amount_residual'],
  152. 'group_by': ['partner_id', 'account_id']
  153. }
  154. }
  155. @api.multi
  156. @api.depends('move_line_ids.amount_residual',
  157. 'move_line_ids.date_maturity',
  158. 'company_id.invoice_unpaid_margin')
  159. def _compute_risk_account_amount(self):
  160. AccountMoveLine = self.env['account.move.line'].sudo()
  161. customers = self.filtered(lambda x: x.customer and not x.parent_id)
  162. if not customers:
  163. return
  164. groups = self._risk_account_groups()
  165. for key, group in groups.iteritems():
  166. group['read_group'] = AccountMoveLine.read_group(
  167. group['domain'] + [('partner_id', 'in', customers.ids)],
  168. group['fields'],
  169. group['group_by'],
  170. lazy=False,
  171. )
  172. for partner in customers:
  173. partner.update(partner._prepare_risk_account_vals(groups))
  174. @api.multi
  175. def _prepare_risk_account_vals(self, groups):
  176. vals = {
  177. 'risk_invoice_open': 0.0,
  178. 'risk_invoice_unpaid': 0.0,
  179. 'risk_account_amount': 0.0,
  180. 'risk_account_amount_unpaid': 0.0,
  181. }
  182. for reg in groups['open']['read_group']:
  183. if reg['partner_id'][0] != self.id:
  184. continue
  185. if self.property_account_receivable_id.id == reg['account_id'][0]:
  186. vals['risk_invoice_open'] += reg['amount_residual']
  187. else:
  188. vals['risk_account_amount'] += reg['amount_residual']
  189. for reg in groups['unpaid']['read_group']:
  190. if reg['partner_id'][0] != self.id:
  191. continue # pragma: no cover
  192. if self.property_account_receivable_id.id == reg['account_id'][0]:
  193. vals['risk_invoice_unpaid'] += reg['amount_residual']
  194. else:
  195. vals['risk_account_amount_unpaid'] += reg['amount_residual']
  196. return vals
  197. @api.multi
  198. @api.depends(lambda x: x._get_depends_compute_risk_exception())
  199. def _compute_risk_exception(self):
  200. risk_field_list = self._risk_field_list()
  201. for partner in self.filtered('customer'):
  202. amount = 0.0
  203. for risk_field in risk_field_list:
  204. field_value = getattr(partner, risk_field[0], 0.0)
  205. max_value = getattr(partner, risk_field[1], 0.0)
  206. if max_value and field_value > max_value:
  207. partner.risk_exception = True
  208. if getattr(partner, risk_field[2], False):
  209. amount += field_value
  210. partner.risk_total = amount
  211. if partner.credit_limit and amount > partner.credit_limit:
  212. partner.risk_exception = True
  213. @api.model
  214. def _max_risk_date_due(self):
  215. return fields.Date.to_string(datetime.today().date() - relativedelta(
  216. days=self.env.user.company_id.invoice_unpaid_margin))
  217. @api.model
  218. def _risk_field_list(self):
  219. return [
  220. ('risk_invoice_draft', 'risk_invoice_draft_limit',
  221. 'risk_invoice_draft_include'),
  222. ('risk_invoice_open', 'risk_invoice_open_limit',
  223. 'risk_invoice_open_include'),
  224. ('risk_invoice_unpaid', 'risk_invoice_unpaid_limit',
  225. 'risk_invoice_unpaid_include'),
  226. ('risk_account_amount', 'risk_account_amount_limit',
  227. 'risk_account_amount_include'),
  228. ('risk_account_amount_unpaid', 'risk_account_amount_unpaid_limit',
  229. 'risk_account_amount_unpaid_include'),
  230. ]
  231. @api.model
  232. def _get_depends_compute_risk_exception(self):
  233. res = []
  234. for x in self._risk_field_list():
  235. res.extend((x[0], x[1], x[2], 'child_ids.%s' % x[0],
  236. 'child_ids.%s' % x[1], 'child_ids.%s' % x[2]))
  237. res.extend(('credit_limit', 'child_ids.credit_limit'))
  238. return res
  239. @api.model
  240. def process_unpaid_invoices(self):
  241. max_date = self._max_risk_date_due()
  242. ConfigParameter = self.env['ir.config_parameter']
  243. last_check = ConfigParameter.get_param(
  244. 'partner_financial_risk.last_check', default='2016-01-01')
  245. move_lines = self.env['account.move.line'].search([
  246. ('reconciled', '=', False),
  247. ('account_id.internal_type', '=', 'receivable'),
  248. ('date_maturity', '>=', last_check),
  249. ('date_maturity', '<', max_date)])
  250. move_lines.mapped('partner_id')._compute_risk_account_amount()
  251. ConfigParameter.set_param(
  252. 'partner_financial_risk.last_check', max_date)
  253. return True