mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
[FIX+IMP] contract: several things
[FIX] - as date_start is required, constraints on it become useless [FIX] - for finished contract _get_lines_to_invoice should return False [FIX] - default value for active field in contract line [FIX] - fix flake8 [IMP] - check invoice line vals before assignment [FIX] - Fix unit tests. [FIX] - do not copy last_date_invoiced on plan_successor [FIX] - renew only recurring_invoices contract [FIX] - filter by termination_notice for contract line to renew [IMP] - Improve inprogress search filter [IMP] - Link invoice line to contract lines [IMP] - Add index on contract line [IMP] - Add is_suspended flag and _search_state [IMP] - Add is_suspended flag to stop contract line [MV] - move onchange method to contract_product module [IMP] - Replace is_suspended flag by manual_renew_needed Add a computed field for the first date of the termination notice period Adapt state compute and search method [IMP] - Improve unit tests
This commit is contained in:
@@ -33,7 +33,6 @@
|
||||
'views/contract.xml',
|
||||
'views/contract_template_line.xml',
|
||||
'views/contract_template.xml',
|
||||
'views/account_invoice_view.xml',
|
||||
'views/contract_line.xml',
|
||||
'views/res_partner_view.xml',
|
||||
],
|
||||
|
||||
@@ -19,6 +19,6 @@ def migrate(cr, version):
|
||||
lambda c: not c.create_invoice_visibility
|
||||
)
|
||||
cr.execute(
|
||||
"UPDATE account_analytic_account set recurring_next_date=null where id in (%)"
|
||||
% ','.join(finished_contract.ids)
|
||||
"UPDATE account_analytic_account set recurring_next_date=null "
|
||||
"where id in (%)" % ','.join(finished_contract.ids)
|
||||
)
|
||||
|
||||
@@ -7,4 +7,5 @@ from . import contract
|
||||
from . import contract_template_line
|
||||
from . import contract_line
|
||||
from . import account_invoice
|
||||
from . import account_invoice_line
|
||||
from . import res_partner
|
||||
|
||||
@@ -205,23 +205,3 @@ class AccountAbstractAnalyticContractLine(models.AbstractModel):
|
||||
vals['price_unit'] = product.price
|
||||
self.update(vals)
|
||||
return {'domain': domain}
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_id_recurring_info(self):
|
||||
for rec in self:
|
||||
rec.date_start = fields.Date.today()
|
||||
if rec.product_id.is_contract:
|
||||
rec.recurring_rule_type = rec.product_id.recurring_rule_type
|
||||
rec.recurring_invoicing_type = (
|
||||
rec.product_id.recurring_invoicing_type
|
||||
)
|
||||
rec.recurring_interval = rec.product_id.recurring_interval
|
||||
rec.is_auto_renew = rec.product_id.is_auto_renew
|
||||
rec.auto_renew_interval = rec.product_id.auto_renew_interval
|
||||
rec.auto_renew_rule_type = rec.product_id.auto_renew_rule_type
|
||||
rec.termination_notice_interval = (
|
||||
rec.product_id.termination_notice_interval
|
||||
)
|
||||
rec.termination_notice_rule_type = (
|
||||
rec.product_id.termination_notice_rule_type
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ from odoo import fields, models
|
||||
class AccountInvoice(models.Model):
|
||||
_inherit = 'account.invoice'
|
||||
|
||||
contract_id = fields.Many2one(
|
||||
'account.analytic.account', string='Contract'
|
||||
# We keep this field for migration purpose
|
||||
old_contract_id = fields.Many2one(
|
||||
'account.analytic.account', oldname="contract_id"
|
||||
)
|
||||
|
||||
12
contract/models/account_invoice_line.py
Normal file
12
contract/models/account_invoice_line.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# Copyright 2018 ACSONE SA/NV.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
_inherit = 'account.invoice.line'
|
||||
|
||||
contract_line_id = fields.Many2one(
|
||||
'account.analytic.invoice.line', string='Contract Line', index=True
|
||||
)
|
||||
@@ -48,9 +48,48 @@ class AccountAnalyticAccount(models.Model):
|
||||
compute='_compute_date_end', string='Date End', store=True
|
||||
)
|
||||
payment_term_id = fields.Many2one(
|
||||
comodel_name='account.payment.term',
|
||||
string='Payment Terms',
|
||||
comodel_name='account.payment.term', string='Payment Terms'
|
||||
)
|
||||
invoice_count = fields.Integer(compute="_compute_invoice_count")
|
||||
|
||||
@api.multi
|
||||
def _get_related_invoices(self):
|
||||
self.ensure_one()
|
||||
|
||||
invoices = (
|
||||
self.env['account.invoice.line']
|
||||
.search(
|
||||
[
|
||||
(
|
||||
'contract_line_id',
|
||||
'in',
|
||||
self.recurring_invoice_line_ids.ids,
|
||||
)
|
||||
]
|
||||
)
|
||||
.mapped('invoice_id')
|
||||
)
|
||||
invoices |= self.env['account.invoice'].search(
|
||||
[('old_contract_id', '=', self.id)]
|
||||
)
|
||||
return invoices
|
||||
|
||||
@api.multi
|
||||
def _compute_invoice_count(self):
|
||||
for rec in self:
|
||||
rec.invoice_count = len(rec._get_related_invoices())
|
||||
|
||||
@api.multi
|
||||
def action_show_invoices(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Invoices',
|
||||
'res_model': 'account.invoice',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,kanban,form,calendar,pivot,graph,activity',
|
||||
'domain': [('id', 'in', self._get_related_invoices().ids)],
|
||||
}
|
||||
|
||||
@api.depends('recurring_invoice_line_ids.date_end')
|
||||
def _compute_date_end(self):
|
||||
@@ -118,7 +157,7 @@ class AccountAnalyticAccount(models.Model):
|
||||
for contract in self.filtered('recurring_invoices'):
|
||||
if not contract.partner_id:
|
||||
raise ValidationError(
|
||||
_("You must supply a customer for the contract '%s'")
|
||||
_("You must supply a partner for the contract '%s'")
|
||||
% contract.name
|
||||
)
|
||||
|
||||
@@ -141,17 +180,6 @@ class AccountAnalyticAccount(models.Model):
|
||||
@api.multi
|
||||
def _prepare_invoice(self, date_invoice, journal=None):
|
||||
self.ensure_one()
|
||||
if not self.partner_id:
|
||||
if self.contract_type == 'purchase':
|
||||
raise ValidationError(
|
||||
_("You must first select a Supplier for Contract %s!")
|
||||
% self.name
|
||||
)
|
||||
else:
|
||||
raise ValidationError(
|
||||
_("You must first select a Customer for Contract %s!")
|
||||
% self.name
|
||||
)
|
||||
if not journal:
|
||||
journal = (
|
||||
self.journal_id
|
||||
@@ -180,15 +208,12 @@ class AccountAnalyticAccount(models.Model):
|
||||
return {
|
||||
'reference': self.code,
|
||||
'type': invoice_type,
|
||||
'partner_id': self.partner_id.address_get(['invoice'])[
|
||||
'invoice'
|
||||
],
|
||||
'partner_id': self.partner_id.address_get(['invoice'])['invoice'],
|
||||
'currency_id': currency.id,
|
||||
'date_invoice': date_invoice,
|
||||
'journal_id': journal.id,
|
||||
'origin': self.name,
|
||||
'company_id': self.company_id.id,
|
||||
'contract_id': self.id,
|
||||
'user_id': self.partner_id.user_id.id,
|
||||
}
|
||||
|
||||
@@ -240,11 +265,13 @@ class AccountAnalyticAccount(models.Model):
|
||||
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,
|
||||
})
|
||||
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
|
||||
@@ -268,7 +295,8 @@ class AccountAnalyticAccount(models.Model):
|
||||
final_invoices_values = []
|
||||
for invoice_values in invoices_values:
|
||||
final_invoices_values.append(
|
||||
self._finalize_invoice_values(invoice_values))
|
||||
self._finalize_invoice_values(invoice_values)
|
||||
)
|
||||
invoices = self.env['account.invoice'].create(final_invoices_values)
|
||||
self._finalize_invoice_creation(invoices)
|
||||
return invoices
|
||||
@@ -302,8 +330,10 @@ class AccountAnalyticAccount(models.Model):
|
||||
"""
|
||||
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)
|
||||
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):
|
||||
@@ -317,15 +347,23 @@ class AccountAnalyticAccount(models.Model):
|
||||
for contract in self:
|
||||
if not date_ref:
|
||||
date_ref = contract.recurring_next_date
|
||||
if not date_ref:
|
||||
# this use case is possible when recurring_create_invoice is
|
||||
# called for a finished contract
|
||||
continue
|
||||
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))
|
||||
invoice_line_values = line._prepare_invoice_line(
|
||||
invoice_id=False
|
||||
)
|
||||
if invoice_line_values:
|
||||
invoice_values['invoice_line_ids'].append(
|
||||
(0, 0, invoice_line_values)
|
||||
)
|
||||
invoices_values.append(invoice_values)
|
||||
contract_lines._update_recurring_next_date()
|
||||
return invoices_values
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Copyright 2017 LasLabs Inc.
|
||||
# Copyright 2018 ACSONE SA/NV.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from datetime import timedelta
|
||||
@@ -32,6 +33,12 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
last_date_invoiced = fields.Date(
|
||||
string='Last Date Invoiced', readonly=True, copy=False
|
||||
)
|
||||
termination_notice_date = fields.Date(
|
||||
string='Termination notice date',
|
||||
compute="_compute_termination_notice_date",
|
||||
store=True,
|
||||
copy=False,
|
||||
)
|
||||
create_invoice_visibility = fields.Boolean(
|
||||
compute='_compute_create_invoice_visibility'
|
||||
)
|
||||
@@ -40,6 +47,7 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
string="Successor Contract Line",
|
||||
required=False,
|
||||
readonly=True,
|
||||
index=True,
|
||||
copy=False,
|
||||
help="In case of restart after suspension, this field contain the new "
|
||||
"contract line created.",
|
||||
@@ -49,9 +57,17 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
string="Predecessor Contract Line",
|
||||
required=False,
|
||||
readonly=True,
|
||||
index=True,
|
||||
copy=False,
|
||||
help="Contract Line origin of this one.",
|
||||
)
|
||||
manual_renew_needed = fields.Boolean(
|
||||
string="Manual renew needed",
|
||||
default=False,
|
||||
help="This flag is used to make a difference between a definitive stop"
|
||||
"and temporary one for which a user is not able to plan a"
|
||||
"successor in advance",
|
||||
)
|
||||
is_plan_successor_allowed = fields.Boolean(
|
||||
string="Plan successor allowed?", compute='_compute_allowed'
|
||||
)
|
||||
@@ -72,37 +88,186 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
selection=[
|
||||
('upcoming', 'Upcoming'),
|
||||
('in-progress', 'In-progress'),
|
||||
('to-renew', 'To renew'),
|
||||
('upcoming-close', 'Upcoming Close'),
|
||||
('closed', 'Closed'),
|
||||
('canceled', 'Canceled'),
|
||||
],
|
||||
compute="_compute_state",
|
||||
search='_search_state',
|
||||
)
|
||||
active = fields.Boolean(
|
||||
string="Active",
|
||||
related="contract_id.active",
|
||||
store=True,
|
||||
readonly=True,
|
||||
default=True,
|
||||
)
|
||||
|
||||
@api.multi
|
||||
@api.depends(
|
||||
'date_end',
|
||||
'termination_notice_rule_type',
|
||||
'termination_notice_interval',
|
||||
)
|
||||
def _compute_termination_notice_date(self):
|
||||
for rec in self:
|
||||
if rec.date_end:
|
||||
rec.termination_notice_date = (
|
||||
rec.date_end
|
||||
- self.get_relative_delta(
|
||||
rec.termination_notice_rule_type,
|
||||
rec.termination_notice_interval,
|
||||
)
|
||||
)
|
||||
|
||||
@api.multi
|
||||
@api.depends('is_canceled', 'date_start', 'date_end', 'is_auto_renew')
|
||||
def _compute_state(self):
|
||||
today = fields.Date.context_today(self)
|
||||
for rec in self:
|
||||
if rec.date_start:
|
||||
if rec.is_canceled:
|
||||
rec.state = 'canceled'
|
||||
elif today < rec.date_start:
|
||||
rec.state = 'upcoming'
|
||||
elif not rec.date_end or (
|
||||
today <= rec.date_end and rec.is_auto_renew
|
||||
if rec.is_canceled:
|
||||
rec.state = 'canceled'
|
||||
continue
|
||||
|
||||
if rec.date_start and rec.date_start > today:
|
||||
# Before period
|
||||
rec.state = 'upcoming'
|
||||
continue
|
||||
if (
|
||||
rec.date_start
|
||||
and rec.date_start <= today
|
||||
and (not rec.date_end or rec.date_end >= today)
|
||||
):
|
||||
# In period
|
||||
if (
|
||||
rec.termination_notice_date
|
||||
and rec.termination_notice_date < today
|
||||
and not rec.is_auto_renew
|
||||
and not rec.manual_renew_needed
|
||||
):
|
||||
rec.state = 'in-progress'
|
||||
elif today <= rec.date_end and not rec.is_auto_renew:
|
||||
rec.state = 'upcoming-close'
|
||||
else:
|
||||
rec.state = 'in-progress'
|
||||
continue
|
||||
if rec.date_end and rec.date_end < today:
|
||||
# After
|
||||
if (
|
||||
rec.manual_renew_needed
|
||||
and not rec.successor_contract_line_id
|
||||
or rec.is_auto_renew
|
||||
):
|
||||
rec.state = 'to-renew'
|
||||
else:
|
||||
rec.state = 'closed'
|
||||
|
||||
@api.model
|
||||
def _get_state_domain(self, state):
|
||||
today = fields.Date.context_today(self)
|
||||
if state == 'upcoming':
|
||||
return [
|
||||
"&",
|
||||
('date_start', '>', today),
|
||||
('is_canceled', '=', False),
|
||||
]
|
||||
if state == 'in-progress':
|
||||
return [
|
||||
"&",
|
||||
"&",
|
||||
"&",
|
||||
('date_start', '<=', today),
|
||||
('is_canceled', '=', False),
|
||||
"|",
|
||||
('date_end', '>=', today),
|
||||
('date_end', '=', False),
|
||||
"|",
|
||||
"&",
|
||||
('is_auto_renew', '=', True),
|
||||
('is_auto_renew', '=', False),
|
||||
('termination_notice_date', '>', today),
|
||||
]
|
||||
if state == 'to-renew':
|
||||
return [
|
||||
"&",
|
||||
"&",
|
||||
('is_canceled', '=', False),
|
||||
('date_end', '<', today),
|
||||
"|",
|
||||
"&",
|
||||
('manual_renew_needed', '=', True),
|
||||
('successor_contract_line_id', '=', False),
|
||||
('is_auto_renew', '=', True),
|
||||
]
|
||||
if state == 'upcoming-close':
|
||||
return [
|
||||
"&",
|
||||
"&",
|
||||
"&",
|
||||
"&",
|
||||
"&",
|
||||
('date_start', '<=', today),
|
||||
('is_auto_renew', '=', False),
|
||||
('manual_renew_needed', '=', False),
|
||||
('is_canceled', '=', False),
|
||||
('termination_notice_date', '<', today),
|
||||
('date_end', '>=', today),
|
||||
]
|
||||
if state == 'closed':
|
||||
return [
|
||||
"&",
|
||||
"&",
|
||||
"&",
|
||||
('is_canceled', '=', False),
|
||||
('date_end', '<', today),
|
||||
('is_auto_renew', '=', False),
|
||||
"|",
|
||||
"&",
|
||||
('manual_renew_needed', '=', True),
|
||||
('successor_contract_line_id', '!=', False),
|
||||
('manual_renew_needed', '=', False),
|
||||
]
|
||||
if state == 'canceled':
|
||||
return [('is_canceled', '=', True)]
|
||||
|
||||
@api.model
|
||||
def _search_state(self, operator, value):
|
||||
states = [
|
||||
'upcoming',
|
||||
'in-progress',
|
||||
'to-renew',
|
||||
'upcoming-close',
|
||||
'closed',
|
||||
'canceled',
|
||||
]
|
||||
if operator == '!=' and not value:
|
||||
return []
|
||||
if operator == '=' and not value:
|
||||
return [('id', '=', False)]
|
||||
if operator == '=':
|
||||
return self._get_state_domain(value)
|
||||
if operator == '!=':
|
||||
domain = []
|
||||
for state in states:
|
||||
if state != value:
|
||||
if domain:
|
||||
domain.insert(0, '|')
|
||||
domain.extend(self._get_state_domain(state))
|
||||
return domain
|
||||
if operator == 'in':
|
||||
domain = []
|
||||
if not value:
|
||||
return [('id', '=', False)]
|
||||
for state in value:
|
||||
if domain:
|
||||
domain.insert(0, '|')
|
||||
domain.extend(self._get_state_domain(state))
|
||||
return domain
|
||||
|
||||
if operator == 'not in':
|
||||
return self._search_state(
|
||||
'in', [state for state in states if state not in value]
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
'date_start',
|
||||
'date_end',
|
||||
@@ -282,15 +447,6 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
% rec.name
|
||||
)
|
||||
|
||||
@api.constrains('date_start')
|
||||
def _check_date_start_recurring_invoices(self):
|
||||
for line in self.filtered('contract_id.recurring_invoices'):
|
||||
if not line.date_start:
|
||||
raise ValidationError(
|
||||
_("You must supply a start date for contract line '%s'")
|
||||
% line.name
|
||||
)
|
||||
|
||||
@api.constrains('date_start', 'date_end')
|
||||
def _check_start_end_dates(self):
|
||||
for line in self.filtered('date_end'):
|
||||
@@ -324,6 +480,7 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
'quantity': self.quantity,
|
||||
'uom_id': self.uom_id.id,
|
||||
'discount': self.discount,
|
||||
'contract_line_id': self.id,
|
||||
}
|
||||
if invoice_id:
|
||||
invoice_line_vals['invoice_id'] = invoice_id.id
|
||||
@@ -393,31 +550,18 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
new_date = old_date + self.get_relative_delta(
|
||||
rec.recurring_rule_type, rec.recurring_interval
|
||||
)
|
||||
|
||||
if rec.recurring_rule_type == 'monthlylastday':
|
||||
rec.last_date_invoiced = (
|
||||
old_date
|
||||
if rec.date_end and old_date < rec.date_end
|
||||
else rec.date_end
|
||||
)
|
||||
last_date_invoiced = old_date
|
||||
elif rec.recurring_invoicing_type == 'post-paid':
|
||||
rec.last_date_invoiced = (
|
||||
old_date - relativedelta(days=1)
|
||||
if rec.date_end and old_date < rec.date_end
|
||||
else rec.date_end
|
||||
)
|
||||
last_date_invoiced = old_date - relativedelta(days=1)
|
||||
elif rec.recurring_invoicing_type == 'pre-paid':
|
||||
rec.last_date_invoiced = (
|
||||
new_date - relativedelta(days=1)
|
||||
if rec.date_end and new_date < rec.date_end
|
||||
else rec.date_end
|
||||
)
|
||||
if (
|
||||
rec.last_date_invoiced
|
||||
and rec.last_date_invoiced == rec.date_end
|
||||
):
|
||||
last_date_invoiced = new_date - relativedelta(days=1)
|
||||
|
||||
if rec.date_end and last_date_invoiced >= rec.date_end:
|
||||
rec.last_date_invoiced = rec.date_end
|
||||
rec.recurring_next_date = False
|
||||
else:
|
||||
rec.last_date_invoiced = last_date_invoiced
|
||||
rec.recurring_next_date = new_date
|
||||
|
||||
@api.multi
|
||||
@@ -481,7 +625,7 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
rec.date_start = new_date_start
|
||||
|
||||
@api.multi
|
||||
def stop(self, date_end, post_message=True):
|
||||
def stop(self, date_end, manual_renew_needed=False, post_message=True):
|
||||
"""
|
||||
Put date_end on contract line
|
||||
We don't consider contract lines that end's before the new end date
|
||||
@@ -508,9 +652,20 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
)
|
||||
)
|
||||
rec.contract_id.message_post(body=msg)
|
||||
rec.write({'date_end': date_end, 'is_auto_renew': False})
|
||||
rec.write(
|
||||
{
|
||||
'date_end': date_end,
|
||||
'is_auto_renew': False,
|
||||
"manual_renew_needed": manual_renew_needed,
|
||||
}
|
||||
)
|
||||
else:
|
||||
rec.write({'is_auto_renew': False})
|
||||
rec.write(
|
||||
{
|
||||
'is_auto_renew': False,
|
||||
"manual_renew_needed": manual_renew_needed,
|
||||
}
|
||||
)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
@@ -527,6 +682,7 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
)
|
||||
new_vals = self.read()[0]
|
||||
new_vals.pop("id", None)
|
||||
new_vals.pop("last_date_invoiced", None)
|
||||
values = self._convert_to_write(new_vals)
|
||||
values['date_start'] = date_start
|
||||
values['date_end'] = date_end
|
||||
@@ -644,7 +800,9 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
+ relativedelta(days=1)
|
||||
)
|
||||
rec.stop(
|
||||
date_start - relativedelta(days=1), post_message=False
|
||||
date_start - relativedelta(days=1),
|
||||
manual_renew_needed=True,
|
||||
post_message=False,
|
||||
)
|
||||
contract_line |= rec.plan_successor(
|
||||
new_date_start,
|
||||
@@ -664,7 +822,9 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
new_date_end = rec.date_end
|
||||
|
||||
rec.stop(
|
||||
date_start - relativedelta(days=1), post_message=False
|
||||
date_start - relativedelta(days=1),
|
||||
manual_renew_needed=True,
|
||||
post_message=False,
|
||||
)
|
||||
contract_line |= rec.plan_successor(
|
||||
new_date_start,
|
||||
@@ -839,7 +999,7 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
res = self.env['account.analytic.invoice.line']
|
||||
for rec in self:
|
||||
is_auto_renew = rec.is_auto_renew
|
||||
rec.stop(rec.date_end, post_message=False)
|
||||
rec.is_auto_renew = False
|
||||
date_start, date_end = rec._get_renewal_dates()
|
||||
new_line = rec.plan_successor(
|
||||
date_start, date_end, is_auto_renew, post_message=False
|
||||
@@ -863,13 +1023,11 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
|
||||
@api.model
|
||||
def _contract_line_to_renew_domain(self):
|
||||
date_ref = fields.Date.context_today(self) + self.get_relative_delta(
|
||||
self.termination_notice_rule_type, self.termination_notice_interval
|
||||
)
|
||||
return [
|
||||
('is_auto_renew', '=', True),
|
||||
('date_end', '<=', date_ref),
|
||||
('is_canceled', '=', False),
|
||||
('contract_id.recurring_invoices', '=', True),
|
||||
('termination_notice_date', '<=', fields.Date.context_today(self)),
|
||||
]
|
||||
|
||||
@api.model
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_account_invoice_filter_contract" model="ir.ui.view">
|
||||
<field name="name">account.invoice.select.contract</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="inherit_id" ref="account.view_account_invoice_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="date" position="after">
|
||||
<separator/>
|
||||
<field name="contract_id"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="act_recurring_invoices" model="ir.actions.act_window">
|
||||
<field name="name">Invoices</field>
|
||||
<field name="res_model">account.invoice</field>
|
||||
<field name="view_ids"
|
||||
eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('account.invoice_tree')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('account.invoice_form')})]"/>
|
||||
<field name="context">{
|
||||
'search_default_contract_id': [active_id],
|
||||
'default_contract_id': active_id}
|
||||
</field>
|
||||
<field name="domain">[('type','in', ['out_invoice', 'out_refund'])]</field>
|
||||
</record>
|
||||
|
||||
<record id="act_purchase_recurring_invoices" model="ir.actions.act_window">
|
||||
<field name="name">Vendor Bills</field>
|
||||
<field name="res_model">account.invoice</field>
|
||||
<field name="view_ids"
|
||||
eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('account.invoice_supplier_tree')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('account.invoice_supplier_form')})]"/>
|
||||
<field name="context">{
|
||||
'search_default_contract_id': [active_id],
|
||||
'default_contract_id': active_id}
|
||||
</field>
|
||||
<field name="domain">[('type','in', ['in_invoice', 'in_refund'])]</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -25,16 +25,17 @@
|
||||
<field name="active" widget="boolean_button"
|
||||
options="{"terminology": "archive"}"/>
|
||||
</button>
|
||||
|
||||
<button name="contract.act_recurring_invoices"
|
||||
type="action"
|
||||
string="Invoices"
|
||||
icon="fa-list"
|
||||
class="oe_stat_button"/>
|
||||
<button name="action_show_invoices"
|
||||
type="object" icon="fa-list"
|
||||
class="oe_stat_button">
|
||||
<field string="Invoices"
|
||||
name="invoice_count"
|
||||
widget="statinfo"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<label for="name" string="Contract Name"
|
||||
class="oe_edit_only"/>
|
||||
class="oe_edit_only"/>
|
||||
<h3>
|
||||
<field name="name" class="oe_inline"
|
||||
placeholder="e.g. Contract XYZ"/>
|
||||
@@ -68,7 +69,8 @@
|
||||
<notebook>
|
||||
<page name="recurring_invoice_line"
|
||||
string="Recurring Invoices">
|
||||
<field name="recurring_invoice_line_ids" context="{'default_contract_type': contract_type}"/>
|
||||
<field name="recurring_invoice_line_ids"
|
||||
context="{'default_contract_type': contract_type}"/>
|
||||
</page>
|
||||
<page name="info" string="Other Information">
|
||||
<div invisible="1">
|
||||
@@ -194,17 +196,17 @@
|
||||
/>
|
||||
<separator/>
|
||||
<filter name="not_finished"
|
||||
string="Valid"
|
||||
domain="['|', ('date_end', '=', False), ('date_end', '>=', time.strftime('%Y-%m-%d'))]"
|
||||
string="In progress"
|
||||
domain="[('recurring_next_date', '>=', time.strftime('%Y-%m-%d'))]"
|
||||
/>
|
||||
<filter name="finished"
|
||||
string="Finished"
|
||||
domain="[('date_end', '<', time.strftime('%Y-%m-%d'))]"
|
||||
domain="[('date_end', '<', time.strftime('%Y-%m-%d')), ('recurring_next_date', '=', False)]"
|
||||
/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter name="next_invoice"
|
||||
string="Next Invoice"
|
||||
domain="[]"
|
||||
domain="[('recurring_next_date', '>=', time.strftime('%Y-%m-%d'))]"
|
||||
context="{'group_by':'recurring_next_date'}"
|
||||
/>
|
||||
<filter name="date_end"
|
||||
|
||||
@@ -26,6 +26,14 @@
|
||||
<field name="date_end"
|
||||
attrs="{'required': [('is_auto_renew', '=', True)]}"/>
|
||||
</group>
|
||||
<group groups="base.group_no_one">
|
||||
<field name="last_date_invoiced" readonly="True"/>
|
||||
<field name="termination_notice_date" readonly="True"/>
|
||||
</group>
|
||||
<group groups="base.group_no_one">
|
||||
<field name="manual_renew_needed"/>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<field name="predecessor_contract_line_id"/>
|
||||
</group>
|
||||
|
||||
@@ -13,16 +13,26 @@ class AccountAnalyticInvoiceLineWizard(models.TransientModel):
|
||||
date_end = fields.Date(string='Date End')
|
||||
recurring_next_date = fields.Date(string='Next Invoice Date')
|
||||
is_auto_renew = fields.Boolean(string="Auto Renew", default=False)
|
||||
manual_renew_needed = fields.Boolean(
|
||||
string="Manual renew needed",
|
||||
default=False,
|
||||
help="This flag is used to make a difference between a definitive stop"
|
||||
"and temporary one for which a user is not able to plan a"
|
||||
"successor in advance",
|
||||
)
|
||||
contract_line_id = fields.Many2one(
|
||||
comodel_name="account.analytic.invoice.line",
|
||||
string="Contract Line",
|
||||
required=True,
|
||||
index=True,
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def stop(self):
|
||||
for wizard in self:
|
||||
wizard.contract_line_id.stop(wizard.date_end)
|
||||
wizard.contract_line_id.stop(
|
||||
wizard.date_end, manual_renew_needed=wizard.manual_renew_needed
|
||||
)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<group>
|
||||
<field name="contract_line_id" invisible="True"/>
|
||||
<field string="Stop Date" name="date_end" required="True"/>
|
||||
<field string="Is a suspension" name="manual_renew_needed"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="stop"
|
||||
|
||||
Reference in New Issue
Block a user