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.

283 lines
10 KiB

  1. # -*- encoding: utf-8 -*-
  2. ###############################################################################
  3. #
  4. # OpenERP, Open Source Management Solution
  5. # This module copyright (C) 2010 - 2014 Savoir-faire Linux
  6. # (<http://www.savoirfairelinux.com>).
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU Affero General Public License as
  10. # published by the Free Software Foundation, either version 3 of the
  11. # License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Affero General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Affero General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. ###############################################################################
  22. import time
  23. import pytz
  24. from datetime import datetime
  25. from dateutil.relativedelta import relativedelta
  26. import itertools
  27. from openerp import pooler
  28. from openerp.report import report_sxw
  29. from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
  30. from openerp.addons.account.report.account_aged_partner_balance import (
  31. aged_trial_report
  32. )
  33. class PartnerAgedTrialReport(aged_trial_report):
  34. _partner = None
  35. def __init__(self, cr, uid, name, context):
  36. super(PartnerAgedTrialReport, self).__init__(cr, uid, name, context)
  37. current_user = self.localcontext["user"]
  38. self._company = current_user.company_id
  39. if self.localcontext.get("active_model", "") == "res.partner":
  40. self._partners = self.localcontext["active_ids"]
  41. self.localcontext.update({
  42. 'message': self._message,
  43. 'getLines30': self._lines_get_30,
  44. 'getLines3060': self._lines_get_30_60,
  45. 'getLines60': self._lines_get_60,
  46. 'show_message': True,
  47. 'get_current_invoice_lines': self._get_current_invoice_lines,
  48. 'get_balance': self._get_balance,
  49. })
  50. self.partner_invoices_dict = {}
  51. self.ttype = 'receipt'
  52. tz = self.localcontext.get('tz', False)
  53. tz = tz and pytz.timezone(tz) or pytz.utc
  54. self.today = datetime.now(tz)
  55. def _get_balance(self, partner, company):
  56. """
  57. Get the lines of balance to display in the report
  58. """
  59. today = self.today
  60. date_30 = today - relativedelta(days=30)
  61. date_60 = today - relativedelta(days=60)
  62. date_90 = today - relativedelta(days=90)
  63. date_120 = today - relativedelta(days=120)
  64. today = today.strftime(DEFAULT_SERVER_DATE_FORMAT)
  65. date_30 = date_30.strftime(DEFAULT_SERVER_DATE_FORMAT)
  66. date_60 = date_60.strftime(DEFAULT_SERVER_DATE_FORMAT)
  67. date_90 = date_90.strftime(DEFAULT_SERVER_DATE_FORMAT)
  68. date_120 = date_120.strftime(DEFAULT_SERVER_DATE_FORMAT)
  69. movelines = self._get_current_invoice_lines(partner, company, today)
  70. movelines = sorted(movelines, key=lambda x: x['currency_name'])
  71. grouped_movelines = [
  72. (key, list(group)) for key, group in itertools.groupby(
  73. movelines, key=lambda x: x['currency_name'])
  74. ]
  75. res = {}
  76. for currency_name, lines in grouped_movelines:
  77. res[currency_name] = {
  78. 'not_due': 0,
  79. '30': 0,
  80. '3060': 0,
  81. '6090': 0,
  82. '90120': 0,
  83. '120': 0,
  84. 'total': 0,
  85. 'currency_name': currency_name,
  86. }
  87. current_dict = res[currency_name]
  88. for line in lines:
  89. amount = line['amount_unreconciled']
  90. if line['date_due'] > today or not line['date_due']:
  91. current_dict['not_due'] += amount
  92. elif line['date_due'] > date_30:
  93. current_dict['30'] += amount
  94. elif line['date_due'] > date_60:
  95. current_dict['3060'] += amount
  96. elif line['date_due'] > date_90:
  97. current_dict['6090'] += amount
  98. elif line['date_due'] > date_120:
  99. current_dict['90120'] += amount
  100. elif line['date_due']:
  101. current_dict['120'] += amount
  102. current_dict['total'] += amount
  103. return res.values()
  104. def _get_current_invoice_lines(self, partner, company, date):
  105. """
  106. Get all invoices with unpaid amounts for the given supplier
  107. :return: a list of dict containing invoice data
  108. {
  109. 'date_due': the date of maturity
  110. 'date_original': the date of the invoice
  111. 'ref': the partner reference on the invoice
  112. 'amount_original': the original date
  113. 'amount_unreconciled': the amount left to pay
  114. 'currency_id': the currency of the invoice
  115. 'currency_name': the name of the currency
  116. 'name': the number of the invoice
  117. }
  118. """
  119. # Only compute this method one time per partner
  120. if partner.id in self.partner_invoices_dict:
  121. return self.partner_invoices_dict[partner.id]
  122. pool = pooler.get_pool(self.cr.dbname)
  123. cr, uid, context = self.cr, self.uid, self.localcontext
  124. inv_obj = pool['account.invoice']
  125. invoice_ids = inv_obj.search(cr, uid, [
  126. ('state', '=', 'open'),
  127. ('company_id', '=', company.id),
  128. ('partner_id', '=', partner.id),
  129. ('type', '=', 'out_invoice'
  130. if self.ttype == 'receipt' else 'in_invoice'),
  131. ], context=context)
  132. invoices = inv_obj.browse(cr, uid, invoice_ids, context=context)
  133. return [
  134. {
  135. 'date_due': inv.date_due or '',
  136. 'date_original': inv.date_invoice or '',
  137. 'amount_original': inv.amount_total,
  138. 'amount_unreconciled': inv.residual,
  139. 'currency_id': inv.currency_id.id,
  140. 'currency_name': inv.currency_id.name,
  141. 'name': inv.number,
  142. 'ref': inv.reference or '',
  143. 'id': inv.id,
  144. } for inv in invoices
  145. ]
  146. def _lines_get_30(self, partner, company):
  147. today = self.today
  148. stop = today - relativedelta(days=30)
  149. today = today.strftime(DEFAULT_SERVER_DATE_FORMAT)
  150. stop = stop.strftime(DEFAULT_SERVER_DATE_FORMAT)
  151. movelines = self._get_current_invoice_lines(partner, company, today)
  152. movelines = [
  153. line for line in movelines
  154. if not line['date_due'] or stop < line['date_due'] <= today
  155. ]
  156. return movelines
  157. def _lines_get_30_60(self, partner, company):
  158. today = self.today
  159. start = today - relativedelta(days=30)
  160. stop = start - relativedelta(days=30)
  161. today = today.strftime(DEFAULT_SERVER_DATE_FORMAT)
  162. start = start.strftime(DEFAULT_SERVER_DATE_FORMAT)
  163. stop = stop.strftime(DEFAULT_SERVER_DATE_FORMAT)
  164. movelines = self._get_current_invoice_lines(partner, company, today)
  165. movelines = [
  166. line for line in movelines
  167. if line['date_due'] and stop < line['date_due'] <= start
  168. ]
  169. return movelines
  170. def _lines_get_60(self, partner, company):
  171. today = self.today
  172. start = today - relativedelta(days=60)
  173. today = today.strftime(DEFAULT_SERVER_DATE_FORMAT)
  174. start = start.strftime(DEFAULT_SERVER_DATE_FORMAT)
  175. movelines = self._get_current_invoice_lines(partner, company, today)
  176. movelines = [
  177. line for line in movelines
  178. if line['date_due'] and line['date_due'] <= start
  179. ]
  180. return movelines
  181. def _message(self, obj, company):
  182. company_pool = pooler.get_pool(self.cr.dbname)['res.company']
  183. message = company_pool.browse(
  184. self.cr, self.uid, company.id, {'lang': obj.lang}).overdue_msg
  185. return message and message.split('\n') or ''
  186. def _get_company(self, data):
  187. return self._company.name
  188. def _get_currency(self, data):
  189. return self._company.currency_id.symbol
  190. def set_context(self, objects, data, ids, report_type=None):
  191. period_length = 30
  192. form = {
  193. "direction_selection": "past",
  194. "period_length": period_length,
  195. "result_selection": "customer",
  196. "date_from": time.strftime("%Y-%m-%d"),
  197. }
  198. # Taken from 'account/wizard/account_report_aged_partner_balance.py
  199. # which sets data from the form
  200. start = datetime.now()
  201. for i in range(4, -1, -1):
  202. stop = start - relativedelta(days=period_length)
  203. form[str(i)] = {
  204. 'name': (i != 0 and "{0}-{1}".format(
  205. 5 - (i + 1), (5 - i) * period_length,
  206. ) or '+{0}'.format(4 * period_length)),
  207. 'stop': start.strftime('%Y-%m-%d'),
  208. 'start': (i != 0 and stop.strftime('%Y-%m-%d') or False),
  209. }
  210. start = stop - relativedelta(days=1)
  211. data["form"] = form
  212. res = super(PartnerAgedTrialReport, self).set_context(
  213. objects, data, ids, report_type=report_type)
  214. self.orig_query = self.query
  215. if self._partner is not None:
  216. self.query = "{0} AND l.partner_id = {1}".format(
  217. self.query,
  218. ", ".join(str(int(i)) for i in self._partners),
  219. )
  220. self.ACCOUNT_TYPE = ['receivable']
  221. return res
  222. def _get_lines(self, form, partner):
  223. # self.query is used to get the lines in super()._get_lines
  224. self.query = "{0} AND l.partner_id = {1}".format(
  225. self.orig_query,
  226. partner.id,)
  227. res = super(PartnerAgedTrialReport, self)._get_lines(form)
  228. self.query = self.orig_query
  229. return res
  230. report_sxw.report_sxw(
  231. 'report.webkit.partner_aged_statement_report',
  232. 'res.partner',
  233. ('addons/'
  234. 'account_partner_aged_statement_webkit/'
  235. 'report/'
  236. 'partner_aged_statement.mako'),
  237. parser=PartnerAgedTrialReport,
  238. )