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.

281 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. store=True)
  103. credit_policy = fields.Char()
  104. risk_allow_edit = fields.Boolean(compute='_compute_risk_allow_edit')
  105. credit_limit = fields.Float(track_visibility='onchange')
  106. @api.multi
  107. def _compute_risk_allow_edit(self):
  108. is_editable = self.env.user.has_group('account.group_account_manager')
  109. for partner in self.filtered('customer'):
  110. partner.risk_allow_edit = is_editable
  111. @api.multi
  112. @api.depends(
  113. 'customer', 'invoice_ids', 'invoice_ids.state',
  114. 'invoice_ids.amount_total',
  115. 'child_ids.invoice_ids', 'child_ids.invoice_ids.state',
  116. 'child_ids.invoice_ids.amount_total')
  117. def _compute_risk_invoice(self):
  118. all_partners_and_children = {}
  119. all_partner_ids = []
  120. for partner in self.filtered('customer'):
  121. if not partner.id:
  122. continue
  123. all_partners_and_children[partner] = self.with_context(
  124. active_test=False).search([('id', 'child_of', partner.id)]).ids
  125. all_partner_ids += all_partners_and_children[partner]
  126. if not all_partner_ids:
  127. return
  128. total_group = self.env['account.invoice'].sudo().read_group(
  129. [('type', 'in', ['out_invoice', 'out_refund']),
  130. ('state', 'in', ['draft', 'proforma', 'proforma2']),
  131. ('partner_id', 'in', self.ids)],
  132. ['partner_id', 'amount_total'],
  133. ['partner_id'])
  134. for partner, child_ids in all_partners_and_children.items():
  135. partner.risk_invoice_draft = sum(
  136. x['amount_total']
  137. for x in total_group if x['partner_id'][0] in child_ids)
  138. @api.model
  139. def _risk_account_groups(self):
  140. max_date = self._max_risk_date_due()
  141. return {
  142. 'open': {
  143. 'domain': [('reconciled', '=', False),
  144. ('account_id.internal_type', '=', 'receivable'),
  145. ('date_maturity', '>=', max_date)],
  146. 'fields': ['partner_id', 'account_id', 'amount_residual'],
  147. 'group_by': ['partner_id', 'account_id']
  148. },
  149. 'unpaid': {
  150. 'domain': [('reconciled', '=', False),
  151. ('account_id.internal_type', '=', 'receivable'),
  152. ('date_maturity', '<', max_date)],
  153. 'fields': ['partner_id', 'account_id', 'amount_residual'],
  154. 'group_by': ['partner_id', 'account_id']
  155. }
  156. }
  157. @api.multi
  158. @api.depends('move_line_ids.amount_residual',
  159. 'move_line_ids.date_maturity',
  160. 'company_id.invoice_unpaid_margin')
  161. def _compute_risk_account_amount(self):
  162. AccountMoveLine = self.env['account.move.line'].sudo()
  163. customers = self.filtered(lambda x: x.customer and not x.parent_id)
  164. if not customers:
  165. return
  166. groups = self._risk_account_groups()
  167. for key, group in groups.iteritems():
  168. group['read_group'] = AccountMoveLine.read_group(
  169. group['domain'] + [('partner_id', 'in', customers.ids)],
  170. group['fields'],
  171. group['group_by'],
  172. lazy=False,
  173. )
  174. for partner in customers:
  175. partner.update(partner._prepare_risk_account_vals(groups))
  176. @api.multi
  177. def _prepare_risk_account_vals(self, groups):
  178. vals = {
  179. 'risk_invoice_open': 0.0,
  180. 'risk_invoice_unpaid': 0.0,
  181. 'risk_account_amount': 0.0,
  182. 'risk_account_amount_unpaid': 0.0,
  183. }
  184. for reg in groups['open']['read_group']:
  185. if reg['partner_id'][0] != self.id:
  186. continue
  187. if self.property_account_receivable_id.id == reg['account_id'][0]:
  188. vals['risk_invoice_open'] += reg['amount_residual']
  189. else:
  190. vals['risk_account_amount'] += reg['amount_residual']
  191. for reg in groups['unpaid']['read_group']:
  192. if reg['partner_id'][0] != self.id:
  193. continue # pragma: no cover
  194. if self.property_account_receivable_id.id == reg['account_id'][0]:
  195. vals['risk_invoice_unpaid'] += reg['amount_residual']
  196. else:
  197. vals['risk_account_amount_unpaid'] += reg['amount_residual']
  198. return vals
  199. @api.multi
  200. @api.depends(lambda x: x._get_depends_compute_risk_exception())
  201. def _compute_risk_exception(self):
  202. risk_field_list = self._risk_field_list()
  203. for partner in self.filtered('customer'):
  204. amount = 0.0
  205. for risk_field in risk_field_list:
  206. field_value = getattr(partner, risk_field[0], 0.0)
  207. max_value = getattr(partner, risk_field[1], 0.0)
  208. if max_value and field_value > max_value:
  209. partner.risk_exception = True
  210. if getattr(partner, risk_field[2], False):
  211. amount += field_value
  212. partner.risk_total = amount
  213. if partner.credit_limit and amount > partner.credit_limit:
  214. partner.risk_exception = True
  215. @api.model
  216. def _max_risk_date_due(self):
  217. return fields.Date.to_string(datetime.today().date() - relativedelta(
  218. days=self.env.user.company_id.invoice_unpaid_margin))
  219. @api.model
  220. def _risk_field_list(self):
  221. return [
  222. ('risk_invoice_draft', 'risk_invoice_draft_limit',
  223. 'risk_invoice_draft_include'),
  224. ('risk_invoice_open', 'risk_invoice_open_limit',
  225. 'risk_invoice_open_include'),
  226. ('risk_invoice_unpaid', 'risk_invoice_unpaid_limit',
  227. 'risk_invoice_unpaid_include'),
  228. ('risk_account_amount', 'risk_account_amount_limit',
  229. 'risk_account_amount_include'),
  230. ('risk_account_amount_unpaid', 'risk_account_amount_unpaid_limit',
  231. 'risk_account_amount_unpaid_include'),
  232. ]
  233. @api.model
  234. def _get_depends_compute_risk_exception(self):
  235. res = []
  236. for x in self._risk_field_list():
  237. res.extend((x[0], x[1], x[2], 'child_ids.%s' % x[0],
  238. 'child_ids.%s' % x[1], 'child_ids.%s' % x[2]))
  239. res.extend(('credit_limit', 'child_ids.credit_limit'))
  240. return res
  241. @api.model
  242. def process_unpaid_invoices(self):
  243. max_date = self._max_risk_date_due()
  244. ConfigParameter = self.env['ir.config_parameter']
  245. last_check = ConfigParameter.get_param(
  246. 'partner_financial_risk.last_check', default='2016-01-01')
  247. groups = self.env['account.move.line'].sudo().read_group(
  248. [('reconciled', '=', False),
  249. ('partner_id', '!=', False),
  250. ('account_id.internal_type', '=', 'receivable'),
  251. ('date_maturity', '>=', last_check),
  252. ('date_maturity', '<', max_date)],
  253. ['company_id', 'partner_id'],
  254. ['company_id', 'partner_id'],
  255. lazy=False,
  256. )
  257. group_dic = defaultdict(list)
  258. for group in groups:
  259. group_dic[group['company_id'][0]].append(group['partner_id'][0])
  260. for company_id, partner_ids in group_dic.iteritems():
  261. partners = self.browse(partner_ids)
  262. partners.with_context(
  263. force_company=company_id,
  264. )._compute_risk_account_amount()
  265. ConfigParameter.set_param(
  266. 'partner_financial_risk.last_check', max_date)
  267. return True