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.

252 lines
12 KiB

  1. # -*- encoding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Author: Nicolas Bessi, Guewen Baconnier
  5. # Copyright Camptocamp SA 2011
  6. # SQL inspired from OpenERP original code
  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. # TODO refactor helper in order to act more like mixin
  23. # By using properties we will have a more simple signature in fuctions
  24. from collections import defaultdict
  25. from common_reports import CommonReportHeaderWebkit
  26. class CommonPartnersReportHeaderWebkit(CommonReportHeaderWebkit):
  27. """Define common helper for partner oriented financial report"""
  28. ####################Account move line retrieval helper ##########################
  29. def get_partners_move_lines_ids(self, account_id, main_filter, start, stop, target_move,
  30. exclude_reconcile=False, partner_filter=False):
  31. filter_from = False
  32. if main_filter in ('filter_period', 'filter_no'):
  33. filter_from = 'period'
  34. elif main_filter == 'filter_date':
  35. filter_from = 'date'
  36. if filter_from:
  37. return self._get_partners_move_line_ids(filter_from,
  38. account_id,
  39. start,
  40. stop,
  41. target_move,
  42. exclude_reconcile=exclude_reconcile,
  43. partner_filter=partner_filter)
  44. def _get_query_params_from_periods(self, period_start, period_stop, mode='exclude_opening'):
  45. # we do not want opening period so we exclude opening
  46. periods = self.pool.get('account.period').build_ctx_periods(self.cr, self.uid, period_start.id, period_stop.id)
  47. if not periods:
  48. return []
  49. if mode == 'exclude_opening':
  50. periods = self.exclude_opening_periods(periods)
  51. search_params = {'period_ids': tuple(periods),
  52. 'date_stop': period_stop.date_stop}
  53. sql_conditions = " AND account_move_line.period_id in %(period_ids)s"
  54. return sql_conditions, search_params
  55. def _get_query_params_from_dates(self, date_start, date_stop, **args):
  56. periods = self._get_opening_periods()
  57. if not periods:
  58. periods = (-1,)
  59. search_params = {'period_ids': tuple(periods),
  60. 'date_start': date_start,
  61. 'date_stop': date_stop}
  62. sql_conditions = " AND account_move_line.period_id not in %(period_ids)s" \
  63. " AND account_move_line.date between date(%(date_start)s) and date((%(date_stop)s))"
  64. return sql_conditions, search_params
  65. def _get_partners_move_line_ids(self, filter_from, account_id, start, stop,
  66. target_move, opening_mode='exclude_opening',
  67. exclude_reconcile=False, partner_filter=False):
  68. final_res = defaultdict(list)
  69. sql_select = "SELECT account_move_line.id, account_move_line.partner_id FROM account_move_line"
  70. sql_joins = ''
  71. sql_where = " WHERE account_move_line.account_id = %(account_ids)s " \
  72. " AND account_move_line.state = 'valid' "
  73. sql_conditions, search_params = getattr(self, '_get_query_params_from_'+filter_from+'s')(start, stop, mode=opening_mode)
  74. sql_where += sql_conditions
  75. if exclude_reconcile:
  76. sql_where += (" AND ((account_move_line.reconcile_id IS NULL)"
  77. " OR (account_move_line.reconcile_id IS NOT NULL AND account_move_line.last_rec_date > date(%(date_stop)s)))")
  78. if partner_filter:
  79. sql_where += " AND account_move_line.partner_id in %(partner_ids)s"
  80. if target_move == 'posted':
  81. sql_joins += "INNER JOIN account_move ON account_move_line.move_id = account_move.id"
  82. sql_where += " AND account_move.state = %(target_move)s"
  83. search_params.update({'target_move': target_move,})
  84. search_params.update({
  85. 'account_ids': account_id,
  86. 'partner_ids': tuple(partner_filter),
  87. })
  88. sql = ' '.join((sql_select, sql_joins, sql_where))
  89. self.cursor.execute(sql, search_params)
  90. res = self.cursor.dictfetchall()
  91. if res:
  92. for row in res:
  93. final_res[row['partner_id']].append(row['id'])
  94. return final_res
  95. def _get_clearance_move_line_ids(self, move_line_ids, date_stop, date_until):
  96. if not move_line_ids:
  97. return []
  98. move_line_obj = self.pool.get('account.move.line')
  99. # we do not use orm in order to gain perfo
  100. # In this case I have to test the effective gain over an itteration
  101. # Actually ORM does not allows distinct behavior
  102. sql = "Select distinct reconcile_id from account_move_line where id in %s"
  103. self.cursor.execute(sql, (tuple(move_line_ids),))
  104. rec_ids = self.cursor.fetchall()
  105. if rec_ids:
  106. rec_ids = [x[0] for x in rec_ids]
  107. l_ids = move_line_obj.search(self.cursor,
  108. self.uid,
  109. [('reconcile_id', 'in', rec_ids),
  110. ('date', '>=', date_stop),
  111. ('date', '<=', date_until)])
  112. return l_ids
  113. else:
  114. return []
  115. ####################Initial Partner Balance helper ########################
  116. def _tree_move_line_ids(self, move_lines_data, key=None):
  117. """
  118. move_lines_data must be a list of dict which contains at least keys :
  119. - account_id
  120. - partner_id
  121. - other keys with values of the line
  122. - if param key is defined, only this key will be inserted in the tree
  123. returns a tree like
  124. res[account_id.1][partner_id.1][move_line.1,
  125. move_line.2]
  126. [partner_id.2][move_line.3]
  127. res[account_id.2][partner_id.1][move_line.4]
  128. """
  129. res = defaultdict(dict)
  130. for row in move_lines_data[:]:
  131. account_id = row.pop('account_id')
  132. partner_id = row.pop('partner_id')
  133. if key:
  134. res[account_id].setdefault(partner_id, []).append(row[key])
  135. else:
  136. res[account_id][partner_id] = row
  137. return res
  138. def _partners_initial_balance_line_ids(self, account_ids, start_period, partner_filter, exclude_reconcile=False, force_period_ids=False, date_stop=None):
  139. # take ALL previous periods
  140. period_ids = force_period_ids \
  141. if force_period_ids \
  142. else self._get_period_range_from_start_period(start_period, fiscalyear=False, include_opening=False)
  143. if not period_ids:
  144. period_ids = [-1]
  145. search_param = {'date_start': start_period.date_start,
  146. 'period_ids': tuple(period_ids),
  147. 'account_ids': tuple(account_ids),}
  148. sql = ("SELECT ml.id, ml.account_id, ml.partner_id "
  149. "FROM account_move_line ml "
  150. "INNER JOIN account_account a "
  151. "ON a.id = ml.account_id "
  152. "WHERE ml.period_id in %(period_ids)s "
  153. "AND ml.account_id in %(account_ids)s ")
  154. if exclude_reconcile:
  155. if not date_stop:
  156. raise Exception("Missing \"date_stop\" to compute the open invoices.")
  157. search_param.update({'date_stop': date_stop})
  158. sql += ("AND ((ml.reconcile_id IS NULL)"
  159. "OR (ml.reconcile_id IS NOT NULL AND ml.last_rec_date > date(%(date_stop)s))) ")
  160. if partner_filter:
  161. sql += "AND ml.partner_id in %(partner_ids)s "
  162. search_param.update({'partner_ids': tuple(partner_filter)})
  163. self.cursor.execute(sql, search_param)
  164. return self.cursor.dictfetchall()
  165. def _compute_partners_initial_balances(self, account_ids, start_period, partner_filter=None, exclude_reconcile=False, force_period_ids=False):
  166. """We compute initial balance.
  167. If form is filtered by date all initial balance are equal to 0
  168. This function will sum pear and apple in currency amount if account as no secondary currency"""
  169. if isinstance(account_ids, (int, long)):
  170. account_ids = [account_ids]
  171. move_line_ids = self._partners_initial_balance_line_ids(account_ids, start_period, partner_filter,
  172. exclude_reconcile=exclude_reconcile,
  173. force_period_ids=force_period_ids)
  174. if not move_line_ids:
  175. move_line_ids = [{'id': -1}]
  176. sql = ("SELECT ml.account_id, ml.partner_id,"
  177. " sum(ml.debit) as debit, sum(ml.credit) as credit,"
  178. " sum(ml.debit-ml.credit) as init_balance,"
  179. " CASE WHEN a.currency_id ISNULL THEN 0.0 ELSE sum(ml.amount_currency) END as init_balance_currency, "
  180. " c.name as currency_name "
  181. "FROM account_move_line ml "
  182. "INNER JOIN account_account a "
  183. "ON a.id = ml.account_id "
  184. "LEFT JOIN res_currency c "
  185. "ON c.id = a.currency_id "
  186. "WHERE ml.id in %(move_line_ids)s "
  187. "GROUP BY ml.account_id, ml.partner_id, a.currency_id, c.name")
  188. search_param = {'move_line_ids': tuple([move_line['id'] for move_line in move_line_ids])}
  189. self.cursor.execute(sql, search_param)
  190. res = self.cursor.dictfetchall()
  191. return self._tree_move_line_ids(res)
  192. ####################Partner specific helper ################################
  193. def _order_partners(self, *args):
  194. """We get the partner linked to all current accounts that are used.
  195. We also use ensure that partner are ordered by name
  196. args must be list"""
  197. res = []
  198. partner_ids = []
  199. for arg in args:
  200. if arg:
  201. partner_ids += arg
  202. if not partner_ids:
  203. return []
  204. existing_partner_ids = [partner_id for partner_id in partner_ids if partner_id]
  205. if existing_partner_ids:
  206. # We may use orm here as the performance optimization is not that big
  207. sql = ("SELECT name|| ' ' ||CASE WHEN ref IS NOT NULL THEN '('||ref||')' ELSE '' END, id, ref, name"
  208. " FROM res_partner WHERE id IN %s ORDER BY LOWER(name), ref")
  209. self.cursor.execute(sql, (tuple(set(existing_partner_ids)),))
  210. res = self.cursor.fetchall()
  211. # move lines without partners, set None for empty partner
  212. if not all(partner_ids):
  213. res.append((None, None, None, None))
  214. if not res:
  215. return []
  216. return res