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.

251 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, target_move, opening_mode='include_opening',
  66. exclude_reconcile=False, partner_filter=False):
  67. final_res = defaultdict(list)
  68. sql_select = "SELECT account_move_line.id, account_move_line.partner_id FROM account_move_line"
  69. sql_joins = ''
  70. sql_where = " WHERE account_move_line.account_id = %(account_ids)s " \
  71. " AND account_move_line.state = 'valid' "
  72. sql_conditions, search_params = getattr(self, '_get_query_params_from_'+filter_from+'s')(start, stop, mode=opening_mode)
  73. sql_where += sql_conditions
  74. if exclude_reconcile:
  75. sql_where += (" AND ((account_move_line.reconcile_id IS NULL)"
  76. " OR (account_move_line.reconcile_id IS NOT NULL AND account_move_line.last_rec_date > date(%(date_stop)s)))")
  77. if partner_filter:
  78. sql_where += " AND account_move_line.partner_id in %(partner_ids)s"
  79. if target_move == 'posted':
  80. sql_joins += "INNER JOIN account_move ON account_move_line.move_id = account_move.id"
  81. sql_where += " AND account_move.state = %(target_move)s"
  82. search_params.update({'target_move': target_move,})
  83. search_params.update({
  84. 'account_ids': account_id,
  85. 'partner_ids': tuple(partner_filter),
  86. })
  87. sql = ' '.join((sql_select, sql_joins, sql_where))
  88. self.cursor.execute(sql, search_params)
  89. res = self.cursor.dictfetchall()
  90. if res:
  91. for row in res:
  92. final_res[row['partner_id']].append(row['id'])
  93. return final_res
  94. def _get_clearance_move_line_ids(self, move_line_ids, date_stop, date_until):
  95. if not move_line_ids:
  96. return []
  97. move_line_obj = self.pool.get('account.move.line')
  98. # we do not use orm in order to gain perfo
  99. # In this case I have to test the effective gain over an itteration
  100. # Actually ORM does not allows distinct behavior
  101. sql = "Select distinct reconcile_id from account_move_line where id in %s"
  102. self.cursor.execute(sql, (tuple(move_line_ids),))
  103. rec_ids = self.cursor.fetchall()
  104. if rec_ids:
  105. rec_ids = [x[0] for x in rec_ids]
  106. l_ids = move_line_obj.search(self.cursor,
  107. self.uid,
  108. [('reconcile_id', 'in', rec_ids),
  109. ('date', '>=', date_stop),
  110. ('date', '<=', date_until)])
  111. return l_ids
  112. else:
  113. return []
  114. ####################Initial Partner Balance helper ########################
  115. def _tree_move_line_ids(self, move_lines_data, key=None):
  116. """
  117. move_lines_data must be a list of dict which contains at least keys :
  118. - account_id
  119. - partner_id
  120. - other keys with values of the line
  121. - if param key is defined, only this key will be inserted in the tree
  122. returns a tree like
  123. res[account_id.1][partner_id.1][move_line.1,
  124. move_line.2]
  125. [partner_id.2][move_line.3]
  126. res[account_id.2][partner_id.1][move_line.4]
  127. """
  128. res = defaultdict(dict)
  129. for row in move_lines_data[:]:
  130. account_id = row.pop('account_id')
  131. partner_id = row.pop('partner_id')
  132. if key:
  133. res[account_id].setdefault(partner_id, []).append(row[key])
  134. else:
  135. res[account_id][partner_id] = row
  136. return res
  137. def _partners_initial_balance_line_ids(self, account_ids, start_period, partner_filter, exclude_reconcile=False, force_period_ids=False, date_stop=None):
  138. # take ALL previous periods
  139. period_ids = force_period_ids \
  140. if force_period_ids \
  141. else self._get_period_range_from_start_period(start_period, fiscalyear=False, include_opening=False)
  142. if not period_ids:
  143. period_ids = [-1]
  144. search_param = {'date_start': start_period.date_start,
  145. 'period_ids': tuple(period_ids),
  146. 'account_ids': tuple(account_ids),}
  147. sql = ("SELECT ml.id, ml.account_id, ml.partner_id "
  148. "FROM account_move_line ml "
  149. "INNER JOIN account_account a "
  150. "ON a.id = ml.account_id "
  151. "WHERE ml.period_id in %(period_ids)s "
  152. "AND ml.account_id in %(account_ids)s ")
  153. if exclude_reconcile:
  154. if not date_stop:
  155. raise Exception("Missing \"date_stop\" to compute the open invoices.")
  156. search_param.update({'date_stop': date_stop})
  157. sql += ("AND ((ml.reconcile_id IS NULL)"
  158. "OR (ml.reconcile_id IS NOT NULL AND ml.last_rec_date > date(%(date_stop)s))) ")
  159. if partner_filter:
  160. sql += "AND ml.partner_id in %(partner_ids)s "
  161. search_param.update({'partner_ids': tuple(partner_filter)})
  162. self.cursor.execute(sql, search_param)
  163. return self.cursor.dictfetchall()
  164. def _compute_partners_initial_balances(self, account_ids, start_period, partner_filter=None, exclude_reconcile=False, force_period_ids=False):
  165. """We compute initial balance.
  166. If form is filtered by date all initial balance are equal to 0
  167. This function will sum pear and apple in currency amount if account as no secondary currency"""
  168. if isinstance(account_ids, (int, long)):
  169. account_ids = [account_ids]
  170. move_line_ids = self._partners_initial_balance_line_ids(account_ids, start_period, partner_filter,
  171. exclude_reconcile=exclude_reconcile,
  172. force_period_ids=force_period_ids)
  173. if not move_line_ids:
  174. move_line_ids = [{'id': -1}]
  175. sql = ("SELECT ml.account_id, ml.partner_id,"
  176. " sum(ml.debit) as debit, sum(ml.credit) as credit,"
  177. " sum(ml.debit-ml.credit) as init_balance,"
  178. " CASE WHEN a.currency_id ISNULL THEN 0.0 ELSE sum(ml.amount_currency) END as init_balance_currency, "
  179. " c.name as currency_name "
  180. "FROM account_move_line ml "
  181. "INNER JOIN account_account a "
  182. "ON a.id = ml.account_id "
  183. "LEFT JOIN res_currency c "
  184. "ON c.id = a.currency_id "
  185. "WHERE ml.id in %(move_line_ids)s "
  186. "GROUP BY ml.account_id, ml.partner_id, a.currency_id, c.name")
  187. search_param = {'move_line_ids': tuple([move_line['id'] for move_line in move_line_ids])}
  188. self.cursor.execute(sql, search_param)
  189. res = self.cursor.dictfetchall()
  190. return self._tree_move_line_ids(res)
  191. ####################Partner specific helper ################################
  192. def _order_partners(self, *args):
  193. """We get the partner linked to all current accounts that are used.
  194. We also use ensure that partner are ordered by name
  195. args must be list"""
  196. res = []
  197. partner_ids = []
  198. for arg in args:
  199. if arg:
  200. partner_ids += arg
  201. if not partner_ids:
  202. return []
  203. existing_partner_ids = [partner_id for partner_id in partner_ids if partner_id]
  204. if existing_partner_ids:
  205. # We may use orm here as the performance optimization is not that big
  206. sql = ("SELECT name|| ' ' ||CASE WHEN ref IS NOT NULL THEN '('||ref||')' ELSE '' END, id, ref, name"
  207. " FROM res_partner WHERE id IN %s ORDER BY name, ref")
  208. self.cursor.execute(sql, (tuple(set(existing_partner_ids)),))
  209. res = self.cursor.fetchall()
  210. # move lines without partners, set None for empty partner
  211. if not all(partner_ids):
  212. res.append((None, None, None, None))
  213. if not res:
  214. return []
  215. return res