account_move.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. # -*- coding: utf-8 -*-
  2. """Classes defining the populate factory for Journal Entries, Invoices and related models."""
  3. from odoo import models, fields, Command
  4. from odoo.tools import populate
  5. import logging
  6. import math
  7. from functools import lru_cache
  8. from dateutil.relativedelta import relativedelta
  9. _logger = logging.getLogger(__name__)
  10. class AccountMove(models.Model):
  11. """Populate factory part for account.move.
  12. Because of the complicated nature of the interraction of account.move and account.move.line,
  13. both models are actualy generated in the same factory.
  14. """
  15. _inherit = "account.move"
  16. _populate_sizes = {
  17. 'small': 1000,
  18. 'medium': 10000,
  19. 'large': 500000,
  20. }
  21. _populate_dependencies = ['res.partner', 'account.journal', 'product.product']
  22. def _populate_factories(self):
  23. @lru_cache()
  24. def search_accounts(company_id, types=None):
  25. """Search all the accounts of a certain type for a company.
  26. This method is cached, only one search is done per tuple(company_id, type).
  27. :param company_id (int): the company to search accounts for.
  28. :param type (str): the type to filter on. If not set, do not filter. Valid values are:
  29. payable, receivable, liquidity, other, False.
  30. :return (Model<account.account>): the recordset of accounts found.
  31. """
  32. domain = [('company_id', '=', company_id), ('account_type', '!=', 'off_balance')]
  33. if types:
  34. domain += [('account_type', 'in', types)]
  35. return self.env['account.account'].search(domain)
  36. @lru_cache()
  37. def search_journals(company_id, journal_type, currency_id):
  38. """Search all the journal of a certain type for a company.
  39. This method is cached, only one search is done per tuple(company_id, journal_type).
  40. :param company_id (int): the company to search journals for.
  41. :param journal_type (str): the journal type to filter on.
  42. Valid values are sale, purchase, cash, bank and general.
  43. :param currency_id (int): the currency to search journals for.
  44. :return (list<int>): the ids of the journals of a company and a certain type
  45. """
  46. return self.env['account.journal'].search([
  47. ('company_id', '=', company_id),
  48. ('currency_id', 'in', (False, currency_id)),
  49. ('type', '=', journal_type),
  50. ]).ids
  51. @lru_cache()
  52. def search_products(company_id):
  53. """Search all the products a company has access to.
  54. This method is cached, only one search is done per company_id.
  55. :param company_id (int): the company to search products for.
  56. :return (Model<product.product>): all the products te company has access to
  57. """
  58. return self.env['product.product'].search([
  59. ('company_id', 'in', (False, company_id)),
  60. ('id', 'in', self.env.registry.populated_models['product.product']),
  61. ])
  62. @lru_cache()
  63. def search_partner_ids(company_id):
  64. """Search all the partners that a company has access to.
  65. This method is cached, only one search is done per company_id.
  66. :param company_id (int): the company to search partners for.
  67. :return (list<int>): the ids of partner the company has access to.
  68. """
  69. return self.env['res.partner'].search([
  70. '|', ('company_id', '=', company_id), ('company_id', '=', False),
  71. ('id', 'in', self.env.registry.populated_models['res.partner']),
  72. ]).ids
  73. def get_invoice_date(values, **kwargs):
  74. """Get the invoice date date.
  75. :param values (dict): the values already selected for the record.
  76. :return (datetime.date, bool): the accounting date if it is an invoice (or similar) document
  77. or False otherwise.
  78. """
  79. if values['move_type'] in self.get_invoice_types(include_receipts=True):
  80. return values['date']
  81. return False
  82. def get_lines(random, values, **kwargs):
  83. """Build the dictionary of account.move.line.
  84. Generate lines depending on the move_type, company_id and currency_id.
  85. :param random: seeded random number generator.
  86. :param values (dict): the values already selected for the record.
  87. :return list: list of ORM create commands for the field line_ids
  88. """
  89. def get_entry_line(label, balance=None):
  90. account = random.choice(accounts)
  91. currency = account.currency_id != account.company_id.currency_id and account.currency_id or random.choice(currencies)
  92. if balance is None:
  93. balance = round(random.uniform(-10000, 10000))
  94. return Command.create({
  95. 'name': 'label_%s' % label,
  96. 'balance': balance,
  97. 'account_id': account.id,
  98. 'partner_id': partner_id,
  99. 'currency_id': currency.id,
  100. 'amount_currency': account.company_id.currency_id._convert(balance, currency, account.company_id, date),
  101. })
  102. def get_invoice_line():
  103. return Command.create({
  104. 'product_id': random.choice(products).id,
  105. 'account_id': random.choice(accounts).id,
  106. 'price_unit': round(random.uniform(0, 10000)),
  107. 'quantity': round(random.uniform(0, 100)),
  108. })
  109. move_type = values['move_type']
  110. date = values['date']
  111. company_id = values['company_id']
  112. partner_id = values['partner_id']
  113. # Determine the right sets of accounts depending on the move_type
  114. if move_type in self.get_sale_types(include_receipts=True):
  115. accounts = search_accounts(company_id, ('income',))
  116. elif move_type in self.get_purchase_types(include_receipts=True):
  117. accounts = search_accounts(company_id, ('expense',))
  118. else:
  119. accounts = search_accounts(company_id)
  120. products = search_products(company_id)
  121. if move_type == 'entry':
  122. # Add a random number of lines (between 1 and 20)
  123. lines = [get_entry_line(
  124. label=i,
  125. ) for i in range(random.randint(1, 20))]
  126. # Add a last line containing the balance.
  127. # For invoices, etc., it will be on the receivable/payable account.
  128. lines += [get_entry_line(
  129. balance=-sum(vals['balance'] for _command, _id, vals in lines),
  130. label='balance',
  131. )]
  132. else:
  133. lines = [get_invoice_line() for _i in range(random.randint(1, 20))]
  134. return lines
  135. def get_journal(random, values, **kwargs):
  136. """Get a random journal depending on the company and the move_type.
  137. :param random: seeded random number generator.
  138. :param values (dict): the values already selected for the record.
  139. :return (int): the id of the journal randomly selected
  140. """
  141. move_type = values['move_type']
  142. company_id = values['company_id']
  143. currency_id = values['company_id']
  144. if move_type in self.get_sale_types(include_receipts=True):
  145. journal_type = 'sale'
  146. elif move_type in self.get_purchase_types(include_receipts=True):
  147. journal_type = 'purchase'
  148. else:
  149. journal_type = 'general'
  150. journal = search_journals(company_id, journal_type, currency_id)
  151. return random.choice(journal)
  152. def get_partner(random, values, **kwargs):
  153. """Get a random partner depending on the company and the move_type.
  154. The first 3/5 of the available partners are used as customer
  155. The last 3/5 of the available partners are used as suppliers
  156. It means 1/5 is both customer/supplier
  157. -> Same proportions as in account.payment
  158. :param random: seeded random number generator.
  159. :param values (dict): the values already selected for the record.
  160. :return (int, bool): the id of the partner randomly selected if it is an invoice document
  161. False if it is a Journal Entry.
  162. """
  163. move_type = values['move_type']
  164. company_id = values['company_id']
  165. partner_ids = search_partner_ids(company_id)
  166. if move_type in self.get_sale_types(include_receipts=True):
  167. return random.choice(partner_ids[:math.ceil(len(partner_ids)/5*2)])
  168. if move_type in self.get_purchase_types(include_receipts=True):
  169. return random.choice(partner_ids[math.floor(len(partner_ids)/5*2):])
  170. return False
  171. company_ids = self.env['res.company'].search([
  172. ('chart_template_id', '!=', False),
  173. ('id', 'in', self.env.registry.populated_models['res.company']),
  174. ])
  175. currencies = self.env['res.currency'].search([
  176. ('active', '=', True),
  177. ])
  178. return [
  179. ('move_type', populate.randomize(
  180. ['entry', 'in_invoice', 'out_invoice', 'in_refund', 'out_refund', 'in_receipt', 'out_receipt'],
  181. [0.2, 0.3, 0.3, 0.07, 0.07, 0.03, 0.03],
  182. )),
  183. ('company_id', populate.randomize(company_ids.ids)),
  184. ('currency_id', populate.randomize(currencies.ids)),
  185. ('journal_id', populate.compute(get_journal)),
  186. ('date', populate.randdatetime(relative_before=relativedelta(years=-4), relative_after=relativedelta(years=1))),
  187. ('invoice_date', populate.compute(get_invoice_date)),
  188. ('partner_id', populate.compute(get_partner)),
  189. ('line_ids', populate.compute(get_lines)),
  190. ]
  191. def _populate(self, size):
  192. records = super()._populate(size)
  193. _logger.info('Posting Journal Entries')
  194. to_post = records.filtered(lambda r: r.date < fields.Date.today())
  195. to_post.action_post()
  196. # TODO add some reconciliations. Not done initially because of perfs.
  197. # _logger.info('Registering Payments for Invoices and Bills')
  198. # random = populate.Random('account.move+register_payment')
  199. # for invoice in to_post:
  200. # if invoice.is_invoice() and random.uniform(0, 1) < 0.9: # 90% of invoices are at least partialy paid
  201. # payment_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({})
  202. # if random.uniform(0, 1) > 0.9: # 90% of paid invoices have the exact amount, others vary a little
  203. # payment_wizard.amount *= random.uniform(0.5, 1.5)
  204. # payment_wizard._create_payments()
  205. return records