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
13 KiB

  1. # -*- encoding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Author: 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. from operator import add
  23. from .common_reports import CommonReportHeaderWebkit
  24. class CommonBalanceReportHeaderWebkit(CommonReportHeaderWebkit):
  25. """Define common helper for balance (trial balance, P&L, BS oriented financial report"""
  26. def _get_numbers_display(self, data):
  27. return self._get_form_param('numbers_display', data)
  28. @staticmethod
  29. def find_key_by_value_in_list(dic, value):
  30. return [key for key, val in dic.iteritems() if value in val][0]
  31. def _get_account_details(self, account_ids, target_move, fiscalyear, main_filter, start, stop, initial_balance_mode, context=None):
  32. """
  33. Get details of accounts to display on the report
  34. @param account_ids: ids of accounts to get details
  35. @param target_move: selection filter for moves (all or posted)
  36. @param fiscalyear: browse of the fiscalyear
  37. @param main_filter: selection filter period / date or none
  38. @param start: start date or start period browse instance
  39. @param stop: stop date or stop period browse instance
  40. @param initial_balance_mode: False: no calculation, 'opening_balance': from the opening period, 'initial_balance': computed from previous year / periods
  41. @return: dict of list containing accounts details, keys are the account ids
  42. """
  43. if context is None:
  44. context = {}
  45. account_obj = self.pool.get('account.account')
  46. period_obj = self.pool.get('account.period')
  47. use_period_ids = main_filter in ('filter_no', 'filter_period', 'filter_opening')
  48. if use_period_ids:
  49. if main_filter == 'filter_opening':
  50. period_ids = [start.id]
  51. else:
  52. period_ids = period_obj.build_ctx_periods(self.cursor, self.uid, start.id, stop.id)
  53. # never include the opening in the debit / credit amounts
  54. period_ids = self.exclude_opening_periods(period_ids)
  55. init_balance = False
  56. if initial_balance_mode == 'opening_balance':
  57. init_balance = self._read_opening_balance(account_ids, start)
  58. elif initial_balance_mode:
  59. init_balance = self._compute_initial_balances(account_ids, start, fiscalyear)
  60. ctx = context.copy()
  61. ctx.update({'state': target_move,
  62. 'all_fiscalyear': True})
  63. if use_period_ids:
  64. ctx.update({'periods': period_ids})
  65. elif main_filter == 'filter_date':
  66. ctx.update({'date_from': start,
  67. 'date_to': stop})
  68. accounts = account_obj.read(
  69. self.cursor,
  70. self.uid,
  71. account_ids,
  72. ['type', 'code', 'name', 'debit', 'credit', 'balance', 'parent_id', 'level', 'child_id'],
  73. ctx)
  74. accounts_by_id = {}
  75. for account in accounts:
  76. if init_balance:
  77. # sum for top level views accounts
  78. child_ids = account_obj._get_children_and_consol(self.cursor, self.uid, account['id'], ctx)
  79. if child_ids:
  80. child_init_balances = [
  81. init_bal['init_balance']
  82. for acnt_id, init_bal in init_balance.iteritems()
  83. if acnt_id in child_ids]
  84. top_init_balance = reduce(add, child_init_balances)
  85. account['init_balance'] = top_init_balance
  86. else:
  87. account.update(init_balance[account['id']])
  88. account['balance'] = account['init_balance'] + account['debit'] - account['credit']
  89. accounts_by_id[account['id']] = account
  90. return accounts_by_id
  91. def _get_comparison_details(self, data, account_ids, target_move, comparison_filter, index):
  92. """
  93. @param data: data of the wizard form
  94. @param account_ids: ids of the accounts to get details
  95. @param comparison_filter: selected filter on the form for the comparison (filter_no, filter_year, filter_period, filter_date)
  96. @param index: index of the fields to get (ie. comp1_fiscalyear_id where 1 is the index)
  97. @return: dict of account details (key = account id)
  98. """
  99. fiscalyear = self._get_info(data, "comp%s_fiscalyear_id" % (index,), 'account.fiscalyear')
  100. start_period = self._get_info(data, "comp%s_period_from" % (index,), 'account.period')
  101. stop_period = self._get_info(data, "comp%s_period_to" % (index,), 'account.period')
  102. start_date = self._get_form_param("comp%s_date_from" % (index,), data)
  103. stop_date = self._get_form_param("comp%s_date_to" % (index,), data)
  104. init_balance = self.is_initial_balance_enabled(comparison_filter)
  105. accounts_by_ids = {}
  106. comp_params = {}
  107. details_filter = comparison_filter
  108. if comparison_filter != 'filter_no':
  109. start_period, stop_period, start, stop = \
  110. self._get_start_stop_for_filter(comparison_filter, fiscalyear, start_date, stop_date, start_period, stop_period)
  111. if comparison_filter == 'filter_year':
  112. details_filter = 'filter_no'
  113. initial_balance_mode = init_balance and self._get_initial_balance_mode(start) or False
  114. accounts_by_ids = self._get_account_details(account_ids, target_move, fiscalyear, details_filter,
  115. start, stop, initial_balance_mode)
  116. comp_params = {
  117. 'comparison_filter': comparison_filter,
  118. 'fiscalyear': fiscalyear,
  119. 'start': start,
  120. 'stop': stop,
  121. 'initial_balance': init_balance,
  122. 'initial_balance_mode': initial_balance_mode,
  123. }
  124. return accounts_by_ids, comp_params
  125. def _get_diff(self, balance, previous_balance):
  126. """
  127. @param balance: current balance
  128. @param previous_balance: last balance
  129. @return: dict of form {'diff': difference, 'percent_diff': diff in percentage}
  130. """
  131. diff = balance - previous_balance
  132. obj_precision = self.pool.get('decimal.precision')
  133. precision = obj_precision.precision_get(self.cursor, self.uid, 'Account')
  134. # round previous balance with account precision to avoid big numbers if previous
  135. # balance is 0.0000001 or a any very small number
  136. if round(previous_balance, precision) == 0:
  137. percent_diff = False
  138. else:
  139. percent_diff = round(diff / previous_balance * 100, precision)
  140. return {'diff': diff, 'percent_diff': percent_diff}
  141. def _comp_filters(self, data, comparison_number):
  142. """
  143. @param data: data of the report
  144. @param comparison_number: number of comparisons
  145. @return: list of comparison filters, nb of comparisons used and comparison mode (no_comparison, single, multiple)
  146. """
  147. comp_filters = []
  148. for index in range(comparison_number):
  149. comp_filters.append(self._get_form_param("comp%s_filter" % (index,), data, default='filter_no'))
  150. nb_comparisons = len([comp_filter for comp_filter in comp_filters if comp_filter != 'filter_no'])
  151. if not nb_comparisons:
  152. comparison_mode = 'no_comparison'
  153. elif nb_comparisons > 1:
  154. comparison_mode = 'multiple'
  155. else:
  156. comparison_mode = 'single'
  157. return comp_filters, nb_comparisons, comparison_mode
  158. def _get_start_stop_for_filter(self, main_filter, fiscalyear, start_date, stop_date, start_period, stop_period):
  159. if main_filter in ('filter_no', 'filter_year'):
  160. start_period = self.get_first_fiscalyear_period(fiscalyear)
  161. stop_period = self.get_last_fiscalyear_period(fiscalyear)
  162. elif main_filter == 'filter_opening':
  163. opening_period = self._get_st_fiscalyear_period(fiscalyear, special=True)
  164. start_period = stop_period = opening_period
  165. if main_filter == 'filter_date':
  166. start = start_date
  167. stop = stop_date
  168. else:
  169. start = start_period
  170. stop = stop_period
  171. return start_period, stop_period, start, stop
  172. def compute_balance_data(self, data, filter_report_type=None):
  173. new_ids = data['form']['account_ids'] or data['form']['chart_account_id']
  174. max_comparison = self._get_form_param('max_comparison', data, default=0)
  175. main_filter = self._get_form_param('filter', data, default='filter_no')
  176. comp_filters, nb_comparisons, comparison_mode = self._comp_filters(data, max_comparison)
  177. fiscalyear = self.get_fiscalyear_br(data)
  178. start_period = self.get_start_period_br(data)
  179. stop_period = self.get_end_period_br(data)
  180. target_move = self._get_form_param('target_move', data, default='all')
  181. start_date = self._get_form_param('date_from', data)
  182. stop_date = self._get_form_param('date_to', data)
  183. chart_account = self._get_chart_account_id_br(data)
  184. start_period, stop_period, start, stop = \
  185. self._get_start_stop_for_filter(main_filter, fiscalyear, start_date, stop_date, start_period, stop_period)
  186. init_balance = self.is_initial_balance_enabled(main_filter)
  187. initial_balance_mode = init_balance and self._get_initial_balance_mode(start) or False
  188. # Retrieving accounts
  189. account_ids = self.get_all_accounts(new_ids, only_type=filter_report_type)
  190. # get details for each accounts, total of debit / credit / balance
  191. accounts_by_ids = self._get_account_details(account_ids, target_move, fiscalyear, main_filter, start, stop, initial_balance_mode)
  192. comparison_params = []
  193. comp_accounts_by_ids = []
  194. for index in range(max_comparison):
  195. if comp_filters[index] != 'filter_no':
  196. comparison_result, comp_params = self._get_comparison_details(data, account_ids, target_move, comp_filters[index], index)
  197. comparison_params.append(comp_params)
  198. comp_accounts_by_ids.append(comparison_result)
  199. to_display = dict.fromkeys(account_ids, True)
  200. objects = []
  201. for account in self.pool.get('account.account').browse(self.cursor, self.uid, account_ids):
  202. if not account.parent_id: # hide top level account
  203. continue
  204. if account.type == 'consolidation':
  205. to_display.update(dict([(a.id, False) for a in account.child_consol_ids]))
  206. elif account.type == 'view':
  207. to_display.update(dict([(a.id, True) for a in account.child_id]))
  208. account.debit = accounts_by_ids[account.id]['debit']
  209. account.credit = accounts_by_ids[account.id]['credit']
  210. account.balance = accounts_by_ids[account.id]['balance']
  211. account.init_balance = accounts_by_ids[account.id].get('init_balance', 0.0)
  212. display_account = False # if any amount is != 0 in comparisons, we have to display the whole account
  213. comp_accounts = []
  214. for comp_account_by_id in comp_accounts_by_ids:
  215. values = comp_account_by_id.get(account.id)
  216. values.update(self._get_diff(account.balance, values['balance']))
  217. display_account = any((values.get('credit', 0.0), values.get('debit', 0.0), values.get('balance', 0.0), values.get('init_balance', 0.0)))
  218. comp_accounts.append(values)
  219. account.comparisons = comp_accounts
  220. # we have to display the account if a comparison as an amount or if we have an amount in the main column
  221. # we set it as a property to let the data in the report if someone want to use it in a custom report
  222. display_account = display_account or any((account.debit, account.credit, account.balance, account.init_balance))
  223. to_display.update({account.id: display_account and to_display[account.id]})
  224. objects.append(account)
  225. for account in objects:
  226. account.to_display = to_display[account.id]
  227. context_report_values = {
  228. 'fiscalyear': fiscalyear,
  229. 'start_date': start_date,
  230. 'stop_date': stop_date,
  231. 'start_period': start_period,
  232. 'stop_period': stop_period,
  233. 'chart_account': chart_account,
  234. 'comparison_mode': comparison_mode,
  235. 'nb_comparison': nb_comparisons,
  236. 'initial_balance': init_balance,
  237. 'initial_balance_mode': initial_balance_mode,
  238. 'comp_params': comparison_params,
  239. }
  240. return objects, new_ids, context_report_values