diff --git a/product_contract/README.rst b/product_contract/README.rst index cf9a1e0af..28989716d 100644 --- a/product_contract/README.rst +++ b/product_contract/README.rst @@ -2,10 +2,13 @@ Recurring - Product Contract ============================ -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:ad8724fbb6c54e3f450ed7f3b3aa16384ed79b097065f0de96cc56e87eef2e71 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -14,16 +17,16 @@ Recurring - Product Contract :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fcontract-lightgray.png?logo=github - :target: https://github.com/OCA/contract/tree/14.0/product_contract + :target: https://github.com/OCA/contract/tree/16.0/product_contract :alt: OCA/contract .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/contract-14-0/contract-14-0-product_contract + :target: https://translation.odoo-community.org/projects/contract-16-0/contract-16-0-product_contract :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/110/14.0 - :alt: Try me on Runbot +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/contract&target_branch=16.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This module adds support for products to be linked to contract templates. @@ -51,8 +54,8 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us smashing it by providing a detailed and welcomed -`feedback `_. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -96,6 +99,6 @@ Current `maintainer `__: |maintainer-sbejaoui| -This module is part of the `OCA/contract `_ project on GitHub. +This module is part of the `OCA/contract `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py index 2214c6b34..9275ab61c 100644 --- a/product_contract/__manifest__.py +++ b/product_contract/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Recurring - Product Contract", - "version": "14.0.1.0.1", + "version": "16.0.1.0.0", "category": "Contract Management", "license": "AGPL-3", "author": "LasLabs, " "ACSONE SA/NV, " "Odoo Community Association (OCA)", diff --git a/product_contract/models/contract_line.py b/product_contract/models/contract_line.py index 91c08480a..cc351aff6 100644 --- a/product_contract/models/contract_line.py +++ b/product_contract/models/contract_line.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models +from odoo import Command, api, fields, models class ContractLine(models.Model): @@ -17,10 +17,10 @@ class ContractLine(models.Model): copy=False, ) - def _prepare_invoice_line(self, move_form): - res = super(ContractLine, self)._prepare_invoice_line(move_form) + def _prepare_invoice_line(self): + res = super()._prepare_invoice_line() if self.sale_order_line_id and res: - res["sale_line_ids"] = [(6, 0, [self.sale_order_line_id.id])] + res["sale_line_ids"] = [Command.set([self.sale_order_line_id.id])] return res def _get_auto_renew_rule_type(self): @@ -37,9 +37,6 @@ class ContractLine(models.Model): if rec.product_id.is_contract: rec.update( { - "recurring_rule_type": rec.product_id.recurring_rule_type, - "recurring_invoicing_type": rec.product_id.recurring_invoicing_type, - "recurring_interval": 1, "is_auto_renew": rec.product_id.is_auto_renew, "auto_renew_interval": rec.product_id.auto_renew_interval, "auto_renew_rule_type": rec.product_id.auto_renew_rule_type, @@ -51,3 +48,30 @@ class ContractLine(models.Model): ), } ) + + def _set_recurrence_field(self, field): + res = super()._set_recurrence_field(field) + for record in self: + if record.product_id.is_contract and field in record.product_id: + record[field] = record.product_id[field] + return res + + @api.depends( + "contract_id.recurring_rule_type", "contract_id.line_recurrence", "product_id" + ) + def _compute_recurring_rule_type(self): + return super()._compute_recurring_rule_type() + + @api.depends( + "contract_id.recurring_invoicing_type", + "contract_id.line_recurrence", + "product_id", + ) + def _compute_recurring_invoicing_type(self): + return super()._compute_recurring_invoicing_type() + + @api.depends( + "contract_id.recurring_interval", "contract_id.line_recurrence", "product_id" + ) + def _compute_recurring_interval(self): + return super()._compute_recurring_interval() diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index 12bbeda64..606ac6ef9 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -68,12 +68,12 @@ class ProductTemplate(models.Model): self.with_company(company).write( {"property_contract_template_id": False} ) - super().write(vals) + return super().write(vals) @api.constrains("is_contract", "type") def _check_contract_product_type(self): """ Contract product should be service type """ - if self.is_contract and self.type != "service": + if any([product.is_contract and product.type != "service" for product in self]): raise ValidationError(_("Contract product should be service type")) diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index 0832b7b1b..dd3bc2a7a 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -14,7 +14,7 @@ class SaleOrder(models.Model): need_contract_creation = fields.Boolean(compute="_compute_need_contract_creation") @api.constrains("state") - def _check_contact_is_not_terminated(self): + def _check_contract_is_not_terminated(self): for rec in self: if rec.state not in ( "sale", @@ -49,9 +49,7 @@ class SaleOrder(models.Model): def _prepare_contract_value(self, contract_template): self.ensure_one() return { - "name": "{template_name}: {sale_name}".format( - template_name=contract_template.name, sale_name=self.name - ), + "name": f"{contract_template.name}: {self.name}", "partner_id": self.partner_id.id, "company_id": self.company_id.id, "contract_template_id": contract_template.id, @@ -59,7 +57,7 @@ class SaleOrder(models.Model): "payment_term_id": self.payment_term_id.id, "fiscal_position_id": self.fiscal_position_id.id, "invoice_partner_id": self.partner_invoice_id.id, - "line_recurrence": self.partner_invoice_id.id, + "line_recurrence": True, } def action_create_contract(self): @@ -84,8 +82,13 @@ class SaleOrder(models.Model): raise ValidationError( _( "You must specify a contract " - "template for '{}' product in '{}' company." - ).format(order_line.product_id.name, rec.company_id.name) + "template for '%(product_name)s' product " + "in '%(company_name)s' company." + ) + % { + "product_name": order_line.product_id.name, + "company_name": rec.company_id.name, + } ) contract_templates |= contract_template for contract_template in contract_templates: @@ -98,7 +101,7 @@ class SaleOrder(models.Model): contract = contract_model.create( rec._prepare_contract_value(contract_template) ) - contracts.append(contract) + contracts.append(contract.id) contract._onchange_contract_template_id() contract._onchange_contract_type() order_lines.create_contract_line(contract) @@ -112,7 +115,7 @@ class SaleOrder(models.Model): self.filtered( lambda order: (order.company_id.create_contract_at_sale_order_confirmation) ).action_create_contract() - return super(SaleOrder, self).action_confirm() + return super().action_confirm() @api.depends("order_line") def _compute_contract_count(self): diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 038dd6d6f..4d2ec0091 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -43,8 +43,8 @@ class SaleOrderLine(models.Model): help="Specify if process date is 'from' or 'to' invoicing date", copy=False, ) - date_start = fields.Date(string="Date Start") - date_end = fields.Date(string="Date End") + date_start = fields.Date() + date_end = fields.Date() contract_line_id = fields.Many2one( comodel_name="contract.line", @@ -93,7 +93,7 @@ class SaleOrderLine(models.Model): _("You can't upsell or downsell a terminated contract") ) - @api.depends("product_id") + @api.depends("product_id", "order_id.company_id") def _compute_contract_template_id(self): for rec in self: rec.contract_template_id = rec.product_id.with_company( @@ -141,21 +141,13 @@ class SaleOrderLine(models.Model): rec.date_end = rec._get_date_end() if rec.date_start else False def _get_contract_line_qty(self): - """Returns the quantity to be put on new contract lines.""" + """Returns the amount that will be placed in new contract lines.""" self.ensure_one() - # The quantity on the generated contract line is 1, as it - # correspond to the most common use cases: - # - quantity on the SO line = number of periods sold and unit - # price the price of one period, so the - # total amount of the SO corresponds to the planned value - # of the contract; in this case the quantity on the contract - # line must be 1 - # - quantity on the SO line = number of hours sold, - # automatic invoicing of the actual hours through a variable - # quantity formula, in which case the quantity on the contract - # line is not used + # The quantity in the generated contract line is the quantity of + # product requested in the order, since they correspond to the most common + # use cases. # Other use cases are easy to implement by overriding this method. - return 1.0 + return self.product_uom_qty def _prepare_contract_line_values( self, contract, predecessor_contract_line_id=False @@ -198,7 +190,7 @@ class SaleOrderLine(models.Model): "contract_id": contract.id, "sale_order_line_id": self.id, "predecessor_contract_line_id": predecessor_contract_line_id, - "analytic_account_id": self.order_id.analytic_account_id.id, + "analytic_distribution": self.analytic_distribution, } def create_contract_line(self, contract): @@ -269,17 +261,11 @@ class SaleOrderLine(models.Model): SaleOrderLine, self.filtered(lambda l: not l.contract_id) ).invoice_line_create(invoice_id, qty) - @api.depends( - "qty_invoiced", - "qty_delivered", - "product_uom_qty", - "order_id.state", - "product_id.is_contract", - ) - def _get_to_invoice_qty(self): + @api.depends("qty_invoiced", "qty_delivered", "product_uom_qty", "state") + def _compute_qty_to_invoice(self): """ sale line linked to contracts must not be invoiced from sale order """ - res = super()._get_to_invoice_qty() + res = super()._compute_qty_to_invoice() self.filtered("product_id.is_contract").update({"qty_to_invoice": 0.0}) return res diff --git a/product_contract/static/description/index.html b/product_contract/static/description/index.html index 80c65180d..a5d3d0bd5 100644 --- a/product_contract/static/description/index.html +++ b/product_contract/static/description/index.html @@ -1,20 +1,20 @@ - + - + Recurring - Product Contract