[REF] Contract: invoice creation

[FIX] - Fix typo

[IMP] - date start required for contract line

[REF] Gitignore: .eggs

[REF] Contract Unit Tests: base the cron check on invoice lines instead of invoices
This commit is contained in:
Thomas Binsfeld
2018-12-18 17:27:41 +01:00
committed by Francisco Ivan Anton Prieto
parent 91017a33f8
commit 29168df7cc
3 changed files with 162 additions and 95 deletions

View File

@@ -173,8 +173,7 @@ class AccountAnalyticAccount(models.Model):
invoice_type = 'out_invoice'
if self.contract_type == 'purchase':
invoice_type = 'in_invoice'
invoice = self.env['account.invoice'].new(
{
return {
'reference': self.code,
'type': invoice_type,
'partner_id': self.partner_id.address_get(['invoice'])[
@@ -188,10 +187,6 @@ class AccountAnalyticAccount(models.Model):
'contract_id': self.id,
'user_id': self.partner_id.user_id.id,
}
)
# Get other invoice values from partner onchange
invoice._onchange_partner_id()
return invoice._convert_to_write(invoice._cache)
@api.multi
def action_contract_send(self):
@@ -217,12 +212,136 @@ class AccountAnalyticAccount(models.Model):
'context': ctx,
}
@api.model
def _finalize_invoice_values(self, invoice_values):
"""
This method adds the missing values in the invoice lines dictionaries.
If no account on the product, the invoice lines account is
taken from the invoice's journal in _onchange_product_id
This code is not in finalize_creation_from_contract because it's
not possible to create an invoice line with no account
:param invoice_values: dictionary (invoice values)
:return: updated dictionary (invoice values)
"""
# If no account on the product, the invoice lines account is
# taken from the invoice's journal in _onchange_product_id
# This code is not in finalize_creation_from_contract because it's
# not possible to create an invoice line with no account
new_invoice = self.env['account.invoice'].new(invoice_values)
for invoice_line in new_invoice.invoice_line_ids:
name = invoice_line.name
account_analytic_id = invoice_line.account_analytic_id
price_unit = invoice_line.price_unit
invoice_line.invoice_id = new_invoice
invoice_line._onchange_product_id()
invoice_line.update({
'name': name,
'account_analytic_id': account_analytic_id,
'price_unit': price_unit,
})
return new_invoice._convert_to_write(new_invoice._cache)
@api.model
def _finalize_invoice_creation(self, invoices):
for invoice in invoices:
invoice._onchange_partner_id()
invoices.compute_taxes()
@api.model
def _finalize_and_create_invoices(self, invoices_values):
"""
This method:
- finalizes the invoices values (onchange's...)
- creates the invoices
- finalizes the created invoices (onchange's, tax computation...)
:param invoices_values: list of dictionaries (invoices values)
:return: created invoices (account.invoice)
"""
if isinstance(invoices_values, dict):
invoices_values = [invoices_values]
final_invoices_values = []
for invoice_values in invoices_values:
final_invoices_values.append(
self._finalize_invoice_values(invoice_values))
invoices = self.env['account.invoice'].create(final_invoices_values)
self._finalize_invoice_creation(invoices)
return invoices
@api.model
def _get_contracts_to_invoice_domain(self, date_ref=None):
"""
This method builds the domain to use to find all
contracts (account.analytic.account) to invoice.
:param date_ref: optional reference date to use instead of today
:return: list (domain) usable on account.analytic.account
"""
domain = []
if not date_ref:
date_ref = fields.Date.context_today(self)
domain.extend(
[
('recurring_invoices', '=', True),
('recurring_next_date', '<=', date_ref),
]
)
return domain
@api.multi
def _get_lines_to_invoice(self, date_ref):
"""
This method fetches and returns the lines to invoice on the contract
(self), based on the given date.
:param date_ref: date used as reference date to find lines to invoice
:return: contract lines (account.analytic.invoice.line recordset)
"""
self.ensure_one()
return self.recurring_invoice_line_ids.filtered(
lambda l: not l.is_canceled and l.recurring_next_date
and l.recurring_next_date <= date_ref)
@api.multi
def _prepare_recurring_invoices_values(self, date_ref=False):
"""
This method builds the list of invoices values to create, based on
the lines to invoice of the contracts in self.
!!! The date of next invoice (recurring_next_date) is updated here !!!
:return: list of dictionaries (invoices values)
"""
invoices_values = []
for contract in self:
if not date_ref:
date_ref = contract.recurring_next_date
contract_lines = contract._get_lines_to_invoice(date_ref)
if not contract_lines:
continue
invoice_values = contract._prepare_invoice(date_ref)
for line in contract_lines:
invoice_values.setdefault('invoice_line_ids', [])
invoice_values['invoice_line_ids'].append(
(0, 0, line._prepare_invoice_line(invoice_id=False))
)
invoices_values.append(invoice_values)
contract_lines._update_recurring_next_date()
return invoices_values
@api.multi
def recurring_create_invoice(self):
return self.env[
'account.analytic.invoice.line'
].recurring_create_invoice(self)
"""
This method triggers the creation of the next invoices of the contracts
even if their next invoicing date is in the future.
"""
return self._recurring_create_invoice()
@api.multi
def _recurring_create_invoice(self, date_ref=False):
invoices_values = self._prepare_recurring_invoices_values(date_ref)
return self._finalize_and_create_invoices(invoices_values)
@api.model
def cron_recurring_create_invoice(self):
self.env['account.analytic.invoice.line'].recurring_create_invoice()
domain = self._get_contracts_to_invoice_domain()
contracts_to_invoice = self.search(domain)
date_ref = fields.Date.context_today(contracts_to_invoice)
contracts_to_invoice._recurring_create_invoice(date_ref)

View File

@@ -24,6 +24,7 @@ class AccountAnalyticInvoiceLine(models.Model):
)
date_start = fields.Date(
string='Date Start',
required=True,
default=lambda self: fields.Date.context_today(self),
)
date_end = fields.Date(string='Date End', index=True)
@@ -78,7 +79,10 @@ class AccountAnalyticInvoiceLine(models.Model):
compute="_compute_state",
)
active = fields.Boolean(
string="Active", related="contract_id.active", strore=True
string="Active",
related="contract_id.active",
store=True,
readonly=True,
)
@api.multi
@@ -312,75 +316,18 @@ class AccountAnalyticInvoiceLine(models.Model):
rec.recurring_next_date
)
@api.model
def _get_recurring_create_invoice_domain(self, contract=False):
domain = []
date_ref = fields.Date.context_today(self)
if contract:
contract.ensure_one()
date_ref = contract.recurring_next_date
domain.append(('contract_id', '=', contract.id))
domain.extend(
[
('contract_id.recurring_invoices', '=', True),
('recurring_next_date', '<=', date_ref),
('is_canceled', '=', False),
]
)
return domain
@api.model
def recurring_create_invoice(self, contract=False):
domain = self._get_recurring_create_invoice_domain(contract)
contract_to_invoice = self.read_group(
domain, ['id', 'contract_id'], ['contract_id']
)
return self._recurring_create_invoice(contract_to_invoice)
@api.model
def _recurring_create_invoice(self, contract_to_invoice):
"""Create invoices from contracts
:return: invoices created
"""
invoices = self.env['account.invoice']
for contract in contract_to_invoice:
lines = self.search(contract['__domain'])
if lines:
invoices |= lines._create_invoice()
lines._update_recurring_next_date()
return invoices
@api.multi
def _create_invoice(self):
"""
:return: invoice created
"""
contract = self.mapped('contract_id')
date_invoice = min(self.mapped('recurring_next_date'))
invoice = self.env['account.invoice'].create(
contract._prepare_invoice(date_invoice)
)
for line in self:
invoice_line_vals = line._prepare_invoice_line(invoice.id)
if invoice_line_vals:
self.env['account.invoice.line'].create(invoice_line_vals)
invoice.compute_taxes()
return invoice
@api.multi
def _prepare_invoice_line(self, invoice_id):
def _prepare_invoice_line(self, invoice_id=False):
self.ensure_one()
invoice_line = self.env['account.invoice.line'].new(
{
'invoice_id': invoice_id,
invoice_line_vals = {
'product_id': self.product_id.id,
'quantity': self.quantity,
'uom_id': self.uom_id.id,
'discount': self.discount,
}
)
if invoice_id:
invoice_line_vals['invoice_id'] = invoice_id.id
invoice_line = self.env['account.invoice.line'].new(invoice_line_vals)
# Get other invoice line values from product onchange
invoice_line._onchange_product_id()
invoice_line_vals = invoice_line._convert_to_write(invoice_line._cache)

View File

@@ -1290,14 +1290,15 @@ class TestContract(TestContractBase):
self.acct_line.recurring_invoicing_type = 'post-paid'
self.acct_line.date_end = '2018-03-15'
self.acct_line._onchange_date_start()
contracts = self.contract
contracts = self.contract2
for i in range(10):
contracts |= self.contract.copy()
self.env['account.analytic.account'].cron_recurring_create_invoice()
invoices = self.env['account.invoice'].search(
[('contract_id', 'in', contracts.ids)]
invoice_lines = self.env['account.invoice.line'].search(
[('account_analytic_id', 'in', contracts.ids)]
)
self.assertEqual(len(contracts), len(invoices))
self.assertEqual(len(contracts.mapped('recurring_invoice_line_ids')),
len(invoice_lines))
def test_get_invoiced_period_monthlylastday(self):
self.acct_line.date_start = '2018-01-05'