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.

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