123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- # -*- coding: utf-8 -*-
- """Classes defining the populate factory for Journal Entries, Invoices and related models."""
- from odoo import models, fields, Command
- from odoo.tools import populate
- import logging
- import math
- from functools import lru_cache
- from dateutil.relativedelta import relativedelta
- _logger = logging.getLogger(__name__)
- class AccountMove(models.Model):
- """Populate factory part for account.move.
- Because of the complicated nature of the interraction of account.move and account.move.line,
- both models are actualy generated in the same factory.
- """
- _inherit = "account.move"
- _populate_sizes = {
- 'small': 1000,
- 'medium': 10000,
- 'large': 500000,
- }
- _populate_dependencies = ['res.partner', 'account.journal', 'product.product']
- def _populate_factories(self):
- @lru_cache()
- def search_accounts(company_id, types=None):
- """Search all the accounts of a certain type for a company.
- This method is cached, only one search is done per tuple(company_id, type).
- :param company_id (int): the company to search accounts for.
- :param type (str): the type to filter on. If not set, do not filter. Valid values are:
- payable, receivable, liquidity, other, False.
- :return (Model<account.account>): the recordset of accounts found.
- """
- domain = [('company_id', '=', company_id), ('account_type', '!=', 'off_balance')]
- if types:
- domain += [('account_type', 'in', types)]
- return self.env['account.account'].search(domain)
- @lru_cache()
- def search_journals(company_id, journal_type, currency_id):
- """Search all the journal of a certain type for a company.
- This method is cached, only one search is done per tuple(company_id, journal_type).
- :param company_id (int): the company to search journals for.
- :param journal_type (str): the journal type to filter on.
- Valid values are sale, purchase, cash, bank and general.
- :param currency_id (int): the currency to search journals for.
- :return (list<int>): the ids of the journals of a company and a certain type
- """
- return self.env['account.journal'].search([
- ('company_id', '=', company_id),
- ('currency_id', 'in', (False, currency_id)),
- ('type', '=', journal_type),
- ]).ids
- @lru_cache()
- def search_products(company_id):
- """Search all the products a company has access to.
- This method is cached, only one search is done per company_id.
- :param company_id (int): the company to search products for.
- :return (Model<product.product>): all the products te company has access to
- """
- return self.env['product.product'].search([
- ('company_id', 'in', (False, company_id)),
- ('id', 'in', self.env.registry.populated_models['product.product']),
- ])
- @lru_cache()
- def search_partner_ids(company_id):
- """Search all the partners that a company has access to.
- This method is cached, only one search is done per company_id.
- :param company_id (int): the company to search partners for.
- :return (list<int>): the ids of partner the company has access to.
- """
- return self.env['res.partner'].search([
- '|', ('company_id', '=', company_id), ('company_id', '=', False),
- ('id', 'in', self.env.registry.populated_models['res.partner']),
- ]).ids
- def get_invoice_date(values, **kwargs):
- """Get the invoice date date.
- :param values (dict): the values already selected for the record.
- :return (datetime.date, bool): the accounting date if it is an invoice (or similar) document
- or False otherwise.
- """
- if values['move_type'] in self.get_invoice_types(include_receipts=True):
- return values['date']
- return False
- def get_lines(random, values, **kwargs):
- """Build the dictionary of account.move.line.
- Generate lines depending on the move_type, company_id and currency_id.
- :param random: seeded random number generator.
- :param values (dict): the values already selected for the record.
- :return list: list of ORM create commands for the field line_ids
- """
- def get_entry_line(label, balance=None):
- account = random.choice(accounts)
- currency = account.currency_id != account.company_id.currency_id and account.currency_id or random.choice(currencies)
- if balance is None:
- balance = round(random.uniform(-10000, 10000))
- return Command.create({
- 'name': 'label_%s' % label,
- 'balance': balance,
- 'account_id': account.id,
- 'partner_id': partner_id,
- 'currency_id': currency.id,
- 'amount_currency': account.company_id.currency_id._convert(balance, currency, account.company_id, date),
- })
- def get_invoice_line():
- return Command.create({
- 'product_id': random.choice(products).id,
- 'account_id': random.choice(accounts).id,
- 'price_unit': round(random.uniform(0, 10000)),
- 'quantity': round(random.uniform(0, 100)),
- })
- move_type = values['move_type']
- date = values['date']
- company_id = values['company_id']
- partner_id = values['partner_id']
- # Determine the right sets of accounts depending on the move_type
- if move_type in self.get_sale_types(include_receipts=True):
- accounts = search_accounts(company_id, ('income',))
- elif move_type in self.get_purchase_types(include_receipts=True):
- accounts = search_accounts(company_id, ('expense',))
- else:
- accounts = search_accounts(company_id)
- products = search_products(company_id)
- if move_type == 'entry':
- # Add a random number of lines (between 1 and 20)
- lines = [get_entry_line(
- label=i,
- ) for i in range(random.randint(1, 20))]
- # Add a last line containing the balance.
- # For invoices, etc., it will be on the receivable/payable account.
- lines += [get_entry_line(
- balance=-sum(vals['balance'] for _command, _id, vals in lines),
- label='balance',
- )]
- else:
- lines = [get_invoice_line() for _i in range(random.randint(1, 20))]
- return lines
- def get_journal(random, values, **kwargs):
- """Get a random journal depending on the company and the move_type.
- :param random: seeded random number generator.
- :param values (dict): the values already selected for the record.
- :return (int): the id of the journal randomly selected
- """
- move_type = values['move_type']
- company_id = values['company_id']
- currency_id = values['company_id']
- if move_type in self.get_sale_types(include_receipts=True):
- journal_type = 'sale'
- elif move_type in self.get_purchase_types(include_receipts=True):
- journal_type = 'purchase'
- else:
- journal_type = 'general'
- journal = search_journals(company_id, journal_type, currency_id)
- return random.choice(journal)
- def get_partner(random, values, **kwargs):
- """Get a random partner depending on the company and the move_type.
- The first 3/5 of the available partners are used as customer
- The last 3/5 of the available partners are used as suppliers
- It means 1/5 is both customer/supplier
- -> Same proportions as in account.payment
- :param random: seeded random number generator.
- :param values (dict): the values already selected for the record.
- :return (int, bool): the id of the partner randomly selected if it is an invoice document
- False if it is a Journal Entry.
- """
- move_type = values['move_type']
- company_id = values['company_id']
- partner_ids = search_partner_ids(company_id)
- if move_type in self.get_sale_types(include_receipts=True):
- return random.choice(partner_ids[:math.ceil(len(partner_ids)/5*2)])
- if move_type in self.get_purchase_types(include_receipts=True):
- return random.choice(partner_ids[math.floor(len(partner_ids)/5*2):])
- return False
- company_ids = self.env['res.company'].search([
- ('chart_template_id', '!=', False),
- ('id', 'in', self.env.registry.populated_models['res.company']),
- ])
- currencies = self.env['res.currency'].search([
- ('active', '=', True),
- ])
- return [
- ('move_type', populate.randomize(
- ['entry', 'in_invoice', 'out_invoice', 'in_refund', 'out_refund', 'in_receipt', 'out_receipt'],
- [0.2, 0.3, 0.3, 0.07, 0.07, 0.03, 0.03],
- )),
- ('company_id', populate.randomize(company_ids.ids)),
- ('currency_id', populate.randomize(currencies.ids)),
- ('journal_id', populate.compute(get_journal)),
- ('date', populate.randdatetime(relative_before=relativedelta(years=-4), relative_after=relativedelta(years=1))),
- ('invoice_date', populate.compute(get_invoice_date)),
- ('partner_id', populate.compute(get_partner)),
- ('line_ids', populate.compute(get_lines)),
- ]
- def _populate(self, size):
- records = super()._populate(size)
- _logger.info('Posting Journal Entries')
- to_post = records.filtered(lambda r: r.date < fields.Date.today())
- to_post.action_post()
- # TODO add some reconciliations. Not done initially because of perfs.
- # _logger.info('Registering Payments for Invoices and Bills')
- # random = populate.Random('account.move+register_payment')
- # for invoice in to_post:
- # if invoice.is_invoice() and random.uniform(0, 1) < 0.9: # 90% of invoices are at least partialy paid
- # payment_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({})
- # if random.uniform(0, 1) > 0.9: # 90% of paid invoices have the exact amount, others vary a little
- # payment_wizard.amount *= random.uniform(0.5, 1.5)
- # payment_wizard._create_payments()
- return records
|