mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
[REF] Contract: invoice creation
[REF] Contract Unit Tests: base the cron check on invoice lines instead of invoices
This commit is contained in:
committed by
sbejaoui
parent
a874e94802
commit
f95d2a3582
@@ -1,7 +1,7 @@
|
|||||||
# © 2016 Carlos Dauden <carlos.dauden@tecnativa.com>
|
# © 2016 Carlos Dauden <carlos.dauden@tecnativa.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
class AccountInvoice(models.Model):
|
class AccountInvoice(models.Model):
|
||||||
@@ -10,9 +10,3 @@ class AccountInvoice(models.Model):
|
|||||||
contract_id = fields.Many2one(
|
contract_id = fields.Many2one(
|
||||||
'account.analytic.account', string='Contract'
|
'account.analytic.account', string='Contract'
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def finalize_creation_from_contract(self):
|
|
||||||
for invoice in self:
|
|
||||||
invoice._onchange_partner_id()
|
|
||||||
self.compute_taxes()
|
|
||||||
|
|||||||
@@ -213,13 +213,73 @@ class AccountAnalyticAccount(models.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_recurring_create_invoice_domain(self, contract=False):
|
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 = []
|
domain = []
|
||||||
|
if not date_ref:
|
||||||
date_ref = fields.Date.context_today(self)
|
date_ref = fields.Date.context_today(self)
|
||||||
if contract:
|
|
||||||
contract.ensure_one()
|
|
||||||
date_ref = contract.recurring_next_date
|
|
||||||
domain.append(('id', '=', contract.id))
|
|
||||||
domain.extend(
|
domain.extend(
|
||||||
[
|
[
|
||||||
('recurring_invoices', '=', True),
|
('recurring_invoices', '=', True),
|
||||||
@@ -229,44 +289,59 @@ class AccountAnalyticAccount(models.Model):
|
|||||||
return domain
|
return domain
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _get_lines_to_invoice(self, date_ref=False):
|
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()
|
self.ensure_one()
|
||||||
if not date_ref:
|
|
||||||
date_ref = fields.Date.context_today(self)
|
|
||||||
return self.recurring_invoice_line_ids.filtered(
|
return self.recurring_invoice_line_ids.filtered(
|
||||||
lambda l: not l.is_canceled and l.recurring_next_date <= date_ref)
|
lambda l: not l.is_canceled and l.recurring_next_date
|
||||||
|
and l.recurring_next_date <= date_ref)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def recurring_create_invoice(self):
|
def _prepare_recurring_invoices_values(self, date_ref=False):
|
||||||
invoice_model = self.env['account.invoice']
|
"""
|
||||||
|
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 = []
|
invoices_values = []
|
||||||
for contract in self:
|
for contract in self:
|
||||||
contract_lines = contract._get_lines_to_invoice()
|
if not date_ref:
|
||||||
|
date_ref = contract.recurring_next_date
|
||||||
|
contract_lines = contract._get_lines_to_invoice(date_ref)
|
||||||
if not contract_lines:
|
if not contract_lines:
|
||||||
continue
|
continue
|
||||||
invoice_values = contract._prepare_invoice(
|
invoice_values = contract._prepare_invoice(date_ref)
|
||||||
contract.recurring_next_date)
|
|
||||||
for line in contract_lines:
|
for line in contract_lines:
|
||||||
invoice_values.setdefault('invoice_line_ids', [])
|
invoice_values.setdefault('invoice_line_ids', [])
|
||||||
invoice_values['invoice_line_ids'].append(
|
invoice_values['invoice_line_ids'].append(
|
||||||
(0, 0, line._prepare_invoice_line(False))
|
(0, 0, line._prepare_invoice_line(invoice_id=False))
|
||||||
)
|
)
|
||||||
# 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 = invoice_model.new(invoice_values)
|
|
||||||
for invoice_line in new_invoice.invoice_line_ids:
|
|
||||||
invoice_line.invoice_id = new_invoice
|
|
||||||
invoice_line._onchange_product_id()
|
|
||||||
invoice_values = new_invoice._convert_to_write(new_invoice._cache)
|
|
||||||
invoices_values.append(invoice_values)
|
invoices_values.append(invoice_values)
|
||||||
contract_lines._update_recurring_next_date()
|
contract_lines._update_recurring_next_date()
|
||||||
invoices = invoice_model.create(invoices_values)
|
return invoices_values
|
||||||
invoices.finalize_creation_from_contract()
|
|
||||||
|
@api.multi
|
||||||
|
def 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
|
@api.model
|
||||||
def cron_recurring_create_invoice(self):
|
def cron_recurring_create_invoice(self):
|
||||||
domain = self._get_recurring_create_invoice_domain()
|
domain = self._get_contracts_to_invoice_domain()
|
||||||
contracts_to_invoice = self.search(domain)
|
contracts_to_invoice = self.search(domain)
|
||||||
contracts_to_invoice.recurring_create_invoice()
|
date_ref = fields.Date.context_today(contracts_to_invoice)
|
||||||
|
contracts_to_invoice._recurring_create_invoice(date_ref)
|
||||||
|
|||||||
@@ -319,14 +319,15 @@ class AccountAnalyticInvoiceLine(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_invoice_line(self, invoice_id=False):
|
def _prepare_invoice_line(self, invoice_id=False):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
invoice_line = self.env['account.invoice.line'].new(
|
invoice_line_vals = {
|
||||||
{
|
|
||||||
'product_id': self.product_id.id,
|
'product_id': self.product_id.id,
|
||||||
'quantity': self.quantity,
|
'quantity': self.quantity,
|
||||||
'uom_id': self.uom_id.id,
|
'uom_id': self.uom_id.id,
|
||||||
'discount': self.discount,
|
'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
|
# Get other invoice line values from product onchange
|
||||||
invoice_line._onchange_product_id()
|
invoice_line._onchange_product_id()
|
||||||
invoice_line_vals = invoice_line._convert_to_write(invoice_line._cache)
|
invoice_line_vals = invoice_line._convert_to_write(invoice_line._cache)
|
||||||
|
|||||||
@@ -1290,14 +1290,15 @@ class TestContract(TestContractBase):
|
|||||||
self.acct_line.recurring_invoicing_type = 'post-paid'
|
self.acct_line.recurring_invoicing_type = 'post-paid'
|
||||||
self.acct_line.date_end = '2018-03-15'
|
self.acct_line.date_end = '2018-03-15'
|
||||||
self.acct_line._onchange_date_start()
|
self.acct_line._onchange_date_start()
|
||||||
contracts = self.contract
|
contracts = self.contract2
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
contracts |= self.contract.copy()
|
contracts |= self.contract.copy()
|
||||||
self.env['account.analytic.account'].cron_recurring_create_invoice()
|
self.env['account.analytic.account'].cron_recurring_create_invoice()
|
||||||
invoices = self.env['account.invoice'].search(
|
invoice_lines = self.env['account.invoice.line'].search(
|
||||||
[('contract_id', 'in', contracts.ids)]
|
[('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):
|
def test_get_invoiced_period_monthlylastday(self):
|
||||||
self.acct_line.date_start = '2018-01-05'
|
self.acct_line.date_start = '2018-01-05'
|
||||||
|
|||||||
Reference in New Issue
Block a user