OCA-git-bot
5 years ago
80 changed files with 5456 additions and 1590 deletions
-
1.gitignore
-
1contract/__init__.py
-
18contract/__manifest__.py
-
4contract/data/contract_cron.xml
-
16contract/data/contract_renew_cron.xml
-
2contract/data/mail_template.xml
-
94contract/migrations/12.0.2.0.0/pre-migration.py
-
47contract/migrations/12.0.4.0.0/post-migration.py
-
113contract/migrations/12.0.4.0.0/pre-migration.py
-
11contract/models/__init__.py
-
73contract/models/abstract_contract.py
-
206contract/models/abstract_contract_line.py
-
361contract/models/account_analytic_account.py
-
100contract/models/account_analytic_contract.py
-
221contract/models/account_analytic_contract_line.py
-
16contract/models/account_analytic_invoice_line.py
-
5contract/models/account_invoice.py
-
12contract/models/account_invoice_line.py
-
457contract/models/contract.py
-
1107contract/models/contract_line.py
-
428contract/models/contract_line_constraints.py
-
22contract/models/contract_template.py
-
24contract/models/contract_template_line.py
-
23contract/models/res_partner.py
-
3contract/readme/CONTRIBUTORS.rst
-
44contract/readme/USAGE.rst
-
2contract/report/contract_views.xml
-
5contract/report/report_contract.xml
-
2contract/security/contract_security.xml
-
14contract/security/ir.model.access.csv
-
1823contract/tests/test_contract.py
-
71contract/views/abstract_contract_line.xml
-
279contract/views/account_analytic_account_view.xml
-
44contract/views/account_invoice_view.xml
-
293contract/views/contract.xml
-
169contract/views/contract_line.xml
-
73contract/views/contract_template.xml
-
17contract/views/contract_template_line.xml
-
21contract/views/res_partner_view.xml
-
1contract/wizards/__init__.py
-
58contract/wizards/contract_line_wizard.py
-
100contract/wizards/contract_line_wizard.xml
-
10contract_sale/__manifest__.py
-
2contract_sale/i18n/contract_sale.pot
-
3contract_sale/i18n/gl.po
-
32contract_sale/migrations/12.0.2.0.0/pre-migration.py
-
8contract_sale/security/contract_security.xml
-
14contract_sale/security/ir.model.access.csv
-
16contract_sale/views/abstract_contract_line.xml
-
28contract_sale/views/account_analytic_account_view.xml
-
21contract_sale/views/account_analytic_contract_view.xml
-
12contract_sale/views/contract.xml
-
16contract_sale/views/contract_line.xml
-
17contract_sale/views/contract_template.xml
-
19contract_sale_invoicing/i18n/ca.po
-
14contract_sale_invoicing/i18n/contract_sale_invoicing.pot
-
31contract_sale_invoicing/i18n/de.po
-
19contract_sale_invoicing/i18n/el_GR.po
-
19contract_sale_invoicing/i18n/es.po
-
19contract_sale_invoicing/i18n/es_MX.po
-
19contract_sale_invoicing/i18n/fi.po
-
19contract_sale_invoicing/i18n/fr.po
-
19contract_sale_invoicing/i18n/gl.po
-
19contract_sale_invoicing/i18n/hi_IN.po
-
19contract_sale_invoicing/i18n/hr.po
-
19contract_sale_invoicing/i18n/hr_HR.po
-
19contract_sale_invoicing/i18n/hu.po
-
30contract_sale_invoicing/i18n/it.po
-
19contract_sale_invoicing/i18n/nl.po
-
15contract_sale_invoicing/i18n/pt.po
-
19contract_sale_invoicing/i18n/pt_PT.po
-
19contract_sale_invoicing/i18n/ro.po
-
19contract_sale_invoicing/i18n/sk_SK.po
-
19contract_sale_invoicing/i18n/sl.po
-
19contract_sale_invoicing/i18n/tr.po
-
19contract_sale_invoicing/i18n/tr_TR.po
-
19contract_sale_invoicing/i18n/zh.po
-
15contract_sale_invoicing/models/contract.py
-
4contract_sale_invoicing/tests/test_contract_sale_invoicing.py
-
16contract_sale_invoicing/views/contract_view.xml
@ -1 +1,2 @@ |
|||||
from . import models |
from . import models |
||||
|
from . import wizards |
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding='UTF-8'?> |
||||
|
<odoo noupdate="1"> |
||||
|
|
||||
|
<record model="ir.cron" id="contract_line_cron_for_renew"> |
||||
|
<field name="name">Renew Contract lines</field> |
||||
|
<field name="model_id" ref="model_contract_line"/> |
||||
|
<field name="state">code</field> |
||||
|
<field name="code">model.cron_renew_contract_line()</field> |
||||
|
<field name="user_id" ref="base.user_root" /> |
||||
|
<field name="interval_number">1</field> |
||||
|
<field name="interval_type">days</field> |
||||
|
<field name="numbercall">-1</field> |
||||
|
<field eval="False" name="doall" /> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,94 @@ |
|||||
|
# Copyright 2018 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
import logging |
||||
|
|
||||
|
from openupgradelib import openupgrade |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
def _set_finished_contract(cr): |
||||
|
_logger.info("set recurring_next_date to false for finished contract") |
||||
|
openupgrade.logged_query( |
||||
|
cr, |
||||
|
""" |
||||
|
UPDATE account_analytic_account |
||||
|
SET recurring_next_date=NULL |
||||
|
WHERE recurring_next_date > date_end |
||||
|
""", |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def _move_contract_recurrence_info_to_contract_line(cr): |
||||
|
_logger.info("Move contract data to line level") |
||||
|
openupgrade.logged_query( |
||||
|
cr, |
||||
|
""" |
||||
|
ALTER TABLE account_analytic_invoice_line |
||||
|
ADD COLUMN IF NOT EXISTS recurring_rule_type VARCHAR(255), |
||||
|
ADD COLUMN IF NOT EXISTS recurring_invoicing_type VARCHAR(255), |
||||
|
ADD COLUMN IF NOT EXISTS recurring_interval INTEGER, |
||||
|
ADD COLUMN IF NOT EXISTS recurring_next_date DATE, |
||||
|
ADD COLUMN IF NOT EXISTS date_start DATE, |
||||
|
ADD COLUMN IF NOT EXISTS date_end DATE |
||||
|
""", |
||||
|
) |
||||
|
|
||||
|
openupgrade.logged_query( |
||||
|
cr, |
||||
|
""" |
||||
|
UPDATE account_analytic_invoice_line AS contract_line |
||||
|
SET |
||||
|
recurring_rule_type=contract.recurring_rule_type, |
||||
|
recurring_invoicing_type=contract.recurring_invoicing_type, |
||||
|
recurring_interval=contract.recurring_interval, |
||||
|
recurring_next_date=contract.recurring_next_date, |
||||
|
date_start=contract.date_start, |
||||
|
date_end=contract.date_end |
||||
|
FROM |
||||
|
account_analytic_account AS contract |
||||
|
WHERE |
||||
|
contract.id=contract_line.analytic_account_id |
||||
|
""", |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def _move_contract_template_recurrence_info_to_contract_template_line(cr): |
||||
|
_logger.info("Move contract template data to line level") |
||||
|
openupgrade.logged_query( |
||||
|
cr, |
||||
|
""" |
||||
|
ALTER TABLE account_analytic_contract_line |
||||
|
ADD COLUMN IF NOT EXISTS recurring_rule_type VARCHAR(255), |
||||
|
ADD COLUMN IF NOT EXISTS recurring_invoicing_type VARCHAR(255), |
||||
|
ADD COLUMN IF NOT EXISTS recurring_interval INTEGER |
||||
|
""", |
||||
|
) |
||||
|
|
||||
|
openupgrade.logged_query( |
||||
|
cr, |
||||
|
""" |
||||
|
UPDATE account_analytic_contract_line AS contract_template_line |
||||
|
SET |
||||
|
recurring_rule_type=contract_template.recurring_rule_type, |
||||
|
recurring_invoicing_type=contract_template.recurring_invoicing_type, |
||||
|
recurring_interval=contract_template.recurring_interval |
||||
|
FROM |
||||
|
account_analytic_contract AS contract_template |
||||
|
WHERE |
||||
|
contract_template.id=contract_template_line.analytic_account_id |
||||
|
""", |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@openupgrade.migrate() |
||||
|
def migrate(env, version): |
||||
|
""" |
||||
|
set recurring_next_date to false for finished contract |
||||
|
""" |
||||
|
_logger.info(">> Pre-Migration 12.0.2.0.0") |
||||
|
cr = env.cr |
||||
|
_set_finished_contract(cr) |
||||
|
_move_contract_recurrence_info_to_contract_line(cr) |
||||
|
_move_contract_template_recurrence_info_to_contract_template_line(cr) |
@ -0,0 +1,47 @@ |
|||||
|
# Copyright 2019 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
import logging |
||||
|
|
||||
|
from openupgradelib import openupgrade |
||||
|
from odoo.tools import parse_version |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
def _update_no_update_ir_cron(env): |
||||
|
# Update ir.cron |
||||
|
env.ref('contract.contract_cron_for_invoice').model_id = env.ref( |
||||
|
'contract.model_contract_contract' |
||||
|
) |
||||
|
env.ref('contract.contract_line_cron_for_renew').model_id = env.ref( |
||||
|
'contract.model_contract_line' |
||||
|
) |
||||
|
env.ref('contract.email_contract_template').model_id = env.ref( |
||||
|
'contract.model_contract_contract' |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def _init_last_date_invoiced_on_contract_lines(env): |
||||
|
_logger.info("init last_date_invoiced field for contract lines") |
||||
|
contract_lines = env["contract.line"].search( |
||||
|
[("recurring_next_date", "!=", False)] |
||||
|
) |
||||
|
contract_lines._init_last_date_invoiced() |
||||
|
|
||||
|
|
||||
|
def _init_invoicing_partner_id_on_contracts(env): |
||||
|
_logger.info("Populate invoicing partner field on contracts") |
||||
|
contracts = env["contract.contract"].search([]) |
||||
|
contracts._inverse_partner_id() |
||||
|
|
||||
|
|
||||
|
@openupgrade.migrate() |
||||
|
def migrate(env, version): |
||||
|
_update_no_update_ir_cron(env) |
||||
|
if parse_version(version) < parse_version('12.0.2.0.0'): |
||||
|
# We check the version here as this post-migration script was in |
||||
|
# 12.0.2.0.0 and already done for those who used the module when |
||||
|
# it was a PR |
||||
|
_init_last_date_invoiced_on_contract_lines(env) |
||||
|
_init_invoicing_partner_id_on_contracts(env) |
@ -0,0 +1,113 @@ |
|||||
|
# Copyright 2019 ACSONE SA/NV |
||||
|
# Copyright 2019 Tecnativa 2019 - Pedro M. Baeza |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
import logging |
||||
|
|
||||
|
from openupgradelib import openupgrade |
||||
|
from psycopg2 import sql |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
models_to_rename = [ |
||||
|
# Contract Line Wizard |
||||
|
('account.analytic.invoice.line.wizard', 'contract.line.wizard'), |
||||
|
# Abstract Contract |
||||
|
('account.abstract.analytic.contract', 'contract.abstract.contract'), |
||||
|
# Abstract Contract Line |
||||
|
( |
||||
|
'account.abstract.analytic.contract.line', |
||||
|
'contract.abstract.contract.line', |
||||
|
), |
||||
|
# Contract Line |
||||
|
('account.analytic.invoice.line', 'contract.line'), |
||||
|
# Contract Template |
||||
|
('account.analytic.contract', 'contract.template'), |
||||
|
# Contract Template Line |
||||
|
('account.analytic.contract.line', 'contract.template.line'), |
||||
|
] |
||||
|
tables_to_rename = [ |
||||
|
# Contract Line |
||||
|
('account_analytic_invoice_line', 'contract_line'), |
||||
|
# Contract Template |
||||
|
('account_analytic_contract', 'contract_template'), |
||||
|
# Contract Template Line |
||||
|
('account_analytic_contract_line', 'contract_template_line'), |
||||
|
] |
||||
|
columns_to_copy = { |
||||
|
'contract_line': [ |
||||
|
('analytic_account_id', 'contract_id', None), |
||||
|
], |
||||
|
} |
||||
|
xmlids_to_rename = [ |
||||
|
( |
||||
|
'contract.account_analytic_cron_for_invoice', |
||||
|
'contract.contract_cron_for_invoice', |
||||
|
), |
||||
|
( |
||||
|
'contract.account_analytic_contract_manager', |
||||
|
'contract.contract_template_manager', |
||||
|
), |
||||
|
( |
||||
|
'contract.account_analytic_contract_user', |
||||
|
'contract.contract_template_user', |
||||
|
), |
||||
|
( |
||||
|
'contract.account_analytic_invoice_line_manager', |
||||
|
'contract.contract_line_manager', |
||||
|
), |
||||
|
( |
||||
|
'contract.account_analytic_invoice_line_user', |
||||
|
'contract.contract_line_user', |
||||
|
), |
||||
|
( |
||||
|
'contract.account_analytic_contract_line_manager', |
||||
|
'contract.contract_template_line_manager', |
||||
|
), |
||||
|
( |
||||
|
'contract.account_analytic_contract_line_user', |
||||
|
'contract.contract_template_line_user', |
||||
|
), |
||||
|
] |
||||
|
|
||||
|
|
||||
|
def _get_contract_field_name(cr): |
||||
|
""" |
||||
|
Contract field changed the name from analytic_account_id to contract_id |
||||
|
in 12.0.2.0.0. This method used to get the contract field name in |
||||
|
account_analytic_invoice_line""" |
||||
|
return ( |
||||
|
'contract_id' |
||||
|
if openupgrade.column_exists( |
||||
|
cr, 'account_analytic_invoice_line', 'contract_id' |
||||
|
) |
||||
|
else 'analytic_account_id' |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def create_contract_records(cr): |
||||
|
contract_field_name = _get_contract_field_name(cr) |
||||
|
openupgrade.logged_query( |
||||
|
cr, """ |
||||
|
CREATE TABLE contract_contract |
||||
|
(LIKE account_analytic_account INCLUDING ALL)""", |
||||
|
) |
||||
|
openupgrade.logged_query( |
||||
|
cr, sql.SQL(""" |
||||
|
INSERT INTO contract_contract |
||||
|
SELECT * FROM account_analytic_account |
||||
|
WHERE id IN (SELECT DISTINCT {} FROM contract_line) |
||||
|
""").format( |
||||
|
sql.Identifier(contract_field_name), |
||||
|
), |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@openupgrade.migrate() |
||||
|
def migrate(env, version): |
||||
|
cr = env.cr |
||||
|
openupgrade.rename_models(cr, models_to_rename) |
||||
|
openupgrade.rename_tables(cr, tables_to_rename) |
||||
|
openupgrade.rename_xmlids(cr, xmlids_to_rename) |
||||
|
openupgrade.copy_columns(cr, columns_to_copy) |
||||
|
create_contract_records(cr) |
@ -1,8 +1,11 @@ |
|||||
# 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 . import account_analytic_contract |
|
||||
from . import account_analytic_account |
|
||||
from . import account_analytic_contract_line |
|
||||
from . import account_analytic_invoice_line |
|
||||
|
from . import abstract_contract |
||||
|
from . import abstract_contract_line |
||||
|
from . import contract_template |
||||
|
from . import contract |
||||
|
from . import contract_template_line |
||||
|
from . import contract_line |
||||
from . import account_invoice |
from . import account_invoice |
||||
|
from . import account_invoice_line |
||||
from . import res_partner |
from . import res_partner |
@ -0,0 +1,73 @@ |
|||||
|
# Copyright 2004-2010 OpenERP SA |
||||
|
# Copyright 2014 Angel Moya <angel.moya@domatix.com> |
||||
|
# Copyright 2015 Pedro M. Baeza <pedro.baeza@tecnativa.com> |
||||
|
# Copyright 2016-2018 Carlos Dauden <carlos.dauden@tecnativa.com> |
||||
|
# Copyright 2016-2017 LasLabs Inc. |
||||
|
# Copyright 2018 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import api, models, fields |
||||
|
|
||||
|
|
||||
|
class ContractAbstractContract(models.AbstractModel): |
||||
|
_name = 'contract.abstract.contract' |
||||
|
_description = 'Abstract Recurring Contract' |
||||
|
|
||||
|
# These fields will not be synced to the contract |
||||
|
NO_SYNC = ['name', 'partner_id'] |
||||
|
|
||||
|
name = fields.Char(required=True) |
||||
|
# Needed for avoiding errors on several inherited behaviors |
||||
|
partner_id = fields.Many2one( |
||||
|
comodel_name="res.partner", string="Partner (always False)", index=True |
||||
|
) |
||||
|
pricelist_id = fields.Many2one( |
||||
|
comodel_name='product.pricelist', string='Pricelist' |
||||
|
) |
||||
|
contract_type = fields.Selection( |
||||
|
selection=[('sale', 'Customer'), ('purchase', 'Supplier')], |
||||
|
default='sale', |
||||
|
index=True, |
||||
|
) |
||||
|
|
||||
|
journal_id = fields.Many2one( |
||||
|
'account.journal', |
||||
|
string='Journal', |
||||
|
default=lambda s: s._default_journal(), |
||||
|
domain="[('type', '=', contract_type)," |
||||
|
"('company_id', '=', company_id)]", |
||||
|
index=True, |
||||
|
) |
||||
|
company_id = fields.Many2one( |
||||
|
'res.company', |
||||
|
string='Company', |
||||
|
required=True, |
||||
|
default=lambda self: self.env['res.company']._company_default_get( |
||||
|
self._name |
||||
|
), |
||||
|
) |
||||
|
|
||||
|
@api.onchange('contract_type') |
||||
|
def _onchange_contract_type(self): |
||||
|
if self.contract_type == 'purchase': |
||||
|
self.contract_line_ids.filtered('automatic_price').update( |
||||
|
{'automatic_price': False} |
||||
|
) |
||||
|
self.journal_id = self.env['account.journal'].search( |
||||
|
[ |
||||
|
('type', '=', self.contract_type), |
||||
|
('company_id', '=', self.company_id.id), |
||||
|
], |
||||
|
limit=1, |
||||
|
) |
||||
|
|
||||
|
@api.model |
||||
|
def _default_journal(self): |
||||
|
company_id = self.env.context.get( |
||||
|
'company_id', self.env.user.company_id.id |
||||
|
) |
||||
|
domain = [ |
||||
|
('type', '=', self.contract_type), |
||||
|
('company_id', '=', company_id), |
||||
|
] |
||||
|
return self.env['account.journal'].search(domain, limit=1) |
@ -0,0 +1,206 @@ |
|||||
|
# Copyright 2004-2010 OpenERP SA |
||||
|
# Copyright 2014 Angel Moya <angel.moya@domatix.com> |
||||
|
# Copyright 2015 Pedro M. Baeza <pedro.baeza@tecnativa.com> |
||||
|
# Copyright 2016-2018 Carlos Dauden <carlos.dauden@tecnativa.com> |
||||
|
# Copyright 2016-2017 LasLabs Inc. |
||||
|
# Copyright 2018 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import api, models, fields |
||||
|
from odoo.addons import decimal_precision as dp |
||||
|
from odoo.exceptions import ValidationError |
||||
|
from odoo.tools.translate import _ |
||||
|
|
||||
|
|
||||
|
class ContractAbstractContractLine(models.AbstractModel): |
||||
|
_name = 'contract.abstract.contract.line' |
||||
|
_description = 'Abstract Recurring Contract Line' |
||||
|
|
||||
|
product_id = fields.Many2one( |
||||
|
'product.product', string='Product', required=True |
||||
|
) |
||||
|
|
||||
|
name = fields.Text(string='Description', required=True) |
||||
|
quantity = fields.Float(default=1.0, required=True) |
||||
|
uom_id = fields.Many2one( |
||||
|
'uom.uom', string='Unit of Measure', required=True |
||||
|
) |
||||
|
automatic_price = fields.Boolean( |
||||
|
string="Auto-price?", |
||||
|
help="If this is marked, the price will be obtained automatically " |
||||
|
"applying the pricelist to the product. If not, you will be " |
||||
|
"able to introduce a manual price", |
||||
|
) |
||||
|
specific_price = fields.Float(string='Specific Price') |
||||
|
price_unit = fields.Float( |
||||
|
string='Unit Price', |
||||
|
compute="_compute_price_unit", |
||||
|
inverse="_inverse_price_unit", |
||||
|
) |
||||
|
price_subtotal = fields.Float( |
||||
|
compute='_compute_price_subtotal', |
||||
|
digits=dp.get_precision('Account'), |
||||
|
string='Sub Total', |
||||
|
) |
||||
|
discount = fields.Float( |
||||
|
string='Discount (%)', |
||||
|
digits=dp.get_precision('Discount'), |
||||
|
help='Discount that is applied in generated invoices.' |
||||
|
' It should be less or equal to 100', |
||||
|
) |
||||
|
sequence = fields.Integer( |
||||
|
string="Sequence", |
||||
|
default=10, |
||||
|
help="Sequence of the contract line when displaying contracts", |
||||
|
) |
||||
|
recurring_rule_type = fields.Selection( |
||||
|
[ |
||||
|
('daily', 'Day(s)'), |
||||
|
('weekly', 'Week(s)'), |
||||
|
('monthly', 'Month(s)'), |
||||
|
('monthlylastday', 'Month(s) last day'), |
||||
|
('yearly', 'Year(s)'), |
||||
|
], |
||||
|
default='monthly', |
||||
|
string='Recurrence', |
||||
|
help="Specify Interval for automatic invoice generation.", |
||||
|
required=True, |
||||
|
) |
||||
|
recurring_invoicing_type = fields.Selection( |
||||
|
[('pre-paid', 'Pre-paid'), ('post-paid', 'Post-paid')], |
||||
|
default='pre-paid', |
||||
|
string='Invoicing type', |
||||
|
help="Specify if process date is 'from' or 'to' invoicing date", |
||||
|
required=True, |
||||
|
) |
||||
|
recurring_interval = fields.Integer( |
||||
|
default=1, |
||||
|
string='Invoice Every', |
||||
|
help="Invoice every (Days/Week/Month/Year)", |
||||
|
required=True, |
||||
|
) |
||||
|
date_start = fields.Date(string='Date Start') |
||||
|
recurring_next_date = fields.Date(string='Date of Next Invoice') |
||||
|
last_date_invoiced = fields.Date(string='Last Date Invoiced') |
||||
|
is_canceled = fields.Boolean(string="Canceled", default=False) |
||||
|
is_auto_renew = fields.Boolean(string="Auto Renew", default=False) |
||||
|
auto_renew_interval = fields.Integer( |
||||
|
default=1, |
||||
|
string='Renew Every', |
||||
|
help="Renew every (Days/Week/Month/Year)", |
||||
|
) |
||||
|
auto_renew_rule_type = fields.Selection( |
||||
|
[ |
||||
|
('daily', 'Day(s)'), |
||||
|
('weekly', 'Week(s)'), |
||||
|
('monthly', 'Month(s)'), |
||||
|
('yearly', 'Year(s)'), |
||||
|
], |
||||
|
default='yearly', |
||||
|
string='Renewal type', |
||||
|
help="Specify Interval for automatic renewal.", |
||||
|
) |
||||
|
termination_notice_interval = fields.Integer( |
||||
|
default=1, string='Termination Notice Before' |
||||
|
) |
||||
|
termination_notice_rule_type = fields.Selection( |
||||
|
[('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)')], |
||||
|
default='monthly', |
||||
|
string='Termination Notice type', |
||||
|
) |
||||
|
contract_id = fields.Many2one( |
||||
|
string='Contract', |
||||
|
comodel_name='contract.abstract.contract', |
||||
|
required=True, |
||||
|
ondelete='cascade', |
||||
|
) |
||||
|
|
||||
|
@api.depends( |
||||
|
'automatic_price', |
||||
|
'specific_price', |
||||
|
'product_id', |
||||
|
'quantity', |
||||
|
'contract_id.pricelist_id', |
||||
|
'contract_id.partner_id', |
||||
|
) |
||||
|
def _compute_price_unit(self): |
||||
|
"""Get the specific price if no auto-price, and the price obtained |
||||
|
from the pricelist otherwise. |
||||
|
""" |
||||
|
for line in self: |
||||
|
if line.automatic_price: |
||||
|
product = line.product_id.with_context( |
||||
|
quantity=line.env.context.get( |
||||
|
'contract_line_qty', |
||||
|
line.quantity, |
||||
|
), |
||||
|
pricelist=line.contract_id.pricelist_id.id, |
||||
|
partner=line.contract_id.partner_id.id, |
||||
|
date=line.env.context.get( |
||||
|
'old_date', fields.Date.context_today(line) |
||||
|
), |
||||
|
) |
||||
|
line.price_unit = product.price |
||||
|
else: |
||||
|
line.price_unit = line.specific_price |
||||
|
|
||||
|
# Tip in https://github.com/odoo/odoo/issues/23891#issuecomment-376910788 |
||||
|
@api.onchange('price_unit') |
||||
|
def _inverse_price_unit(self): |
||||
|
"""Store the specific price in the no auto-price records.""" |
||||
|
for line in self.filtered(lambda x: not x.automatic_price): |
||||
|
line.specific_price = line.price_unit |
||||
|
|
||||
|
@api.multi |
||||
|
@api.depends('quantity', 'price_unit', 'discount') |
||||
|
def _compute_price_subtotal(self): |
||||
|
for line in self: |
||||
|
subtotal = line.quantity * line.price_unit |
||||
|
discount = line.discount / 100 |
||||
|
subtotal *= 1 - discount |
||||
|
if line.contract_id.pricelist_id: |
||||
|
cur = line.contract_id.pricelist_id.currency_id |
||||
|
line.price_subtotal = cur.round(subtotal) |
||||
|
else: |
||||
|
line.price_subtotal = subtotal |
||||
|
|
||||
|
@api.multi |
||||
|
@api.constrains('discount') |
||||
|
def _check_discount(self): |
||||
|
for line in self: |
||||
|
if line.discount > 100: |
||||
|
raise ValidationError( |
||||
|
_("Discount should be less or equal to 100") |
||||
|
) |
||||
|
|
||||
|
@api.multi |
||||
|
@api.onchange('product_id') |
||||
|
def _onchange_product_id(self): |
||||
|
if not self.product_id: |
||||
|
return {'domain': {'uom_id': []}} |
||||
|
|
||||
|
vals = {} |
||||
|
domain = { |
||||
|
'uom_id': [ |
||||
|
('category_id', '=', self.product_id.uom_id.category_id.id) |
||||
|
] |
||||
|
} |
||||
|
if not self.uom_id or ( |
||||
|
self.product_id.uom_id.category_id.id != self.uom_id.category_id.id |
||||
|
): |
||||
|
vals['uom_id'] = self.product_id.uom_id |
||||
|
|
||||
|
date = self.recurring_next_date or fields.Date.context_today(self) |
||||
|
partner = self.contract_id.partner_id or self.env.user.partner_id |
||||
|
product = self.product_id.with_context( |
||||
|
lang=partner.lang, |
||||
|
partner=partner.id, |
||||
|
quantity=self.quantity, |
||||
|
date=date, |
||||
|
pricelist=self.contract_id.pricelist_id.id, |
||||
|
uom=self.uom_id.id, |
||||
|
) |
||||
|
vals['name'] = self.product_id.get_product_multiline_description_sale() |
||||
|
vals['price_unit'] = product.price |
||||
|
self.update(vals) |
||||
|
return {'domain': domain} |
@ -1,361 +0,0 @@ |
|||||
# Copyright 2004-2010 OpenERP SA |
|
||||
# Copyright 2014 Angel Moya <angel.moya@domatix.com> |
|
||||
# Copyright 2015 Pedro M. Baeza <pedro.baeza@tecnativa.com> |
|
||||
# Copyright 2016-2018 Carlos Dauden <carlos.dauden@tecnativa.com> |
|
||||
# Copyright 2016-2017 LasLabs Inc. |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
|
|
||||
from dateutil.relativedelta import relativedelta |
|
||||
|
|
||||
from odoo import api, fields, models |
|
||||
from odoo.exceptions import ValidationError |
|
||||
from odoo.tools.translate import _ |
|
||||
|
|
||||
|
|
||||
class AccountAnalyticAccount(models.Model): |
|
||||
_name = 'account.analytic.account' |
|
||||
_inherit = ['account.analytic.account', |
|
||||
'account.analytic.contract', |
|
||||
] |
|
||||
|
|
||||
contract_template_id = fields.Many2one( |
|
||||
string='Contract Template', |
|
||||
comodel_name='account.analytic.contract', |
|
||||
) |
|
||||
recurring_invoice_line_ids = fields.One2many( |
|
||||
string='Invoice Lines', |
|
||||
comodel_name='account.analytic.invoice.line', |
|
||||
inverse_name='analytic_account_id', |
|
||||
copy=True, |
|
||||
) |
|
||||
date_start = fields.Date( |
|
||||
string='Date Start', |
|
||||
default=fields.Date.context_today, |
|
||||
) |
|
||||
date_end = fields.Date( |
|
||||
string='Date End', |
|
||||
index=True, |
|
||||
) |
|
||||
recurring_invoices = fields.Boolean( |
|
||||
string='Generate recurring invoices automatically', |
|
||||
) |
|
||||
recurring_next_date = fields.Date( |
|
||||
default=fields.Date.context_today, |
|
||||
copy=False, |
|
||||
string='Date of Next Invoice', |
|
||||
) |
|
||||
user_id = fields.Many2one( |
|
||||
comodel_name='res.users', |
|
||||
string='Responsible', |
|
||||
index=True, |
|
||||
default=lambda self: self.env.user, |
|
||||
) |
|
||||
create_invoice_visibility = fields.Boolean( |
|
||||
compute='_compute_create_invoice_visibility', |
|
||||
) |
|
||||
|
|
||||
@api.depends('recurring_next_date', 'date_end') |
|
||||
def _compute_create_invoice_visibility(self): |
|
||||
for contract in self: |
|
||||
contract.create_invoice_visibility = ( |
|
||||
not contract.date_end or |
|
||||
contract.recurring_next_date <= contract.date_end |
|
||||
) |
|
||||
|
|
||||
@api.onchange('contract_template_id') |
|
||||
def _onchange_contract_template_id(self): |
|
||||
"""Update the contract fields with that of the template. |
|
||||
|
|
||||
Take special consideration with the `recurring_invoice_line_ids`, |
|
||||
which must be created using the data from the contract lines. Cascade |
|
||||
deletion ensures that any errant lines that are created are also |
|
||||
deleted. |
|
||||
""" |
|
||||
contract = self.contract_template_id |
|
||||
if not contract: |
|
||||
return |
|
||||
for field_name, field in contract._fields.items(): |
|
||||
if field.name == 'recurring_invoice_line_ids': |
|
||||
lines = self._convert_contract_lines(contract) |
|
||||
self.recurring_invoice_line_ids = lines |
|
||||
elif not any(( |
|
||||
field.compute, field.related, field.automatic, |
|
||||
field.readonly, field.company_dependent, |
|
||||
field.name in self.NO_SYNC, |
|
||||
)): |
|
||||
self[field_name] = self.contract_template_id[field_name] |
|
||||
|
|
||||
@api.onchange('date_start') |
|
||||
def _onchange_date_start(self): |
|
||||
if self.date_start: |
|
||||
self.recurring_next_date = self.date_start |
|
||||
|
|
||||
@api.onchange('partner_id') |
|
||||
def _onchange_partner_id(self): |
|
||||
self.pricelist_id = self.partner_id.property_product_pricelist.id |
|
||||
|
|
||||
@api.constrains('partner_id', 'recurring_invoices') |
|
||||
def _check_partner_id_recurring_invoices(self): |
|
||||
for contract in self.filtered('recurring_invoices'): |
|
||||
if not contract.partner_id: |
|
||||
raise ValidationError( |
|
||||
_("You must supply a customer for the contract '%s'") % |
|
||||
contract.name |
|
||||
) |
|
||||
|
|
||||
@api.constrains('recurring_next_date', 'date_start') |
|
||||
def _check_recurring_next_date_start_date(self): |
|
||||
for contract in self.filtered('recurring_next_date'): |
|
||||
if contract.date_start > contract.recurring_next_date: |
|
||||
raise ValidationError( |
|
||||
_("You can't have a next invoicing date before the start " |
|
||||
"of the contract '%s'") % contract.name |
|
||||
) |
|
||||
|
|
||||
@api.constrains('recurring_next_date', 'recurring_invoices') |
|
||||
def _check_recurring_next_date_recurring_invoices(self): |
|
||||
for contract in self.filtered('recurring_invoices'): |
|
||||
if not contract.recurring_next_date: |
|
||||
raise ValidationError( |
|
||||
_("You must supply a next invoicing date for contract " |
|
||||
"'%s'") % contract.name |
|
||||
) |
|
||||
|
|
||||
@api.constrains('date_start', 'recurring_invoices') |
|
||||
def _check_date_start_recurring_invoices(self): |
|
||||
for contract in self.filtered('recurring_invoices'): |
|
||||
if not contract.date_start: |
|
||||
raise ValidationError( |
|
||||
_("You must supply a start date for contract '%s'") % |
|
||||
contract.name |
|
||||
) |
|
||||
|
|
||||
@api.constrains('date_start', 'date_end') |
|
||||
def _check_start_end_dates(self): |
|
||||
for contract in self.filtered('date_end'): |
|
||||
if contract.date_start > contract.date_end: |
|
||||
raise ValidationError( |
|
||||
_("Contract '%s' start date can't be later than end date") |
|
||||
% contract.name |
|
||||
) |
|
||||
|
|
||||
@api.multi |
|
||||
def _convert_contract_lines(self, contract): |
|
||||
self.ensure_one() |
|
||||
new_lines = [] |
|
||||
for contract_line in contract.recurring_invoice_line_ids: |
|
||||
vals = contract_line._convert_to_write(contract_line.read()[0]) |
|
||||
# Remove template link field named as analytic account field |
|
||||
vals.pop('analytic_account_id', False) |
|
||||
new_lines.append((0, 0, vals)) |
|
||||
return new_lines |
|
||||
|
|
||||
@api.model |
|
||||
def get_relative_delta(self, recurring_rule_type, interval): |
|
||||
if recurring_rule_type == 'daily': |
|
||||
return relativedelta(days=interval) |
|
||||
elif recurring_rule_type == 'weekly': |
|
||||
return relativedelta(weeks=interval) |
|
||||
elif recurring_rule_type == 'monthly': |
|
||||
return relativedelta(months=interval) |
|
||||
elif recurring_rule_type == 'monthlylastday': |
|
||||
return relativedelta(months=interval, day=31) |
|
||||
else: |
|
||||
return relativedelta(years=interval) |
|
||||
|
|
||||
@api.model |
|
||||
def _insert_markers(self, line, date_format): |
|
||||
date_from = fields.Date.from_string(line.date_from) |
|
||||
date_to = fields.Date.from_string(line.date_to) |
|
||||
name = line.name |
|
||||
name = name.replace('#START#', date_from.strftime(date_format)) |
|
||||
name = name.replace('#END#', date_to.strftime(date_format)) |
|
||||
return name |
|
||||
|
|
||||
@api.model |
|
||||
def _prepare_invoice_line(self, line, invoice_id): |
|
||||
invoice_line = self.env['account.invoice.line'].new({ |
|
||||
'invoice_id': invoice_id, |
|
||||
'product_id': line.product_id.id, |
|
||||
'quantity': line.quantity, |
|
||||
'uom_id': line.uom_id.id, |
|
||||
'discount': line.discount, |
|
||||
}) |
|
||||
# Get other invoice line values from product onchange |
|
||||
invoice_line._onchange_product_id() |
|
||||
invoice_line_vals = invoice_line._convert_to_write(invoice_line._cache) |
|
||||
# Insert markers |
|
||||
contract = line.analytic_account_id |
|
||||
lang_obj = self.env['res.lang'] |
|
||||
lang = lang_obj.search( |
|
||||
[('code', '=', contract.partner_id.lang)]) |
|
||||
date_format = lang.date_format or '%m/%d/%Y' |
|
||||
name = self._insert_markers(line, date_format) |
|
||||
invoice_line_vals.update({ |
|
||||
'name': name, |
|
||||
'account_analytic_id': contract.id, |
|
||||
'price_unit': line.price_unit, |
|
||||
}) |
|
||||
return invoice_line_vals |
|
||||
|
|
||||
@api.multi |
|
||||
def _prepare_invoice(self, 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 or self.env['account.journal'].search([ |
|
||||
('type', '=', self.contract_type), |
|
||||
('company_id', '=', self.company_id.id) |
|
||||
], limit=1) |
|
||||
if not journal: |
|
||||
raise ValidationError( |
|
||||
_("Please define a %s journal for the company '%s'.") % |
|
||||
(self.contract_type, self.company_id.name or '') |
|
||||
) |
|
||||
currency = ( |
|
||||
self.pricelist_id.currency_id or |
|
||||
self.partner_id.property_product_pricelist.currency_id or |
|
||||
self.company_id.currency_id |
|
||||
) |
|
||||
invoice_type = 'out_invoice' |
|
||||
if self.contract_type == 'purchase': |
|
||||
invoice_type = 'in_invoice' |
|
||||
invoice = self.env['account.invoice'].new({ |
|
||||
'reference': self.code, |
|
||||
'type': invoice_type, |
|
||||
'partner_id': self.partner_id.address_get( |
|
||||
['invoice'])['invoice'], |
|
||||
'currency_id': currency.id, |
|
||||
'journal_id': journal.id, |
|
||||
'date_invoice': self.recurring_next_date, |
|
||||
'origin': self.name, |
|
||||
'company_id': self.company_id.id, |
|
||||
'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 _prepare_invoice_update(self, invoice): |
|
||||
vals = self._prepare_invoice() |
|
||||
update_vals = { |
|
||||
'contract_id': self.id, |
|
||||
'date_invoice': vals.get('date_invoice', False), |
|
||||
'reference': ' '.join(filter(None, [ |
|
||||
invoice.reference, vals.get('reference')])), |
|
||||
'origin': ' '.join(filter(None, [ |
|
||||
invoice.origin, vals.get('origin')])), |
|
||||
} |
|
||||
return update_vals |
|
||||
|
|
||||
@api.multi |
|
||||
def _create_invoice(self, invoice=False): |
|
||||
""" |
|
||||
:param invoice: If not False add lines to this invoice |
|
||||
:return: invoice created or updated |
|
||||
""" |
|
||||
self.ensure_one() |
|
||||
if invoice and invoice.state == 'draft': |
|
||||
invoice.update(self._prepare_invoice_update(invoice)) |
|
||||
else: |
|
||||
invoice = self.env['account.invoice'].create( |
|
||||
self._prepare_invoice()) |
|
||||
for line in self.recurring_invoice_line_ids: |
|
||||
invoice_line_vals = self._prepare_invoice_line(line, invoice.id) |
|
||||
if invoice_line_vals: |
|
||||
self.env['account.invoice.line'].create(invoice_line_vals) |
|
||||
invoice.compute_taxes() |
|
||||
return invoice |
|
||||
|
|
||||
@api.multi |
|
||||
def recurring_create_invoice(self): |
|
||||
"""Create invoices from contracts |
|
||||
|
|
||||
:return: invoices created |
|
||||
""" |
|
||||
invoices = self.env['account.invoice'] |
|
||||
for contract in self: |
|
||||
ref_date = contract.recurring_next_date or fields.Date.today() |
|
||||
if (contract.date_start > ref_date or |
|
||||
contract.date_end and contract.date_end < ref_date): |
|
||||
if self.env.context.get('cron'): |
|
||||
continue # Don't fail on cron jobs |
|
||||
raise ValidationError( |
|
||||
_("You must review start and end dates!\n%s") % |
|
||||
contract.name |
|
||||
) |
|
||||
old_date = fields.Date.from_string(ref_date) |
|
||||
new_date = old_date + self.get_relative_delta( |
|
||||
contract.recurring_rule_type, contract.recurring_interval) |
|
||||
ctx = self.env.context.copy() |
|
||||
ctx.update({ |
|
||||
'old_date': old_date, |
|
||||
'next_date': new_date, |
|
||||
# Force company for correct evaluation of domain access rules |
|
||||
'force_company': contract.company_id.id, |
|
||||
}) |
|
||||
# Re-read contract with correct company |
|
||||
invoices |= contract.with_context(ctx)._create_invoice() |
|
||||
contract.write({ |
|
||||
'recurring_next_date': fields.Date.to_string(new_date) |
|
||||
}) |
|
||||
return invoices |
|
||||
|
|
||||
@api.model |
|
||||
def cron_recurring_create_invoice(self): |
|
||||
today = fields.Date.today() |
|
||||
contracts = self.with_context(cron=True).search([ |
|
||||
('recurring_invoices', '=', True), |
|
||||
('recurring_next_date', '<=', today), |
|
||||
'|', |
|
||||
('date_end', '=', False), |
|
||||
('date_end', '>=', today), |
|
||||
]) |
|
||||
return contracts.recurring_create_invoice() |
|
||||
|
|
||||
@api.multi |
|
||||
def action_contract_send(self): |
|
||||
self.ensure_one() |
|
||||
template = self.env.ref( |
|
||||
'contract.email_contract_template', |
|
||||
False, |
|
||||
) |
|
||||
compose_form = self.env.ref('mail.email_compose_message_wizard_form') |
|
||||
ctx = dict( |
|
||||
default_model='account.analytic.account', |
|
||||
default_res_id=self.id, |
|
||||
default_use_template=bool(template), |
|
||||
default_template_id=template and template.id or False, |
|
||||
default_composition_mode='comment', |
|
||||
) |
|
||||
return { |
|
||||
'name': _('Compose Email'), |
|
||||
'type': 'ir.actions.act_window', |
|
||||
'view_type': 'form', |
|
||||
'view_mode': 'form', |
|
||||
'res_model': 'mail.compose.message', |
|
||||
'views': [(compose_form.id, 'form')], |
|
||||
'view_id': compose_form.id, |
|
||||
'target': 'new', |
|
||||
'context': ctx, |
|
||||
} |
|
||||
|
|
||||
@api.multi |
|
||||
def button_show_recurring_invoices(self): |
|
||||
self.ensure_one() |
|
||||
action = self.env.ref( |
|
||||
'contract.act_purchase_recurring_invoices') |
|
||||
if self.contract_type == 'sale': |
|
||||
action = self.env.ref( |
|
||||
'contract.act_recurring_invoices') |
|
||||
return action.read()[0] |
|
@ -1,100 +0,0 @@ |
|||||
# Copyright 2004-2010 OpenERP SA |
|
||||
# Copyright 2014 Angel Moya <angel.moya@domatix.com> |
|
||||
# Copyright 2016 Carlos Dauden <carlos.dauden@tecnativa.com> |
|
||||
# Copyright 2016-2017 LasLabs Inc. |
|
||||
# Copyright 2015-2017 Tecnativa - Pedro M. Baeza |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
|
|
||||
from odoo import api, fields, models |
|
||||
|
|
||||
|
|
||||
class AccountAnalyticContract(models.Model): |
|
||||
_name = 'account.analytic.contract' |
|
||||
_description = "Account Analytic Contract" |
|
||||
|
|
||||
# These fields will not be synced to the contract |
|
||||
NO_SYNC = [ |
|
||||
'name', |
|
||||
'partner_id', |
|
||||
] |
|
||||
|
|
||||
name = fields.Char( |
|
||||
required=True, |
|
||||
) |
|
||||
# Needed for avoiding errors on several inherited behaviors |
|
||||
partner_id = fields.Many2one( |
|
||||
comodel_name="res.partner", |
|
||||
string="Partner (always False)", |
|
||||
) |
|
||||
contract_type = fields.Selection( |
|
||||
selection=[ |
|
||||
('sale', 'Customer'), |
|
||||
('purchase', 'Supplier'), |
|
||||
], default='sale', |
|
||||
) |
|
||||
pricelist_id = fields.Many2one( |
|
||||
comodel_name='product.pricelist', |
|
||||
string='Pricelist', |
|
||||
) |
|
||||
recurring_invoice_line_ids = fields.One2many( |
|
||||
comodel_name='account.analytic.contract.line', |
|
||||
inverse_name='analytic_account_id', |
|
||||
copy=True, |
|
||||
string='Invoice Lines', |
|
||||
) |
|
||||
recurring_rule_type = fields.Selection( |
|
||||
[('daily', 'Day(s)'), |
|
||||
('weekly', 'Week(s)'), |
|
||||
('monthly', 'Month(s)'), |
|
||||
('monthlylastday', 'Month(s) last day'), |
|
||||
('yearly', 'Year(s)'), |
|
||||
], |
|
||||
default='monthly', |
|
||||
string='Recurrence', |
|
||||
help="Specify Interval for automatic invoice generation.", |
|
||||
) |
|
||||
recurring_invoicing_type = fields.Selection( |
|
||||
[('pre-paid', 'Pre-paid'), |
|
||||
('post-paid', 'Post-paid'), |
|
||||
], |
|
||||
default='pre-paid', |
|
||||
string='Invoicing type', |
|
||||
help="Specify if process date is 'from' or 'to' invoicing date", |
|
||||
) |
|
||||
recurring_interval = fields.Integer( |
|
||||
default=1, |
|
||||
string='Repeat Every', |
|
||||
help="Repeat every (Days/Week/Month/Year)", |
|
||||
) |
|
||||
journal_id = fields.Many2one( |
|
||||
'account.journal', |
|
||||
string='Journal', |
|
||||
default=lambda s: s._default_journal(), |
|
||||
domain="[('type', '=', contract_type)," |
|
||||
"('company_id', '=', company_id)]", |
|
||||
) |
|
||||
company_id = fields.Many2one( |
|
||||
'res.company', |
|
||||
string='Company', |
|
||||
required=True, |
|
||||
default=lambda self: self.env.user.company_id, |
|
||||
) |
|
||||
|
|
||||
@api.onchange('contract_type') |
|
||||
def _onchange_contract_type(self): |
|
||||
if self.contract_type == 'purchase': |
|
||||
self.recurring_invoice_line_ids.filtered('automatic_price').update( |
|
||||
{'automatic_price': False}) |
|
||||
self.journal_id = self.env['account.journal'].search([ |
|
||||
('type', '=', self.contract_type), |
|
||||
('company_id', '=', self.company_id.id) |
|
||||
], limit=1) |
|
||||
|
|
||||
@api.model |
|
||||
def _default_journal(self): |
|
||||
company_id = self.env.context.get( |
|
||||
'company_id', self.env.user.company_id.id) |
|
||||
domain = [ |
|
||||
('type', '=', self.contract_type), |
|
||||
('company_id', '=', company_id)] |
|
||||
return self.env['account.journal'].search(domain, limit=1) |
|
@ -1,221 +0,0 @@ |
|||||
# Copyright 2004-2010 OpenERP SA |
|
||||
# Copyright 2014 Angel Moya <angel.moya@domatix.com> |
|
||||
# Copyright 2016 Carlos Dauden <carlos.dauden@tecnativa.com> |
|
||||
# Copyright 2016-2017 LasLabs Inc. |
|
||||
# Copyright 2015-2018 Tecnativa - Pedro M. Baeza |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
|
|
||||
from dateutil.relativedelta import relativedelta |
|
||||
|
|
||||
from odoo import api, fields, models |
|
||||
from odoo.addons import decimal_precision as dp |
|
||||
from odoo.exceptions import ValidationError |
|
||||
from odoo.tools.translate import _ |
|
||||
|
|
||||
|
|
||||
class AccountAnalyticContractLine(models.Model): |
|
||||
_name = 'account.analytic.contract.line' |
|
||||
_description = 'Contract Lines' |
|
||||
_order = "sequence,id" |
|
||||
|
|
||||
product_id = fields.Many2one( |
|
||||
'product.product', |
|
||||
string='Product', |
|
||||
required=True, |
|
||||
) |
|
||||
analytic_account_id = fields.Many2one( |
|
||||
string='Contract', |
|
||||
comodel_name='account.analytic.contract', |
|
||||
required=True, |
|
||||
ondelete='cascade', |
|
||||
) |
|
||||
name = fields.Text( |
|
||||
string='Description', |
|
||||
required=True, |
|
||||
) |
|
||||
quantity = fields.Float( |
|
||||
default=1.0, |
|
||||
required=True, |
|
||||
) |
|
||||
uom_id = fields.Many2one( |
|
||||
'uom.uom', |
|
||||
string='Unit of Measure', |
|
||||
required=True, |
|
||||
) |
|
||||
automatic_price = fields.Boolean( |
|
||||
string="Auto-price?", |
|
||||
help="If this is marked, the price will be obtained automatically " |
|
||||
"applying the pricelist to the product. If not, you will be " |
|
||||
"able to introduce a manual price", |
|
||||
) |
|
||||
specific_price = fields.Float( |
|
||||
string='Specific Price', |
|
||||
) |
|
||||
price_unit = fields.Float( |
|
||||
string='Unit Price', |
|
||||
compute="_compute_price_unit", |
|
||||
inverse="_inverse_price_unit", |
|
||||
) |
|
||||
price_subtotal = fields.Float( |
|
||||
compute='_compute_price_subtotal', |
|
||||
digits=dp.get_precision('Account'), |
|
||||
string='Sub Total', |
|
||||
) |
|
||||
discount = fields.Float( |
|
||||
string='Discount (%)', |
|
||||
digits=dp.get_precision('Discount'), |
|
||||
help='Discount that is applied in generated invoices.' |
|
||||
' It should be less or equal to 100', |
|
||||
) |
|
||||
sequence = fields.Integer( |
|
||||
string="Sequence", |
|
||||
default=10, |
|
||||
help="Sequence of the contract line when displaying contracts", |
|
||||
) |
|
||||
date_from = fields.Date( |
|
||||
string='Date From', |
|
||||
compute='_compute_date_from', |
|
||||
help='Date from invoiced period', |
|
||||
) |
|
||||
date_to = fields.Date( |
|
||||
string='Date To', |
|
||||
compute='_compute_date_to', |
|
||||
help='Date to invoiced period', |
|
||||
) |
|
||||
|
|
||||
@api.depends( |
|
||||
'automatic_price', |
|
||||
'specific_price', |
|
||||
'product_id', |
|
||||
'quantity', |
|
||||
'analytic_account_id.pricelist_id', |
|
||||
'analytic_account_id.partner_id', |
|
||||
) |
|
||||
def _compute_price_unit(self): |
|
||||
"""Get the specific price if no auto-price, and the price obtained |
|
||||
from the pricelist otherwise. |
|
||||
""" |
|
||||
for line in self: |
|
||||
if line.automatic_price: |
|
||||
product = line.product_id.with_context( |
|
||||
quantity=line.env.context.get( |
|
||||
'contract_line_qty', line.quantity, |
|
||||
), |
|
||||
pricelist=line.analytic_account_id.pricelist_id.id, |
|
||||
partner=line.analytic_account_id.partner_id.id, |
|
||||
date=line.env.context.get('old_date', fields.Date.today()), |
|
||||
) |
|
||||
line.price_unit = product.price |
|
||||
else: |
|
||||
line.price_unit = line.specific_price |
|
||||
|
|
||||
# Tip in https://github.com/odoo/odoo/issues/23891#issuecomment-376910788 |
|
||||
@api.onchange('price_unit') |
|
||||
def _inverse_price_unit(self): |
|
||||
"""Store the specific price in the no auto-price records.""" |
|
||||
for line in self.filtered(lambda x: not x.automatic_price): |
|
||||
line.specific_price = line.price_unit |
|
||||
|
|
||||
@api.multi |
|
||||
@api.depends('quantity', 'price_unit', 'discount') |
|
||||
def _compute_price_subtotal(self): |
|
||||
for line in self: |
|
||||
subtotal = line.quantity * line.price_unit |
|
||||
discount = line.discount / 100 |
|
||||
subtotal *= 1 - discount |
|
||||
if line.analytic_account_id.pricelist_id: |
|
||||
cur = line.analytic_account_id.pricelist_id.currency_id |
|
||||
line.price_subtotal = cur.round(subtotal) |
|
||||
else: |
|
||||
line.price_subtotal = subtotal |
|
||||
|
|
||||
def _compute_date_from(self): |
|
||||
# When call from template line.analytic_account_id comodel is |
|
||||
# 'account.analytic.contract', |
|
||||
if self._name != 'account.analytic.invoice.line': |
|
||||
return |
|
||||
for line in self: |
|
||||
contract = line.analytic_account_id |
|
||||
date_start = ( |
|
||||
self.env.context.get('old_date') or fields.Date.from_string( |
|
||||
contract.recurring_next_date or fields.Date.today()) |
|
||||
) |
|
||||
if contract.recurring_invoicing_type == 'pre-paid': |
|
||||
date_from = date_start |
|
||||
else: |
|
||||
date_from = (date_start - contract.get_relative_delta( |
|
||||
contract.recurring_rule_type, |
|
||||
contract.recurring_interval) + relativedelta(days=1)) |
|
||||
line.date_from = fields.Date.to_string(date_from) |
|
||||
|
|
||||
def _compute_date_to(self): |
|
||||
# When call from template line.analytic_account_id comodel is |
|
||||
# 'account.analytic.contract', |
|
||||
if self._name != 'account.analytic.invoice.line': |
|
||||
return |
|
||||
for line in self: |
|
||||
contract = line.analytic_account_id |
|
||||
date_start = ( |
|
||||
self.env.context.get('old_date') or fields.Date.from_string( |
|
||||
contract.recurring_next_date or fields.Date.today()) |
|
||||
) |
|
||||
next_date = ( |
|
||||
self.env.context.get('next_date') or |
|
||||
date_start + contract.get_relative_delta( |
|
||||
contract.recurring_rule_type, contract.recurring_interval) |
|
||||
) |
|
||||
if contract.recurring_invoicing_type == 'pre-paid': |
|
||||
date_to = next_date - relativedelta(days=1) |
|
||||
else: |
|
||||
date_to = date_start |
|
||||
line.date_to = fields.Date.to_string(date_to) |
|
||||
|
|
||||
@api.multi |
|
||||
@api.constrains('discount') |
|
||||
def _check_discount(self): |
|
||||
for line in self: |
|
||||
if line.discount > 100: |
|
||||
raise ValidationError( |
|
||||
_("Discount should be less or equal to 100")) |
|
||||
|
|
||||
@api.multi |
|
||||
@api.onchange('product_id') |
|
||||
def _onchange_product_id(self): |
|
||||
if not self.product_id: |
|
||||
return {'domain': {'uom_id': []}} |
|
||||
|
|
||||
vals = {} |
|
||||
domain = {'uom_id': [ |
|
||||
('category_id', '=', self.product_id.uom_id.category_id.id)]} |
|
||||
if not self.uom_id or (self.product_id.uom_id.category_id.id != |
|
||||
self.uom_id.category_id.id): |
|
||||
vals['uom_id'] = self.product_id.uom_id |
|
||||
|
|
||||
if self.analytic_account_id._name == 'account.analytic.account': |
|
||||
date = ( |
|
||||
self.analytic_account_id.recurring_next_date or |
|
||||
fields.Date.today() |
|
||||
) |
|
||||
partner = self.analytic_account_id.partner_id |
|
||||
|
|
||||
else: |
|
||||
date = fields.Date.today() |
|
||||
partner = self.env.user.partner_id |
|
||||
|
|
||||
product = self.product_id.with_context( |
|
||||
lang=partner.lang, |
|
||||
partner=partner.id, |
|
||||
quantity=self.quantity, |
|
||||
date=date, |
|
||||
pricelist=self.analytic_account_id.pricelist_id.id, |
|
||||
uom=self.uom_id.id |
|
||||
) |
|
||||
|
|
||||
name = product.name_get()[0][1] |
|
||||
if product.description_sale: |
|
||||
name += '\n' + product.description_sale |
|
||||
vals['name'] = name |
|
||||
|
|
||||
vals['price_unit'] = product.price |
|
||||
self.update(vals) |
|
||||
return {'domain': domain} |
|
@ -1,16 +0,0 @@ |
|||||
# Copyright 2017 LasLabs Inc. |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
|
|
||||
from odoo import fields, models |
|
||||
|
|
||||
|
|
||||
class AccountAnalyticInvoiceLine(models.Model): |
|
||||
_name = 'account.analytic.invoice.line' |
|
||||
_inherit = 'account.analytic.contract.line' |
|
||||
|
|
||||
analytic_account_id = fields.Many2one( |
|
||||
comodel_name='account.analytic.account', |
|
||||
string='Analytic Account', |
|
||||
required=True, |
|
||||
ondelete='cascade', |
|
||||
) |
|
@ -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( |
||||
|
'contract.line', string='Contract Line', index=True |
||||
|
) |
@ -0,0 +1,457 @@ |
|||||
|
# Copyright 2004-2010 OpenERP SA |
||||
|
# Copyright 2014 Angel Moya <angel.moya@domatix.com> |
||||
|
# Copyright 2015 Pedro M. Baeza <pedro.baeza@tecnativa.com> |
||||
|
# Copyright 2016-2018 Carlos Dauden <carlos.dauden@tecnativa.com> |
||||
|
# Copyright 2016-2017 LasLabs Inc. |
||||
|
# Copyright 2018 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import api, fields, models |
||||
|
from odoo.exceptions import ValidationError |
||||
|
from odoo.tools.translate import _ |
||||
|
|
||||
|
|
||||
|
class ContractContract(models.Model): |
||||
|
_name = 'contract.contract' |
||||
|
_description = "Contract" |
||||
|
_order = 'code, name asc' |
||||
|
_inherit = [ |
||||
|
'mail.thread', |
||||
|
'mail.activity.mixin', |
||||
|
'contract.abstract.contract', |
||||
|
] |
||||
|
|
||||
|
active = fields.Boolean( |
||||
|
default=True, |
||||
|
) |
||||
|
code = fields.Char( |
||||
|
string="Reference", |
||||
|
) |
||||
|
group_id = fields.Many2one( |
||||
|
string="Group", |
||||
|
comodel_name='account.analytic.account', |
||||
|
ondelete='restrict', |
||||
|
) |
||||
|
currency_id = fields.Many2one( |
||||
|
related="company_id.currency_id", |
||||
|
string="Currency", |
||||
|
readonly=True, |
||||
|
) |
||||
|
contract_template_id = fields.Many2one( |
||||
|
string='Contract Template', comodel_name='contract.template' |
||||
|
) |
||||
|
contract_line_ids = fields.One2many( |
||||
|
string='Contract lines', |
||||
|
comodel_name='contract.line', |
||||
|
inverse_name='contract_id', |
||||
|
copy=True, |
||||
|
) |
||||
|
|
||||
|
user_id = fields.Many2one( |
||||
|
comodel_name='res.users', |
||||
|
string='Responsible', |
||||
|
index=True, |
||||
|
default=lambda self: self.env.user, |
||||
|
) |
||||
|
create_invoice_visibility = fields.Boolean( |
||||
|
compute='_compute_create_invoice_visibility' |
||||
|
) |
||||
|
recurring_next_date = fields.Date( |
||||
|
compute='_compute_recurring_next_date', |
||||
|
string='Date of Next Invoice', |
||||
|
store=True, |
||||
|
) |
||||
|
date_end = fields.Date( |
||||
|
compute='_compute_date_end', string='Date End', store=True |
||||
|
) |
||||
|
payment_term_id = fields.Many2one( |
||||
|
comodel_name='account.payment.term', string='Payment Terms', index=True |
||||
|
) |
||||
|
invoice_count = fields.Integer(compute="_compute_invoice_count") |
||||
|
fiscal_position_id = fields.Many2one( |
||||
|
comodel_name='account.fiscal.position', |
||||
|
string='Fiscal Position', |
||||
|
ondelete='restrict', |
||||
|
) |
||||
|
invoice_partner_id = fields.Many2one( |
||||
|
string="Invoicing contact", |
||||
|
comodel_name='res.partner', |
||||
|
ondelete='restrict', |
||||
|
) |
||||
|
partner_id = fields.Many2one( |
||||
|
comodel_name='res.partner', |
||||
|
inverse='_inverse_partner_id', |
||||
|
required=True |
||||
|
) |
||||
|
|
||||
|
@api.multi |
||||
|
def _inverse_partner_id(self): |
||||
|
for rec in self: |
||||
|
if not rec.invoice_partner_id: |
||||
|
rec.invoice_partner_id = rec.partner_id.address_get( |
||||
|
['invoice'] |
||||
|
)['invoice'] |
||||
|
|
||||
|
@api.multi |
||||
|
def _get_related_invoices(self): |
||||
|
self.ensure_one() |
||||
|
|
||||
|
invoices = ( |
||||
|
self.env['account.invoice.line'] |
||||
|
.search( |
||||
|
[ |
||||
|
( |
||||
|
'contract_line_id', |
||||
|
'in', |
||||
|
self.contract_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() |
||||
|
tree_view_ref = ( |
||||
|
'account.invoice_supplier_tree' |
||||
|
if self.contract_type == 'purchase' |
||||
|
else 'account.invoice_tree_with_onboarding' |
||||
|
) |
||||
|
form_view_ref = ( |
||||
|
'account.invoice_supplier_form' |
||||
|
if self.contract_type == 'purchase' |
||||
|
else 'account.invoice_form' |
||||
|
) |
||||
|
tree_view = self.env.ref(tree_view_ref, raise_if_not_found=False) |
||||
|
form_view = self.env.ref(form_view_ref, raise_if_not_found=False) |
||||
|
action = { |
||||
|
'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)], |
||||
|
} |
||||
|
if tree_view and form_view: |
||||
|
action['views'] = [(tree_view.id, 'tree'), (form_view.id, 'form')] |
||||
|
return action |
||||
|
|
||||
|
@api.depends('contract_line_ids.date_end') |
||||
|
def _compute_date_end(self): |
||||
|
for contract in self: |
||||
|
contract.date_end = False |
||||
|
date_end = contract.contract_line_ids.mapped('date_end') |
||||
|
if date_end and all(date_end): |
||||
|
contract.date_end = max(date_end) |
||||
|
|
||||
|
@api.depends( |
||||
|
'contract_line_ids.recurring_next_date', |
||||
|
'contract_line_ids.is_canceled', |
||||
|
) |
||||
|
def _compute_recurring_next_date(self): |
||||
|
for contract in self: |
||||
|
recurring_next_date = contract.contract_line_ids.filtered( |
||||
|
lambda l: l.recurring_next_date and not l.is_canceled |
||||
|
).mapped('recurring_next_date') |
||||
|
if recurring_next_date: |
||||
|
contract.recurring_next_date = min(recurring_next_date) |
||||
|
|
||||
|
@api.depends('contract_line_ids.create_invoice_visibility') |
||||
|
def _compute_create_invoice_visibility(self): |
||||
|
for contract in self: |
||||
|
contract.create_invoice_visibility = any( |
||||
|
contract.contract_line_ids.mapped( |
||||
|
'create_invoice_visibility' |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
@api.onchange('contract_template_id') |
||||
|
def _onchange_contract_template_id(self): |
||||
|
"""Update the contract fields with that of the template. |
||||
|
|
||||
|
Take special consideration with the `contract_line_ids`, |
||||
|
which must be created using the data from the contract lines. Cascade |
||||
|
deletion ensures that any errant lines that are created are also |
||||
|
deleted. |
||||
|
""" |
||||
|
contract_template_id = self.contract_template_id |
||||
|
if not contract_template_id: |
||||
|
return |
||||
|
for field_name, field in contract_template_id._fields.items(): |
||||
|
if field.name == 'contract_line_ids': |
||||
|
lines = self._convert_contract_lines(contract_template_id) |
||||
|
self.contract_line_ids += lines |
||||
|
elif not any( |
||||
|
( |
||||
|
field.compute, |
||||
|
field.related, |
||||
|
field.automatic, |
||||
|
field.readonly, |
||||
|
field.company_dependent, |
||||
|
field.name in self.NO_SYNC, |
||||
|
) |
||||
|
): |
||||
|
self[field_name] = self.contract_template_id[field_name] |
||||
|
|
||||
|
@api.onchange('partner_id') |
||||
|
def _onchange_partner_id(self): |
||||
|
self.pricelist_id = self.partner_id.property_product_pricelist.id |
||||
|
self.fiscal_position_id = self.partner_id.property_account_position_id |
||||
|
if self.contract_type == 'purchase': |
||||
|
self.payment_term_id = \ |
||||
|
self.partner_id.property_supplier_payment_term_id |
||||
|
else: |
||||
|
self.payment_term_id = \ |
||||
|
self.partner_id.property_payment_term_id |
||||
|
self.invoice_partner_id = self.partner_id.address_get(['invoice'])[ |
||||
|
'invoice' |
||||
|
] |
||||
|
return { |
||||
|
'domain': { |
||||
|
'invoice_partner_id': [ |
||||
|
'|', |
||||
|
('id', 'parent_of', self.partner_id.id), |
||||
|
('id', 'child_of', self.partner_id.id), |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@api.multi |
||||
|
def _convert_contract_lines(self, contract): |
||||
|
self.ensure_one() |
||||
|
new_lines = self.env['contract.line'] |
||||
|
contract_line_model = self.env['contract.line'] |
||||
|
for contract_line in contract.contract_line_ids: |
||||
|
vals = contract_line._convert_to_write(contract_line.read()[0]) |
||||
|
# Remove template link field |
||||
|
vals.pop('contract_template_id', False) |
||||
|
vals['date_start'] = fields.Date.context_today(contract_line) |
||||
|
vals['recurring_next_date'] = fields.Date.context_today( |
||||
|
contract_line |
||||
|
) |
||||
|
new_lines += contract_line_model.new(vals) |
||||
|
new_lines._onchange_date_start() |
||||
|
new_lines._onchange_is_auto_renew() |
||||
|
return new_lines |
||||
|
|
||||
|
@api.multi |
||||
|
def _prepare_invoice(self, date_invoice, journal=None): |
||||
|
self.ensure_one() |
||||
|
if not journal: |
||||
|
journal = ( |
||||
|
self.journal_id |
||||
|
if self.journal_id.type == self.contract_type |
||||
|
else self.env['account.journal'].search( |
||||
|
[ |
||||
|
('type', '=', self.contract_type), |
||||
|
('company_id', '=', self.company_id.id), |
||||
|
], |
||||
|
limit=1, |
||||
|
) |
||||
|
) |
||||
|
if not journal: |
||||
|
raise ValidationError( |
||||
|
_("Please define a %s journal for the company '%s'.") |
||||
|
% (self.contract_type, self.company_id.name or '') |
||||
|
) |
||||
|
currency = ( |
||||
|
self.pricelist_id.currency_id |
||||
|
or self.partner_id.property_product_pricelist.currency_id |
||||
|
or self.company_id.currency_id |
||||
|
) |
||||
|
invoice_type = 'out_invoice' |
||||
|
if self.contract_type == 'purchase': |
||||
|
invoice_type = 'in_invoice' |
||||
|
return { |
||||
|
'name': self.code, |
||||
|
'type': invoice_type, |
||||
|
'partner_id': self.invoice_partner_id.id, |
||||
|
'currency_id': currency.id, |
||||
|
'date_invoice': date_invoice, |
||||
|
'journal_id': journal.id, |
||||
|
'origin': self.name, |
||||
|
'company_id': self.company_id.id, |
||||
|
'user_id': self.user_id.id, |
||||
|
'payment_term_id': self.payment_term_id.id, |
||||
|
'fiscal_position_id': self.fiscal_position_id.id, |
||||
|
} |
||||
|
|
||||
|
@api.multi |
||||
|
def action_contract_send(self): |
||||
|
self.ensure_one() |
||||
|
template = self.env.ref('contract.email_contract_template', False) |
||||
|
compose_form = self.env.ref('mail.email_compose_message_wizard_form') |
||||
|
ctx = dict( |
||||
|
default_model='contract.contract', |
||||
|
default_res_id=self.id, |
||||
|
default_use_template=bool(template), |
||||
|
default_template_id=template and template.id or False, |
||||
|
default_composition_mode='comment', |
||||
|
) |
||||
|
return { |
||||
|
'name': _('Compose Email'), |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'view_type': 'form', |
||||
|
'view_mode': 'form', |
||||
|
'res_model': 'mail.compose.message', |
||||
|
'views': [(compose_form.id, 'form')], |
||||
|
'view_id': compose_form.id, |
||||
|
'target': 'new', |
||||
|
'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: |
||||
|
payment_term = invoice.payment_term_id |
||||
|
fiscal_position = invoice.fiscal_position_id |
||||
|
invoice._onchange_partner_id() |
||||
|
invoice.payment_term_id = payment_term |
||||
|
invoice.fiscal_position_id = fiscal_position |
||||
|
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 (contract.contract) to invoice. |
||||
|
:param date_ref: optional reference date to use instead of today |
||||
|
:return: list (domain) usable on contract.contract |
||||
|
""" |
||||
|
domain = [] |
||||
|
if not date_ref: |
||||
|
date_ref = fields.Date.context_today(self) |
||||
|
domain.extend([('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 (contract.line recordset) |
||||
|
""" |
||||
|
self.ensure_one() |
||||
|
return self.contract_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 |
||||
|
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_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 |
||||
|
|
||||
|
@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 |
||||
|
def cron_recurring_create_invoice(self): |
||||
|
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) |
1107
contract/models/contract_line.py
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,428 @@ |
|||||
|
# Copyright 2018 ACSONE SA/NV. |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
import itertools |
||||
|
from collections import namedtuple |
||||
|
from odoo.fields import Date |
||||
|
|
||||
|
Criteria = namedtuple( |
||||
|
'Criteria', |
||||
|
[ |
||||
|
'when', # Contract line relatively to today (BEFORE, IN, AFTER) |
||||
|
'has_date_end', # Is date_end set on contract line (bool) |
||||
|
'has_last_date_invoiced', # Is last_date_invoiced set on contract line |
||||
|
'is_auto_renew', # Is is_auto_renew set on contract line (bool) |
||||
|
'has_successor', # Is contract line has_successor (bool) |
||||
|
'predecessor_has_successor', |
||||
|
# Is contract line predecessor has successor (bool) |
||||
|
# In almost of the cases |
||||
|
# contract_line.predecessor.successor == contract_line |
||||
|
# But at cancel action, |
||||
|
# contract_line.predecessor.successor == False |
||||
|
# This is to permit plan_successor on predecessor |
||||
|
# If contract_line.predecessor.successor != False |
||||
|
# and contract_line is canceled, we don't allow uncancel |
||||
|
# else we re-link contract_line and its predecessor |
||||
|
'canceled', # Is contract line canceled (bool) |
||||
|
], |
||||
|
) |
||||
|
Allowed = namedtuple( |
||||
|
'Allowed', |
||||
|
['plan_successor', 'stop_plan_successor', 'stop', 'cancel', 'uncancel'], |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def _expand_none(criteria): |
||||
|
variations = [] |
||||
|
for attribute, value in criteria._asdict().items(): |
||||
|
if value is None: |
||||
|
if attribute == 'when': |
||||
|
variations.append(['BEFORE', 'IN', 'AFTER']) |
||||
|
else: |
||||
|
variations.append([True, False]) |
||||
|
else: |
||||
|
variations.append([value]) |
||||
|
return itertools.product(*variations) |
||||
|
|
||||
|
|
||||
|
def _add(matrix, criteria, allowed): |
||||
|
""" Expand None values to True/False combination """ |
||||
|
for c in _expand_none(criteria): |
||||
|
matrix[c] = allowed |
||||
|
|
||||
|
|
||||
|
CRITERIA_ALLOWED_DICT = { |
||||
|
Criteria( |
||||
|
when='BEFORE', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=False, |
||||
|
is_auto_renew=True, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=True, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='BEFORE', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=False, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=True, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=False, |
||||
|
stop=True, |
||||
|
cancel=True, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='BEFORE', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=False, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=True, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=True, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='BEFORE', |
||||
|
has_date_end=False, |
||||
|
has_last_date_invoiced=False, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=True, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='IN', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=False, |
||||
|
is_auto_renew=True, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=True, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='IN', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=False, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=True, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=False, |
||||
|
stop=True, |
||||
|
cancel=True, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='IN', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=False, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=True, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=True, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='IN', |
||||
|
has_date_end=False, |
||||
|
has_last_date_invoiced=False, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=True, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='BEFORE', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=True, |
||||
|
is_auto_renew=True, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='BEFORE', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=True, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=True, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=False, |
||||
|
stop=True, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='BEFORE', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=True, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=True, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='BEFORE', |
||||
|
has_date_end=False, |
||||
|
has_last_date_invoiced=True, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='IN', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=True, |
||||
|
is_auto_renew=True, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='IN', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=True, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=True, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=False, |
||||
|
stop=True, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='IN', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=True, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=True, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='IN', |
||||
|
has_date_end=False, |
||||
|
has_last_date_invoiced=True, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=True, |
||||
|
stop=True, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='AFTER', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=None, |
||||
|
is_auto_renew=True, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=False, |
||||
|
stop=True, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='AFTER', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=None, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=True, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=False, |
||||
|
stop=False, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when='AFTER', |
||||
|
has_date_end=True, |
||||
|
has_last_date_invoiced=None, |
||||
|
is_auto_renew=False, |
||||
|
has_successor=False, |
||||
|
predecessor_has_successor=None, |
||||
|
canceled=False, |
||||
|
): Allowed( |
||||
|
plan_successor=True, |
||||
|
stop_plan_successor=False, |
||||
|
stop=True, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
Criteria( |
||||
|
when=None, |
||||
|
has_date_end=None, |
||||
|
has_last_date_invoiced=None, |
||||
|
is_auto_renew=None, |
||||
|
has_successor=None, |
||||
|
predecessor_has_successor=False, |
||||
|
canceled=True, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=False, |
||||
|
stop=False, |
||||
|
cancel=False, |
||||
|
uncancel=True, |
||||
|
), |
||||
|
Criteria( |
||||
|
when=None, |
||||
|
has_date_end=None, |
||||
|
has_last_date_invoiced=None, |
||||
|
is_auto_renew=None, |
||||
|
has_successor=None, |
||||
|
predecessor_has_successor=True, |
||||
|
canceled=True, |
||||
|
): Allowed( |
||||
|
plan_successor=False, |
||||
|
stop_plan_successor=False, |
||||
|
stop=False, |
||||
|
cancel=False, |
||||
|
uncancel=False, |
||||
|
), |
||||
|
} |
||||
|
criteria_allowed_dict = {} |
||||
|
|
||||
|
for c in CRITERIA_ALLOWED_DICT: |
||||
|
_add(criteria_allowed_dict, c, CRITERIA_ALLOWED_DICT[c]) |
||||
|
|
||||
|
|
||||
|
def compute_when(date_start, date_end): |
||||
|
today = Date.today() |
||||
|
if today < date_start: |
||||
|
return 'BEFORE' |
||||
|
if date_end and today > date_end: |
||||
|
return 'AFTER' |
||||
|
return 'IN' |
||||
|
|
||||
|
|
||||
|
def compute_criteria( |
||||
|
date_start, |
||||
|
date_end, |
||||
|
has_last_date_invoiced, |
||||
|
is_auto_renew, |
||||
|
successor_contract_line_id, |
||||
|
predecessor_contract_line_id, |
||||
|
is_canceled, |
||||
|
): |
||||
|
return Criteria( |
||||
|
when=compute_when(date_start, date_end), |
||||
|
has_date_end=bool(date_end), |
||||
|
has_last_date_invoiced=bool(has_last_date_invoiced), |
||||
|
is_auto_renew=is_auto_renew, |
||||
|
has_successor=bool(successor_contract_line_id), |
||||
|
predecessor_has_successor=bool( |
||||
|
predecessor_contract_line_id.successor_contract_line_id |
||||
|
), |
||||
|
canceled=is_canceled, |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def get_allowed( |
||||
|
date_start, |
||||
|
date_end, |
||||
|
has_last_date_invoiced, |
||||
|
is_auto_renew, |
||||
|
successor_contract_line_id, |
||||
|
predecessor_contract_line_id, |
||||
|
is_canceled, |
||||
|
): |
||||
|
criteria = compute_criteria( |
||||
|
date_start, |
||||
|
date_end, |
||||
|
has_last_date_invoiced, |
||||
|
is_auto_renew, |
||||
|
successor_contract_line_id, |
||||
|
predecessor_contract_line_id, |
||||
|
is_canceled, |
||||
|
) |
||||
|
if criteria in criteria_allowed_dict: |
||||
|
return criteria_allowed_dict[criteria] |
||||
|
return False |
@ -0,0 +1,22 @@ |
|||||
|
# Copyright 2004-2010 OpenERP SA |
||||
|
# Copyright 2014 Angel Moya <angel.moya@domatix.com> |
||||
|
# Copyright 2015 Pedro M. Baeza <pedro.baeza@tecnativa.com> |
||||
|
# Copyright 2016-2018 Carlos Dauden <carlos.dauden@tecnativa.com> |
||||
|
# Copyright 2016-2017 LasLabs Inc. |
||||
|
# Copyright 2018 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class ContractTemplate(models.Model): |
||||
|
_name = 'contract.template' |
||||
|
_inherit = 'contract.abstract.contract' |
||||
|
_description = "Contract Template" |
||||
|
|
||||
|
contract_line_ids = fields.One2many( |
||||
|
comodel_name='contract.template.line', |
||||
|
inverse_name='contract_id', |
||||
|
copy=True, |
||||
|
string='Contract template lines', |
||||
|
) |
@ -0,0 +1,24 @@ |
|||||
|
# Copyright 2004-2010 OpenERP SA |
||||
|
# Copyright 2014 Angel Moya <angel.moya@domatix.com> |
||||
|
# Copyright 2015 Pedro M. Baeza <pedro.baeza@tecnativa.com> |
||||
|
# Copyright 2016-2018 Carlos Dauden <carlos.dauden@tecnativa.com> |
||||
|
# Copyright 2016-2017 LasLabs Inc. |
||||
|
# Copyright 2018 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class ContractTemplateLine(models.Model): |
||||
|
_name = 'contract.template.line' |
||||
|
_inherit = 'contract.abstract.contract.line' |
||||
|
_description = "Contract Template Line" |
||||
|
_order = "sequence,id" |
||||
|
|
||||
|
contract_id = fields.Many2one( |
||||
|
string='Contract', |
||||
|
comodel_name='contract.template', |
||||
|
required=True, |
||||
|
ondelete='cascade', |
||||
|
oldname='analytic_account_id', |
||||
|
) |
@ -1,27 +1,25 @@ |
|||||
To use this module, you need to: |
|
||||
|
#. Contracts are in Invoicing -> Customers -> Customer and Invoicing -> Vendors -> Supplier Contracts |
||||
|
#. When creating a contract, fill fields for selecting the invoicing parameters: |
||||
|
|
||||
#. Go to Accounting -> Contracts and select or create a new contract. |
|
||||
#. Check *Generate recurring invoices automatically*. |
|
||||
#. Fill fields for selecting the recurrency and invoice parameters: |
|
||||
|
* a journal |
||||
|
* a price list (optional) |
||||
|
|
||||
* Journal |
|
||||
* Pricelist |
|
||||
* Period. It can be any interval of days, weeks, months, months last day or |
|
||||
years. |
|
||||
* Start date and next invoice date. |
|
||||
* Invoicing type: pre-paid or post-paid. |
|
||||
|
#. And add the lines to be invoiced with: |
||||
|
|
||||
#. Add the lines to be invoiced with the product, description, quantity and |
|
||||
price. |
|
||||
#. You can mark Auto-price? for having a price automatically obtained applying |
|
||||
the pricelist to the product price. |
|
||||
#. You have the possibility to use the markers #START# or #END# in the |
|
||||
description field to show the start and end date of the invoiced period. |
|
||||
#. Choosing between pre-paid and post-paid, you modify the dates that are shown |
|
||||
with the markers. |
|
||||
#. A cron is created with daily interval, but if you are in debug mode, you can |
|
||||
click on *Create invoices* to force this action. |
|
||||
#. Click *Show recurring invoices* link to show all invoices created by the |
|
||||
|
* the product with a description, a quantity and a price |
||||
|
* the recurrence parameters: interval (days, weeks, months, months last day or years), |
||||
|
start date, date of next invoice (automatically computed, can be modified) and end date (optional) |
||||
|
* auto-price, for having a price automatically obtained from the price list |
||||
|
* #START# or #END# in the description field to display the start/end date of |
||||
|
the invoiced period in the invoice line description |
||||
|
* pre-paid (invoice at period start) or post-paid (invoice at start of next period) |
||||
|
|
||||
|
#. The "Generate Recurring Invoices from Contracts" cron runs daily to generate the invoices. |
||||
|
If you are in debug mode, you can click on the invoice creation button. |
||||
|
#. The *Show recurring invoices* shortcut on contracts shows all invoices created from the |
||||
contract. |
contract. |
||||
#. Click on *Print > Contract* menu to print contract report. |
|
||||
#. Click on *Send by Email* button to send contract by email. |
|
||||
|
#. The contract report can be printed from the Print menu |
||||
|
#. The contract can be sent by email with the *Send by Email* button |
||||
|
#. Contract templates can be created from the Configuration -> Contracts -> Contract Templates menu. |
||||
|
They allow to define default journal, price list and lines when creating a contract. |
||||
|
To use it, just select the template on the contract and fields will be filled automatically. |
@ -1,7 +1,9 @@ |
|||||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
||||
"account_analytic_contract_manager","Recurring manager","model_account_analytic_contract","account.group_account_manager",1,1,1,1 |
|
||||
"account_analytic_contract_user","Recurring user","model_account_analytic_contract","account.group_account_invoice",1,0,0,0 |
|
||||
"account_analytic_invoice_line_manager","Recurring manager","model_account_analytic_invoice_line","account.group_account_manager",1,1,1,1 |
|
||||
"account_analytic_invoice_line_user","Recurring user","model_account_analytic_invoice_line","account.group_account_invoice",1,0,0,0 |
|
||||
"account_analytic_contract_line_manager","Recurring manager","model_account_analytic_contract_line","account.group_account_manager",1,1,1,1 |
|
||||
"account_analytic_contract_line_user","Recurring user","model_account_analytic_contract_line","account.group_account_invoice",1,0,0,0 |
|
||||
|
"contract_template_manager","Recurring manager","model_contract_template","account.group_account_manager",1,1,1,1 |
||||
|
"contract_template_user","Recurring user","model_contract_template","account.group_account_invoice",1,0,0,0 |
||||
|
"contract_manager","Recurring manager","model_contract_contract","account.group_account_manager",1,1,1,1 |
||||
|
"contract_user","Recurring user","model_contract_contract","account.group_account_invoice",1,0,0,0 |
||||
|
"contract_line_manager","Recurring manager","model_contract_line","account.group_account_manager",1,1,1,1 |
||||
|
"contract_line_user","Recurring user","model_contract_line","account.group_account_invoice",1,0,0,0 |
||||
|
"contract_template_line_manager","Recurring manager","model_contract_template_line","account.group_account_manager",1,1,1,1 |
||||
|
"contract_template_line_user","Recurring user","model_contract_template_line","account.group_account_invoice",1,0,0,0 |
1823
contract/tests/test_contract.py
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,71 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
|
||||
|
<!--FORM view--> |
||||
|
<record id="contract_abstract_contract_line_form_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.abstract.contract.line form view (in contract)</field> |
||||
|
<field name="model">contract.abstract.contract.line</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<header/> |
||||
|
<sheet> |
||||
|
<group col="4"> |
||||
|
<field colspan="4" name="product_id"/> |
||||
|
<field colspan="4" name="name"/> |
||||
|
<field colspan="2" name="quantity"/> |
||||
|
<field colspan="2" name="uom_id"/> |
||||
|
<field colspan="2" name="automatic_price"/> |
||||
|
<field name="specific_price" invisible="1"/> |
||||
|
<field colspan="2" name="price_unit" |
||||
|
attrs="{'readonly': [('automatic_price', '=', True)]}"/> |
||||
|
<field colspan="2" name="discount" groups="base.group_no_one"/> |
||||
|
</group> |
||||
|
<group col="4"> |
||||
|
<field colspan="2" name="is_auto_renew"/> |
||||
|
<field colspan="2" name="is_canceled" invisible="1"/> |
||||
|
</group> |
||||
|
<group attrs="{'invisible':[('is_auto_renew', '=', False)]}"> |
||||
|
<group> |
||||
|
<label for="auto_renew_interval"/> |
||||
|
<div> |
||||
|
<field name="auto_renew_interval" |
||||
|
class="oe_inline" nolabel="1" |
||||
|
attrs="{'required':[('is_auto_renew', '=', True)]}"/> |
||||
|
<field name="auto_renew_rule_type" |
||||
|
class="oe_inline" nolabel="1" |
||||
|
attrs="{'required':[('is_auto_renew', '=', True)]}"/> |
||||
|
</div> |
||||
|
</group> |
||||
|
<group> |
||||
|
<label for="termination_notice_interval"/> |
||||
|
<div> |
||||
|
<field name="termination_notice_interval" |
||||
|
class="oe_inline" nolabel="1" |
||||
|
attrs="{'required':[('is_auto_renew', '=', True)]}"/> |
||||
|
<field name="termination_notice_rule_type" |
||||
|
class="oe_inline" nolabel="1" |
||||
|
attrs="{'required':[('is_auto_renew', '=', True)]}"/> |
||||
|
</div> |
||||
|
</group> |
||||
|
</group> |
||||
|
<group name="recurrence_info"> |
||||
|
<group> |
||||
|
<label for="recurring_interval"/> |
||||
|
<div> |
||||
|
<field name="recurring_interval" |
||||
|
class="oe_inline" nolabel="1"/> |
||||
|
<field name="recurring_rule_type" |
||||
|
class="oe_inline" nolabel="1"/> |
||||
|
</div> |
||||
|
</group> |
||||
|
<group> |
||||
|
<field name="recurring_invoicing_type" |
||||
|
attrs="{'invisible': [('recurring_rule_type', '=', 'monthlylastday')]}"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
</sheet> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -1,279 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<odoo> |
|
||||
|
|
||||
<record id="account_analytic_account_recurring_form_form" model="ir.ui.view"> |
|
||||
<field name="name">Contract form</field> |
|
||||
<field name="model">account.analytic.account</field> |
|
||||
<field name="inherit_id" ref="analytic.view_account_analytic_account_form"/> |
|
||||
<field name="mode">primary</field> |
|
||||
<field name="priority" eval="9999"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<field name="partner_id" position="attributes"> |
|
||||
<attribute name="attrs">{'required': [('recurring_invoices', '=', True)]}</attribute> |
|
||||
</field> |
|
||||
<xpath expr="//div[@name='button_box']/.." position="before"> |
|
||||
<header> |
|
||||
<button name="action_contract_send" type="object" string="Send by Email" groups="base.group_user"/> |
|
||||
</header> |
|
||||
</xpath> |
|
||||
<xpath expr='//field[@name="code"]' position='before'> |
|
||||
<field name="contract_type" invisible="1" required="1"/> |
|
||||
</xpath> |
|
||||
<group name="main" position="after"> |
|
||||
<separator string="Recurring Invoices" |
|
||||
attrs="{'invisible': [('recurring_invoices','!=',True)]}" |
|
||||
/> |
|
||||
<div> |
|
||||
<field name="recurring_invoices" class="oe_inline"/> |
|
||||
<field name="create_invoice_visibility" invisible="1"/> |
|
||||
<label for="recurring_invoices" /> |
|
||||
<button name="recurring_create_invoice" |
|
||||
type="object" |
|
||||
attrs="{'invisible': ['|', ('recurring_invoices', '!=', True), ('create_invoice_visibility', '=', False)]}" |
|
||||
string="Create invoices" |
|
||||
class="oe_link" |
|
||||
groups="base.group_no_one" |
|
||||
/> |
|
||||
<button name="button_show_recurring_invoices" |
|
||||
type="object" |
|
||||
attrs="{'invisible': [('recurring_invoices','!=',True)]}" |
|
||||
string="⇒ Show recurring invoices" |
|
||||
class="oe_link" |
|
||||
/> |
|
||||
</div> |
|
||||
<group col="4" attrs="{'invisible': [('recurring_invoices','!=',True)]}"> |
|
||||
<field name="contract_template_id" colspan="4" domain="['|', ('contract_type', '=', contract_type), ('contract_type', '=', False)]" context="{'default_contract_type': contract_type}"/> |
|
||||
<field name="journal_id" |
|
||||
domain="[('type', '=', contract_type),('company_id', '=', company_id)]" |
|
||||
attrs="{'required': [('recurring_invoices', '=', True)]}" |
|
||||
/> |
|
||||
<field name="pricelist_id"/> |
|
||||
<field name="company_id" groups="base.group_multi_company"/> |
|
||||
<label for="recurring_interval"/> |
|
||||
<div> |
|
||||
<field name="recurring_interval" |
|
||||
class="oe_inline" |
|
||||
attrs="{'required': [('recurring_invoices', '=', True)]}" |
|
||||
/> |
|
||||
<field name="recurring_rule_type" |
|
||||
class="oe_inline" |
|
||||
attrs="{'required': [('recurring_invoices', '=', True)]}" |
|
||||
/> |
|
||||
</div> |
|
||||
<field name="recurring_invoicing_type" |
|
||||
attrs="{'required': [('recurring_invoices', '=', True)]}" |
|
||||
/> |
|
||||
<field name="date_start" |
|
||||
attrs="{'required': [('recurring_invoices', '=', True)]}" |
|
||||
/> |
|
||||
<field name="date_end"/> |
|
||||
<field name="recurring_next_date" |
|
||||
attrs="{'required': [('recurring_invoices', '=', True)]}" |
|
||||
/> |
|
||||
</group> |
|
||||
<label for="recurring_invoice_line_ids" |
|
||||
attrs="{'invisible': [('recurring_invoices','=',False)]}" |
|
||||
/> |
|
||||
<div attrs="{'invisible': [('recurring_invoices','=',False)]}"> |
|
||||
<field name="recurring_invoice_line_ids"> |
|
||||
<tree string="Account Analytic Lines" editable="bottom"> |
|
||||
<field name="sequence" widget="handle" /> |
|
||||
<field name="product_id"/> |
|
||||
<field name="name"/> |
|
||||
<field name="quantity"/> |
|
||||
<field name="uom_id"/> |
|
||||
<field name="automatic_price"/> |
|
||||
<field name="price_unit" attrs="{'readonly': [('automatic_price', '=', True)]}"/> |
|
||||
<field name="specific_price" invisible="1"/> |
|
||||
<field name="discount" groups="base.group_no_one" /> |
|
||||
<field name="price_subtotal"/> |
|
||||
</tree> |
|
||||
</field> |
|
||||
</div> |
|
||||
<group string="Legend (for the markers inside invoice lines description)" |
|
||||
name="group_legend" attrs="{'invisible': [('recurring_invoices','!=',True)]}"> |
|
||||
<p colspan="2"> <strong>#START#</strong>: Start date of the invoiced period</p> |
|
||||
<p colspan="2"> <strong>#END#</strong>: End date of the invoiced period</p> |
|
||||
</group> |
|
||||
</group> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<record id="account_analytic_account_sale_form" model="ir.ui.view"> |
|
||||
<field name="name">account.analytic.account.sale.form</field> |
|
||||
<field name="model">account.analytic.account</field> |
|
||||
<field name="inherit_id" ref="account_analytic_account_recurring_form_form"/> |
|
||||
<field name="mode">primary</field> |
|
||||
<field name="priority" eval="20"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<field name="partner_id" position="attributes"> |
|
||||
<attribute name="string">Customer</attribute> |
|
||||
<attribute name="domain">[('customer', '=', True)]</attribute> |
|
||||
<attribute name="context">{'default_customer': True, 'default_supplier': False}</attribute> |
|
||||
</field> |
|
||||
<field name="journal_id" position="attributes"> |
|
||||
<attribute name="domain">[('type', '=', 'sale'),('company_id', '=', company_id)]</attribute> |
|
||||
</field> |
|
||||
<field name="product_id" position="attributes"> |
|
||||
<attribute name="domain">[('sale_ok', '=', True)]</attribute> |
|
||||
</field> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<record id="account_analytic_account_purchase_form" model="ir.ui.view"> |
|
||||
<field name="name">account.analytic.account.purchase.form</field> |
|
||||
<field name="model">account.analytic.account</field> |
|
||||
<field name="inherit_id" ref="account_analytic_account_recurring_form_form"/> |
|
||||
<field name="mode">primary</field> |
|
||||
<field name="priority" eval="20"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<field name="partner_id" position="attributes"> |
|
||||
<attribute name="string">Supplier</attribute> |
|
||||
<attribute name="domain">[('supplier', '=', True)]</attribute> |
|
||||
<attribute name="context">{'default_customer': False, 'default_supplier': True}</attribute> |
|
||||
</field> |
|
||||
<field name="journal_id" position="attributes"> |
|
||||
<attribute name="domain">[('type', '=', 'purchase'),('company_id', '=', company_id)]</attribute> |
|
||||
</field> |
|
||||
<field name="product_id" position="attributes"> |
|
||||
<attribute name="domain">[('purchase_ok', '=', True)]</attribute> |
|
||||
</field> |
|
||||
<xpath expr="//field[@name='recurring_invoice_line_ids']/tree/field[@name='automatic_price']" position="attributes"> |
|
||||
<attribute name="invisible">True</attribute> |
|
||||
</xpath> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<!-- Inherited Analytic Account list for contracts --> |
|
||||
<record id="view_account_analytic_account_journal_tree" model="ir.ui.view"> |
|
||||
<field name="name">Contract list</field> |
|
||||
<field name="model">account.analytic.account</field> |
|
||||
<field name="inherit_id" ref="analytic.view_account_analytic_account_list" /> |
|
||||
<field name="mode">primary</field> |
|
||||
<field name="priority" eval="9999"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<field name="partner_id" position="before"> |
|
||||
<field name="journal_id" groups="account.group_account_user"/> |
|
||||
</field> |
|
||||
<field name="partner_id" position="after"> |
|
||||
<field name="recurring_next_date" invisible="not context.get('is_contract', False)"/> |
|
||||
</field> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<!-- Analytic Account search view for contract --> |
|
||||
<record id="view_account_analytic_account_contract_search" model="ir.ui.view"> |
|
||||
<field name="name">Contract search</field> |
|
||||
<field name="model">account.analytic.account</field> |
|
||||
<field name="inherit_id" ref="analytic.view_account_analytic_account_search"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<field name="partner_id" position="after"> |
|
||||
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]" |
|
||||
string="Partner and dependents"/> |
|
||||
</field> |
|
||||
<field name="name" position="after"> |
|
||||
<field name="journal_id"/> |
|
||||
<field name="pricelist_id"/> |
|
||||
<separator/> |
|
||||
<filter name="recurring_invoices" |
|
||||
string="Recurring Invoices" |
|
||||
domain="[('recurring_invoices','=',True)]" |
|
||||
/> |
|
||||
<separator/> |
|
||||
<filter name="not_finished" |
|
||||
string="Valid" |
|
||||
domain="['|', ('date_end', '=', False), ('date_end', '>=', time.strftime('%Y-%m-%d'))]" |
|
||||
/> |
|
||||
<filter name="finished" |
|
||||
string="Finished" |
|
||||
domain="[('date_end', '<', time.strftime('%Y-%m-%d'))]" |
|
||||
/> |
|
||||
<group expand="0" string="Group By..."> |
|
||||
<filter name="next_invoice" |
|
||||
string="Next Invoice" |
|
||||
domain="[]" |
|
||||
context="{'group_by':'recurring_next_date'}" |
|
||||
/> |
|
||||
<filter name="date_end" |
|
||||
string="Date End" |
|
||||
domain="[]" |
|
||||
context="{'group_by':'date_end'}" |
|
||||
/> |
|
||||
</group> |
|
||||
</field> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<!-- Action Sales/Sales/Contracts --> |
|
||||
<record id="action_account_analytic_sale_overdue_all" model="ir.actions.act_window"> |
|
||||
<field name="name">Customer Contracts</field> |
|
||||
<field name="res_model">account.analytic.account</field> |
|
||||
<field name="view_type">form</field> |
|
||||
<field name="view_mode">tree,form</field> |
|
||||
<field name="domain">[('contract_type', '=', 'sale')]</field> |
|
||||
<field name="context">{'is_contract':1, 'search_default_not_finished':1, 'search_default_recurring_invoices':1, 'default_recurring_invoices': 1, 'default_contract_type': 'sale'}</field> |
|
||||
<field name="search_view_id" ref="view_account_analytic_account_contract_search"/> |
|
||||
<field name="help" type="html"> |
|
||||
<p class="oe_view_nocontent_create"> |
|
||||
Click to create a new contract. |
|
||||
</p> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<record id="action_account_analytic_sale_overdue_all_tree" model="ir.actions.act_window.view"> |
|
||||
<field name="sequence" eval="1"/> |
|
||||
<field name="view_mode">tree</field> |
|
||||
<field name="view_id" ref="view_account_analytic_account_journal_tree"/> |
|
||||
<field name="act_window_id" ref="action_account_analytic_sale_overdue_all"/> |
|
||||
</record> |
|
||||
|
|
||||
<record id="action_account_analytic_sale_overdue_all_form" model="ir.actions.act_window.view"> |
|
||||
<field name="sequence" eval="2"/> |
|
||||
<field name="view_mode">form</field> |
|
||||
<field name="view_id" ref="account_analytic_account_sale_form"/> |
|
||||
<field name="act_window_id" ref="action_account_analytic_sale_overdue_all"/> |
|
||||
</record> |
|
||||
|
|
||||
<menuitem id="menu_action_account_analytic_sale_overdue_all" |
|
||||
parent="account.menu_finance_receivables" |
|
||||
action="action_account_analytic_sale_overdue_all" |
|
||||
sequence="99" |
|
||||
/> |
|
||||
|
|
||||
<!-- Action Purchases/Purchases/Contracts --> |
|
||||
<record id="action_account_analytic_purchase_overdue_all" model="ir.actions.act_window"> |
|
||||
<field name="name">Supplier Contracts</field> |
|
||||
<field name="res_model">account.analytic.account</field> |
|
||||
<field name="view_type">form</field> |
|
||||
<field name="view_mode">tree,form</field> |
|
||||
<field name="domain">[('contract_type', '=', 'purchase')]</field> |
|
||||
<field name="context">{'is_contract':1, 'search_default_not_finished':1, 'search_default_recurring_invoices':1, 'default_recurring_invoices': 1, 'default_contract_type': 'purchase'}</field> |
|
||||
<field name="search_view_id" ref="view_account_analytic_account_contract_search"/> |
|
||||
<field name="help" type="html"> |
|
||||
<p class="oe_view_nocontent_create"> |
|
||||
Click to create a new contract. |
|
||||
</p> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<record id="action_account_analytic_purchase_overdue_all_tree" model="ir.actions.act_window.view"> |
|
||||
<field name="sequence" eval="1"/> |
|
||||
<field name="view_mode">tree</field> |
|
||||
<field name="view_id" ref="view_account_analytic_account_journal_tree"/> |
|
||||
<field name="act_window_id" ref="action_account_analytic_purchase_overdue_all"/> |
|
||||
</record> |
|
||||
|
|
||||
<record id="action_account_analytic_purchase_overdue_all_form" model="ir.actions.act_window.view"> |
|
||||
<field name="sequence" eval="2"/> |
|
||||
<field name="view_mode">form</field> |
|
||||
<field name="view_id" ref="account_analytic_account_purchase_form"/> |
|
||||
<field name="act_window_id" ref="action_account_analytic_purchase_overdue_all"/> |
|
||||
</record> |
|
||||
|
|
||||
<menuitem id="menu_action_account_analytic_purchase_overdue_all" |
|
||||
parent="account.menu_finance_payables" |
|
||||
action="action_account_analytic_purchase_overdue_all" |
|
||||
sequence="99" |
|
||||
/> |
|
||||
|
|
||||
</odoo> |
|
@ -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> |
|
@ -0,0 +1,293 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
|
||||
|
<!--Main FORM view--> |
||||
|
<record id="contract_contract_form_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.contract form view (in contract)</field> |
||||
|
<field name="model">contract.contract</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<header> |
||||
|
<button name="action_contract_send" |
||||
|
type="object" |
||||
|
string="Send by Email" |
||||
|
groups="base.group_user"/> |
||||
|
<button name="recurring_create_invoice" |
||||
|
type="object" |
||||
|
attrs="{'invisible': [('create_invoice_visibility', '=', False)]}" |
||||
|
string="Create invoices" |
||||
|
groups="base.group_no_one"/> |
||||
|
</header> |
||||
|
<sheet string="Contract"> |
||||
|
<div class="oe_button_box" name="button_box"> |
||||
|
<button class="oe_stat_button" type="object" |
||||
|
name="toggle_active" icon="fa-archive"> |
||||
|
<field name="active" widget="boolean_button" |
||||
|
options="{"terminology": "archive"}"/> |
||||
|
</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"/> |
||||
|
<h3> |
||||
|
<field name="name" class="oe_inline" |
||||
|
placeholder="e.g. Contract XYZ"/> |
||||
|
</h3> |
||||
|
</div> |
||||
|
<group name="main"> |
||||
|
<group> |
||||
|
<field name="partner_id" required="1"/> |
||||
|
<field name="payment_term_id"/> |
||||
|
<field name="user_id"/> |
||||
|
</group> |
||||
|
<group> |
||||
|
<field name="contract_template_id" |
||||
|
domain="['|', ('contract_type', '=', contract_type), ('contract_type', '=', False)]" |
||||
|
context="{'default_contract_type': contract_type}"/> |
||||
|
<field name="contract_type" invisible="1" |
||||
|
required="1"/> |
||||
|
<field name="fiscal_position_id"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
|
||||
|
<group name="recurring_invoices"> |
||||
|
<group> |
||||
|
<field name="journal_id" required="1"/> |
||||
|
<field name="recurring_next_date"/> |
||||
|
</group> |
||||
|
<group> |
||||
|
<field name="pricelist_id"/> |
||||
|
<field name="date_end"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
<notebook> |
||||
|
<page name="recurring_invoice_line" |
||||
|
string="Recurring Invoices"> |
||||
|
<field name="contract_line_ids" |
||||
|
context="{'default_contract_type': contract_type}"/> |
||||
|
</page> |
||||
|
<page name="info" string="Other Information"> |
||||
|
<field name="create_invoice_visibility" |
||||
|
invisible="1"/> |
||||
|
<group> |
||||
|
<field name="code"/> |
||||
|
<field name="group_id"/> |
||||
|
<field name="company_id" |
||||
|
options="{'no_create': True}" |
||||
|
groups="base.group_multi_company"/> |
||||
|
<field name="currency_id" |
||||
|
options="{'no_create': True}" |
||||
|
groups="base.group_multi_currency"/> |
||||
|
<field name="invoice_partner_id" |
||||
|
required="1"/> |
||||
|
</group> |
||||
|
<group string="Legend (for the markers inside invoice lines description)" |
||||
|
name="group_legend"> |
||||
|
<p colspan="2"><strong>#START#</strong>: Start |
||||
|
date |
||||
|
of the |
||||
|
invoiced period |
||||
|
</p> |
||||
|
<p colspan="2"><strong>#END#</strong>: End date |
||||
|
of |
||||
|
the |
||||
|
invoiced period |
||||
|
</p> |
||||
|
</group> |
||||
|
</page> |
||||
|
</notebook> |
||||
|
</sheet> |
||||
|
<div class="oe_chatter"> |
||||
|
<field name="message_follower_ids" widget="mail_followers"/> |
||||
|
<field name="activity_ids" widget="mail_activity"/> |
||||
|
<field name="message_ids" widget="mail_thread"/> |
||||
|
</div> |
||||
|
</form> |
||||
|
|
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!--Customer FORM view--> |
||||
|
<record id="contract_contract_customer_form_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.contract customer form view (in contract)</field> |
||||
|
<field name="model">contract.contract</field> |
||||
|
<field name="inherit_id" ref="contract_contract_form_view"/> |
||||
|
<field name="mode">primary</field> |
||||
|
<field name="priority" eval="20"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="partner_id" position="attributes"> |
||||
|
<attribute name="string">Customer</attribute> |
||||
|
<attribute name="domain">[('customer', '=', True)]</attribute> |
||||
|
<attribute name="context">{'default_customer': True, 'default_supplier': False}</attribute> |
||||
|
</field> |
||||
|
<field name="journal_id" position="attributes"> |
||||
|
<attribute name="domain">[('type', '=', 'sale'),('company_id', '=', company_id)]</attribute> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!--Supplier FORM view--> |
||||
|
<record id="contract_contract_supplier_form_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.contract supplier form view (in contract)</field> |
||||
|
<field name="model">contract.contract</field> |
||||
|
<field name="inherit_id" ref="contract_contract_form_view"/> |
||||
|
<field name="mode">primary</field> |
||||
|
<field name="priority" eval="20"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="partner_id" position="attributes"> |
||||
|
<attribute name="string">Supplier</attribute> |
||||
|
<attribute name="domain">[('supplier', '=', True)]</attribute> |
||||
|
<attribute name="context">{'default_customer': False, 'default_supplier': True}</attribute> |
||||
|
</field> |
||||
|
<field name="journal_id" position="attributes"> |
||||
|
<attribute name="domain">[('type', '=', 'purchase'),('company_id', '=', company_id)]</attribute> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!--TREE view--> |
||||
|
<record id="contract_contract_tree_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.contract tree view (in contract)</field> |
||||
|
<field name="model">contract.contract</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree> |
||||
|
<field name="name" string="Name"/> |
||||
|
<field name="code"/> |
||||
|
<field name="journal_id" groups="account.group_account_user"/> |
||||
|
<field name="partner_id"/> |
||||
|
<field name="active" invisible="1"/> |
||||
|
<field name="company_id" groups="base.group_multi_company"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!--SEARCH view--> |
||||
|
<record id="contract_contract_search_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.contract search view (in contract)</field> |
||||
|
<field name="model">contract.contract</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<search> |
||||
|
<field name="name" |
||||
|
filter_domain="['|', ('name','ilike',self), ('code','ilike',self)]"/> |
||||
|
<field name="journal_id"/> |
||||
|
<field name="pricelist_id"/> |
||||
|
<separator/> |
||||
|
<separator/> |
||||
|
<filter name="not_finished" |
||||
|
string="In progress" |
||||
|
domain="['|', ('date_end', '>=', context_today().strftime('%Y-%m-%d')), ('date_end', '=', False)]" |
||||
|
/> |
||||
|
<filter name="finished" |
||||
|
string="Finished" |
||||
|
domain="[('date_end', '<', context_today().strftime('%Y-%m-%d')), ('recurring_next_date', '=', False)]" |
||||
|
/> |
||||
|
<field name="partner_id"/> |
||||
|
<filter string="Archived" |
||||
|
domain="[('active', '=', False)]" |
||||
|
name="inactive"/> |
||||
|
<group expand="0" string="Group By..."> |
||||
|
<filter string="Associated Partner" |
||||
|
name="group_by_partner" |
||||
|
domain="[]" |
||||
|
context="{'group_by':'partner_id'}"/> |
||||
|
<filter name="group_by_next_invoice" |
||||
|
string="Next Invoice" |
||||
|
domain="[('recurring_next_date', '!=', False)]" |
||||
|
context="{'group_by':'recurring_next_date'}" |
||||
|
/> |
||||
|
<filter name="group_by_date_end" |
||||
|
string="Date End" |
||||
|
domain="[]" |
||||
|
context="{'group_by':'date_end'}" |
||||
|
/> |
||||
|
</group> |
||||
|
</search> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!--ACTION customer contracts--> |
||||
|
<record id="action_customer_contract" model="ir.actions.act_window"> |
||||
|
<field name="name">Customer Contracts</field> |
||||
|
<field name="res_model">contract.contract</field> |
||||
|
<field name="view_type">form</field> |
||||
|
<field name="view_mode">tree,form</field> |
||||
|
<field name="domain">[('contract_type', '=', 'sale')]</field> |
||||
|
<field name="context">{'is_contract':1, |
||||
|
'search_default_not_finished':1, |
||||
|
'default_contract_type': 'sale'} |
||||
|
</field> |
||||
|
<field name="search_view_id" ref="contract_contract_search_view"/> |
||||
|
<field name="help" type="html"> |
||||
|
<p class="oe_view_nocontent_create"> |
||||
|
Click to create a new contract. |
||||
|
</p> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="action_customer_contract_view_tree" model="ir.actions.act_window.view"> |
||||
|
<field name="sequence" eval="1"/> |
||||
|
<field name="view_mode">tree</field> |
||||
|
<field name="view_id" ref="contract_contract_tree_view"/> |
||||
|
<field name="act_window_id" ref="action_customer_contract"/> |
||||
|
</record> |
||||
|
|
||||
|
<record id="action_customer_contract_view_form" model="ir.actions.act_window.view"> |
||||
|
<field name="sequence" eval="2"/> |
||||
|
<field name="view_mode">form</field> |
||||
|
<field name="view_id" ref="contract_contract_customer_form_view"/> |
||||
|
<field name="act_window_id" ref="action_customer_contract"/> |
||||
|
</record> |
||||
|
|
||||
|
<menuitem id="menu_contract_contract_customer" |
||||
|
parent="account.menu_finance_receivables" |
||||
|
action="action_customer_contract" |
||||
|
sequence="99" |
||||
|
/> |
||||
|
|
||||
|
<!--ACTION supplier contracts--> |
||||
|
<record id="action_supplier_contract" model="ir.actions.act_window"> |
||||
|
<field name="name">Supplier Contracts</field> |
||||
|
<field name="res_model">contract.contract</field> |
||||
|
<field name="view_type">form</field> |
||||
|
<field name="view_mode">tree,form</field> |
||||
|
<field name="domain">[('contract_type', '=', 'purchase')]</field> |
||||
|
<field name="context">{'is_contract':1, |
||||
|
'search_default_not_finished':1, |
||||
|
'default_contract_type': 'purchase'} |
||||
|
</field> |
||||
|
<field name="search_view_id" ref="contract_contract_search_view"/> |
||||
|
<field name="help" type="html"> |
||||
|
<p class="oe_view_nocontent_create"> |
||||
|
Click to create a new contract. |
||||
|
</p> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="action_supplier_contract_view_tree" model="ir.actions.act_window.view"> |
||||
|
<field name="sequence" eval="1"/> |
||||
|
<field name="view_mode">tree</field> |
||||
|
<field name="view_id" ref="contract_contract_tree_view"/> |
||||
|
<field name="act_window_id" ref="action_supplier_contract"/> |
||||
|
</record> |
||||
|
|
||||
|
<record id="action_supplier_contract_view_form" model="ir.actions.act_window.view"> |
||||
|
<field name="sequence" eval="2"/> |
||||
|
<field name="view_mode">form</field> |
||||
|
<field name="view_id" ref="contract_contract_supplier_form_view"/> |
||||
|
<field name="act_window_id" ref="action_supplier_contract"/> |
||||
|
</record> |
||||
|
|
||||
|
<menuitem id="menu_contract_contract_supplier" |
||||
|
parent="account.menu_finance_payables" |
||||
|
action="action_supplier_contract" |
||||
|
sequence="99" |
||||
|
/> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,169 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
|
||||
|
<!--FORM view--> |
||||
|
<record id="contract_line_form_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.line form view (in contract)</field> |
||||
|
<field name="model">contract.line</field> |
||||
|
<field name="inherit_id" ref="contract_abstract_contract_line_form_view"/> |
||||
|
<field name="mode">primary</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<header position="inside"> |
||||
|
<field name="state" widget="statusbar"/> |
||||
|
</header> |
||||
|
<group name="recurrence_info" position="inside"> |
||||
|
<group> |
||||
|
<field name="create_invoice_visibility" invisible="1"/> |
||||
|
<field name="date_start" required="1"/> |
||||
|
<field name="recurring_next_date"/> |
||||
|
</group> |
||||
|
<group> |
||||
|
<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> |
||||
|
<field name="manual_renew_needed"/> |
||||
|
</group> |
||||
|
<group> |
||||
|
<field name="predecessor_contract_line_id"/> |
||||
|
</group> |
||||
|
<group> |
||||
|
<field name="successor_contract_line_id"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
<group name="recurrence_info" position="after"> |
||||
|
<group name="analytic" groups="analytic.group_analytic_accounting"> |
||||
|
<field name="analytic_account_id"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!--Customer FORM view--> |
||||
|
<record id="contract_line_customer_form_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.line customer form view (in contract)</field> |
||||
|
<field name="model">contract.line</field> |
||||
|
<field name="inherit_id" ref="contract_line_form_view"/> |
||||
|
<field name="mode">primary</field> |
||||
|
<field name="priority" eval="20"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="product_id" position="attributes"> |
||||
|
<attribute name="domain">[('sale_ok', '=', True)]</attribute> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!--Supplier FORM view--> |
||||
|
<record id="contract_line_supplier_form_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.line supplier form view (in contract)</field> |
||||
|
<field name="model">contract.line</field> |
||||
|
<field name="inherit_id" ref="contract_line_form_view"/> |
||||
|
<field name="mode">primary</field> |
||||
|
<field name="priority" eval="20"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="product_id" position="attributes"> |
||||
|
<attribute name="domain">[('purchase_ok', '=', True)]</attribute> |
||||
|
</field> |
||||
|
<field name="automatic_price" position="attributes"> |
||||
|
<attribute name="invisible">True</attribute> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!--TREE view--> |
||||
|
<record id="contract_line_tree_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.line tree view (in contract)</field> |
||||
|
<field name="model">contract.line</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree decoration-muted="is_canceled" |
||||
|
decoration-info="create_invoice_visibility and not is_canceled"> |
||||
|
<field name="sequence" widget="handle"/> |
||||
|
<field name="product_id"/> |
||||
|
<field name="name"/> |
||||
|
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/> |
||||
|
<field name="quantity"/> |
||||
|
<field name="uom_id"/> |
||||
|
<field name="automatic_price"/> |
||||
|
<field name="price_unit" |
||||
|
attrs="{'readonly': [('automatic_price', '=', True)]}"/> |
||||
|
<field name="specific_price" |
||||
|
invisible="1"/> |
||||
|
<field name="discount" |
||||
|
groups="base.group_no_one"/> |
||||
|
<field name="price_subtotal"/> |
||||
|
<field name="recurring_interval" |
||||
|
invisible="1"/> |
||||
|
<field name="recurring_rule_type" |
||||
|
invisible="1"/> |
||||
|
<field name="recurring_invoicing_type" |
||||
|
invisible="1"/> |
||||
|
<field name="date_start" required="1"/> |
||||
|
<field name="date_end"/> |
||||
|
<field name="recurring_next_date" |
||||
|
required="1"/> |
||||
|
<field name="last_date_invoiced" |
||||
|
groups="base.group_no_one"/> |
||||
|
<field name="create_invoice_visibility" |
||||
|
invisible="1"/> |
||||
|
<field name="is_plan_successor_allowed" |
||||
|
invisible="1"/> |
||||
|
<field name="is_stop_plan_successor_allowed" |
||||
|
invisible="1"/> |
||||
|
<field name="is_stop_allowed" |
||||
|
invisible="1"/> |
||||
|
<field name="is_cancel_allowed" |
||||
|
invisible="1"/> |
||||
|
<field name="is_un_cancel_allowed" |
||||
|
invisible="1"/> |
||||
|
<field name="is_auto_renew" invisible="1"/> |
||||
|
<field name="is_canceled" invisible="1"/> |
||||
|
<button name="action_plan_successor" |
||||
|
string="Plan Start" type="object" |
||||
|
icon="fa-calendar text-success" |
||||
|
attrs="{'invisible': [('is_plan_successor_allowed', '=', False)]}"/> |
||||
|
<button name="action_stop_plan_successor" |
||||
|
string="Stop Plan Successor" |
||||
|
type="object" |
||||
|
icon="fa-pause text-muted" |
||||
|
attrs="{'invisible': [('is_stop_plan_successor_allowed', '=', False)]}"/> |
||||
|
<button name="action_stop" string="Stop" |
||||
|
type="object" |
||||
|
icon="fa-stop text-danger" |
||||
|
attrs="{'invisible': [('is_stop_allowed', '=', False)]}"/> |
||||
|
<button name="cancel" string="Cancel" |
||||
|
type="object" |
||||
|
icon="fa-ban text-danger" |
||||
|
confirm="Are you sure you want to cancel this line" |
||||
|
attrs="{'invisible': [('is_cancel_allowed', '=', False)]}"/> |
||||
|
<button name="action_uncancel" |
||||
|
string="Un-cancel" type="object" |
||||
|
icon="fa-ban text-success" |
||||
|
attrs="{'invisible': [('is_un_cancel_allowed', '=', False)]}"/> |
||||
|
<button name="renew" string="Renew" |
||||
|
type="object" |
||||
|
icon="fa-fast-forward text-success" |
||||
|
groups="base.group_no_one" |
||||
|
attrs="{'invisible': [('is_auto_renew', '=', False)]}"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<!--Supplier TREE view--> |
||||
|
<record id="contract_line_supplier_tree_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.line supplier tree view (in contract)</field> |
||||
|
<field name="model">contract.line</field> |
||||
|
<field name="mode">primary</field> |
||||
|
<field name="priority" eval="20"/> |
||||
|
<field name="inherit_id" ref="contract_line_tree_view"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="automatic_price" position="attributes"> |
||||
|
<attribute name="invisible">True</attribute> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,17 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
|
||||
|
<!--FORM view--> |
||||
|
<record id="contract_template_line_form_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.template.line form view (in contract)</field> |
||||
|
<field name="model">contract.template.line</field> |
||||
|
<field name="inherit_id" ref="contract_abstract_contract_line_form_view"/> |
||||
|
<field name="mode">primary</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form position="attributes"> |
||||
|
<attribute name="string">Contract Template Line</attribute> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1 @@ |
|||||
|
from . import contract_line_wizard |
@ -0,0 +1,58 @@ |
|||||
|
# Copyright 2018 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import api, fields, models |
||||
|
|
||||
|
|
||||
|
class ContractLineWizard(models.TransientModel): |
||||
|
|
||||
|
_name = 'contract.line.wizard' |
||||
|
_description = 'Contract Line Wizard' |
||||
|
|
||||
|
date_start = fields.Date(string='Date Start') |
||||
|
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="contract.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, manual_renew_needed=wizard.manual_renew_needed |
||||
|
) |
||||
|
return True |
||||
|
|
||||
|
@api.multi |
||||
|
def plan_successor(self): |
||||
|
for wizard in self: |
||||
|
wizard.contract_line_id.plan_successor( |
||||
|
wizard.date_start, wizard.date_end, wizard.is_auto_renew |
||||
|
) |
||||
|
return True |
||||
|
|
||||
|
@api.multi |
||||
|
def stop_plan_successor(self): |
||||
|
for wizard in self: |
||||
|
wizard.contract_line_id.stop_plan_successor( |
||||
|
wizard.date_start, wizard.date_end, wizard.is_auto_renew |
||||
|
) |
||||
|
return True |
||||
|
|
||||
|
@api.multi |
||||
|
def uncancel(self): |
||||
|
for wizard in self: |
||||
|
wizard.contract_line_id.uncancel(wizard.recurring_next_date) |
||||
|
return True |
@ -0,0 +1,100 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- Copyright 2018 ACSONE SA/NV |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
|
<record model="ir.ui.view" id="contract_line_wizard_stop_form_view"> |
||||
|
<field name="name">contract.line.stop.wizard.form (in contract)</field> |
||||
|
<field name="model">contract.line.wizard</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<group> |
||||
|
<field name="contract_line_id" invisible="True"/> |
||||
|
<field string="Stop Date" name="date_end" required="True"/> |
||||
|
<field string="Is suspension without end date" name="manual_renew_needed"/> |
||||
|
</group> |
||||
|
<footer> |
||||
|
<button name="stop" |
||||
|
string="Validate" |
||||
|
class="btn-primary" |
||||
|
type="object"/> |
||||
|
<button string="Cancel" |
||||
|
class="btn-default" |
||||
|
special="cancel"/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record model="ir.ui.view" id="contract_line_wizard_plan_successor_form_view"> |
||||
|
<field name="name">contract.line.plan_successor.wizard.form (in contract)</field> |
||||
|
<field name="model">contract.line.wizard</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<group> |
||||
|
<field name="contract_line_id" invisible="True"/> |
||||
|
<field name="date_start" required="True"/> |
||||
|
<field name="date_end" attrs="{'required': [('is_auto_renew', '=', True)]}"/> |
||||
|
<field name="is_auto_renew"/> |
||||
|
</group> |
||||
|
<footer> |
||||
|
<button name="plan_successor" |
||||
|
string="Validate" |
||||
|
class="btn-primary" |
||||
|
type="object"/> |
||||
|
<button string="Cancel" |
||||
|
class="btn-default" |
||||
|
special="cancel"/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record model="ir.ui.view" id="contract_line_wizard_stop_plan_successor_form_view"> |
||||
|
<field name="name">contract.line.stop_plan_successor.wizard.form (in contract)</field> |
||||
|
<field name="model">contract.line.wizard</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<group> |
||||
|
<field name="contract_line_id" invisible="True"/> |
||||
|
<field string="Suspension Start Date" name="date_start" required="True"/> |
||||
|
<field string="Suspension End Date" name="date_end" required="True"/> |
||||
|
<field name="is_auto_renew" invisible="1"/> |
||||
|
</group> |
||||
|
<footer> |
||||
|
<button name="stop_plan_successor" |
||||
|
string="Validate" |
||||
|
class="btn-primary" |
||||
|
type="object"/> |
||||
|
<button string="Cancel" |
||||
|
class="btn-default" |
||||
|
special="cancel"/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record model="ir.ui.view" id="contract_line_wizard_uncancel_form_view"> |
||||
|
<field name="name">contract.line.stop_plan_successor.wizard.form (in contract)</field> |
||||
|
<field name="model">contract.line.wizard</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<group> |
||||
|
<field name="contract_line_id" invisible="True"/> |
||||
|
<field name="recurring_next_date" required="True"/> |
||||
|
</group> |
||||
|
<footer> |
||||
|
<button name="uncancel" |
||||
|
string="Validate" |
||||
|
class="btn-primary" |
||||
|
type="object"/> |
||||
|
<button string="Cancel" |
||||
|
class="btn-default" |
||||
|
special="cancel"/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,32 @@ |
|||||
|
# Copyright 2019 ACSONE SA/NV |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
import logging |
||||
|
|
||||
|
from openupgradelib import openupgrade |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
def migrate(cr, version): |
||||
|
xmlids_to_rename = [ |
||||
|
('contract_sale.account_analytic_account_own_salesman', |
||||
|
'contract_sale.contract_contract_own_salesman'), |
||||
|
('contract_sale.account_analytic_account_see_all', |
||||
|
'contract_sale.contract_contract_see_all'), |
||||
|
('contract_sale.account_analytic_contract_salesman', |
||||
|
'contract_sale.contract_template_salesman'), |
||||
|
('contract_sale.account_analytic_contract_sale_manager', |
||||
|
'contract_sale.contract_template_sale_manager'), |
||||
|
('contract_sale.account_analytic_invoice_line_saleman', |
||||
|
'contract_sale.contract_line_saleman'), |
||||
|
('contract_sale.account_analytic_invoice_line_manager', |
||||
|
'contract_sale.contract_line_manager'), |
||||
|
('contract_sale.account_analytic_contract_line_salesman', |
||||
|
'contract_sale.contract_template_line_salesman'), |
||||
|
('contract_sale.account_analytic_contract_line_manager', |
||||
|
'contract_sale.contract_template_line_manager'), |
||||
|
('contract_sale.account_analytic_account_contract_salesman', |
||||
|
'contract_sale.contract_contract_salesman'), |
||||
|
] |
||||
|
openupgrade.rename_xmlids(cr, xmlids_to_rename) |
@ -1,15 +1,15 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<odoo noupdate="1"> |
<odoo noupdate="1"> |
||||
|
|
||||
<record id="account_analytic_account_own_salesman" model="ir.rule"> |
|
||||
|
<record id="contract_contract_own_salesman" model="ir.rule"> |
||||
<field name="name">See Own Contracts</field> |
<field name="name">See Own Contracts</field> |
||||
<field name="model_id" ref="analytic.model_account_analytic_account"/> |
|
||||
|
<field name="model_id" ref="contract.model_contract_contract"/> |
||||
<field name="domain_force">['|', ('user_id','=',user.id), ('user_id','=',False)]</field> |
<field name="domain_force">['|', ('user_id','=',user.id), ('user_id','=',False)]</field> |
||||
<field name="groups" eval="[(4,ref('sales_team.group_sale_salesman'))]"/> |
<field name="groups" eval="[(4,ref('sales_team.group_sale_salesman'))]"/> |
||||
</record> |
</record> |
||||
<record id="account_analytic_account_see_all" model="ir.rule"> |
|
||||
|
<record id="contract_contract_see_all" model="ir.rule"> |
||||
<field name="name">See All Contracts</field> |
<field name="name">See All Contracts</field> |
||||
<field name="model_id" ref="analytic.model_account_analytic_account"/> |
|
||||
|
<field name="model_id" ref="contract.model_contract_contract"/> |
||||
<field name="domain_force">[(1,'=',1)]</field> |
<field name="domain_force">[(1,'=',1)]</field> |
||||
<field name="groups" eval="[ |
<field name="groups" eval="[ |
||||
(4,ref('sales_team.group_sale_salesman_all_leads')), |
(4,ref('sales_team.group_sale_salesman_all_leads')), |
@ -1,12 +1,12 @@ |
|||||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
||||
"account_analytic_contract_salesman","Recurring Salesman","contract.model_account_analytic_contract","sales_team.group_sale_salesman",1,1,1,0 |
|
||||
"account_analytic_contract_sale_manager","Recurring Sale Manager","contract.model_account_analytic_contract","sales_team.group_sale_manager",1,1,1,1 |
|
||||
"account_analytic_invoice_line_saleman","Recurring Invoice Line Saleman","contract.model_account_analytic_invoice_line","sales_team.group_sale_salesman",1,1,1,0 |
|
||||
"account_analytic_invoice_line_manager","Recurring Invoice Line Manager","contract.model_account_analytic_invoice_line","sales_team.group_sale_manager",1,1,1,1 |
|
||||
"account_analytic_contract_line_salesman","Recurring Contract Line Salesman","contract.model_account_analytic_contract_line","sales_team.group_sale_salesman",1,1,1,0 |
|
||||
"account_analytic_contract_line_manager","Recurring Contract Line Manager","contract.model_account_analytic_contract_line","sales_team.group_sale_manager",1,1,1,1 |
|
||||
|
"contract_template_salesman","Recurring Salesman","contract.model_contract_template","sales_team.group_sale_salesman",1,1,1,0 |
||||
|
"contract_template_sale_manager","Recurring Sale Manager","contract.model_contract_template","sales_team.group_sale_manager",1,1,1,1 |
||||
|
"contract_line_saleman","Recurring Invoice Line Saleman","contract.model_contract_line","sales_team.group_sale_salesman",1,1,1,0 |
||||
|
"contract_line_manager","Recurring Invoice Line Manager","contract.model_contract_line","sales_team.group_sale_manager",1,1,1,1 |
||||
|
"contract_template_line_salesman","Recurring Contract Line Salesman","contract.model_contract_template_line","sales_team.group_sale_salesman",1,1,1,0 |
||||
|
"contract_template_line_manager","Recurring Contract Line Manager","contract.model_contract_template_line","sales_team.group_sale_manager",1,1,1,1 |
||||
"account_analytic_line_contract_salesman","Recurring Analytic Line Salesman","analytic.model_account_analytic_line","sales_team.group_sale_salesman",1,1,1,0 |
"account_analytic_line_contract_salesman","Recurring Analytic Line Salesman","analytic.model_account_analytic_line","sales_team.group_sale_salesman",1,1,1,0 |
||||
"account_analytic_account_contract_salesman","Recurring Analytic Account Salesman","analytic.model_account_analytic_account","sales_team.group_sale_salesman",1,1,1,0 |
|
||||
|
"contract_contract_salesman","Recurring Analytic Account Salesman","contract.model_contract_contract","sales_team.group_sale_salesman",1,1,1,0 |
||||
"account_analytic_tag_contract_salesman","Recurring Account Analytic Tag Salesman","analytic.model_account_analytic_tag","sales_team.group_sale_salesman",1,1,1,0 |
"account_analytic_tag_contract_salesman","Recurring Account Analytic Tag Salesman","analytic.model_account_analytic_tag","sales_team.group_sale_salesman",1,1,1,0 |
||||
"account_invoice_contract_salesman","Recurring Account Inoice Salesman","account.model_account_invoice","sales_team.group_sale_salesman",1,0,0,0 |
"account_invoice_contract_salesman","Recurring Account Inoice Salesman","account.model_account_invoice","sales_team.group_sale_salesman",1,0,0,0 |
||||
"account_journal_contract_salesman","Recurring Account Journal Salesman","account.model_account_journal","sales_team.group_sale_salesman",1,0,0,0 |
"account_journal_contract_salesman","Recurring Account Journal Salesman","account.model_account_journal","sales_team.group_sale_salesman",1,0,0,0 |
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
|
||||
|
<!--FORM view--> |
||||
|
<record id="contract_abstract_contract_line_form_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.abstract.contract.line form view (in contract_sale)</field> |
||||
|
<field name="model">contract.abstract.contract.line</field> |
||||
|
<field name="inherit_id" ref="contract.contract_abstract_contract_line_form_view"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="discount" position="attributes"> |
||||
|
<attribute name="groups">sale.group_discount_per_so_line</attribute> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -1,28 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<odoo> |
|
||||
|
|
||||
<record id="account_analytic_account_recurring_form_form" |
|
||||
model="ir.ui.view"> |
|
||||
<field name="name">Contract form (in contract_sale)</field> |
|
||||
<field name="model">account.analytic.account</field> |
|
||||
<field name="inherit_id" |
|
||||
ref="contract.account_analytic_account_recurring_form_form"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
|
|
||||
<xpath expr="//field[@name='discount']" position="attributes"> |
|
||||
<attribute name="groups">sale.group_discount_per_so_line |
|
||||
</attribute> |
|
||||
</xpath> |
|
||||
|
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<menuitem |
|
||||
id="menu_contract_sale" name="Contracts" |
|
||||
parent="sale.sale_order_menu" |
|
||||
action="contract.action_account_analytic_sale_overdue_all" |
|
||||
sequence="2" |
|
||||
groups="sales_team.group_sale_salesman" |
|
||||
/> |
|
||||
|
|
||||
</odoo> |
|
@ -1,21 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<odoo> |
|
||||
|
|
||||
<record id="account_analytic_contract_view_form" model="ir.ui.view"> |
|
||||
<field name="name">Account Analytic Contract Form View (in |
|
||||
sale_contract) |
|
||||
</field> |
|
||||
<field name="model">account.analytic.contract</field> |
|
||||
<field name="inherit_id" |
|
||||
ref="contract.account_analytic_contract_view_form"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
|
|
||||
<xpath expr="//field[@name='discount']" position="attributes"> |
|
||||
<attribute name="groups">sale.group_discount_per_so_line |
|
||||
</attribute> |
|
||||
</xpath> |
|
||||
|
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
</odoo> |
|
@ -0,0 +1,12 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
|
||||
|
<menuitem |
||||
|
id="menu_contract_sale" name="Contracts" |
||||
|
parent="sale.sale_order_menu" |
||||
|
action="contract.action_supplier_contract" |
||||
|
sequence="2" |
||||
|
groups="sales_team.group_sale_salesman" |
||||
|
/> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
|
||||
|
<!--TREE view--> |
||||
|
<record id="contract_line_tree_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.template.line tree view (in contract_sale)</field> |
||||
|
<field name="model">contract.line</field> |
||||
|
<field name="inherit_id" ref="contract.contract_line_tree_view"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="discount" position="attributes"> |
||||
|
<attribute name="groups">sale.group_discount_per_so_line</attribute> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -0,0 +1,17 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
|
||||
|
<!--FORM view--> |
||||
|
<record id="contract_template_form_view" model="ir.ui.view"> |
||||
|
<field name="name">contract.template form view (in contract_sale)</field> |
||||
|
<field name="model">contract.template</field> |
||||
|
<field name="inherit_id" ref="contract.contract_template_form_view"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="discount" position="attributes"> |
||||
|
<attribute name="groups">sale.group_discount_per_so_line</attribute> |
||||
|
</field> |
||||
|
|
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue