From 563c0c360e75cf3e1c828b00febc8ce869bd141c Mon Sep 17 00:00:00 2001 From: Alexandre Fayolle Date: Sun, 29 Sep 2019 23:17:03 +0200 Subject: [PATCH] [ADD] agreement_sale_condition_base --- agreement_sale_condition_base/README.rst | 102 ++++ agreement_sale_condition_base/__init__.py | 2 + agreement_sale_condition_base/__manifest__.py | 38 ++ .../data/agreement.xml | 14 + .../data/agreement_type.xml | 8 + .../demo/agreement.csv | 5 + .../demo/agreement_type.xml | 25 + .../models/__init__.py | 7 + .../models/account_invoice.py | 9 + .../models/agreement.py | 135 ++++++ .../models/agreement_parameter_value.py | 51 ++ .../models/agreement_type.py | 30 ++ .../models/procurement_group.py | 20 + .../models/res_partner.py | 21 + .../models/sale_order.py | 123 +++++ .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 24 + .../security/ir.model.access.csv | 3 + .../static/description/index.html | 440 ++++++++++++++++++ .../tests/__init__.py | 1 + .../tests/test_agreement_propagation.py | 75 +++ agreement_sale_condition_base/tools.py | 10 + .../views/agreement.xml | 216 +++++++++ .../views/agreement_parameter_value.xml | 44 ++ .../views/agreement_type.xml | 52 +++ .../views/procurement_group.xml | 13 + .../views/res_partner.xml | 21 + .../views/sale_order.xml | 17 + 28 files changed, 1507 insertions(+) create mode 100644 agreement_sale_condition_base/README.rst create mode 100644 agreement_sale_condition_base/__init__.py create mode 100644 agreement_sale_condition_base/__manifest__.py create mode 100644 agreement_sale_condition_base/data/agreement.xml create mode 100644 agreement_sale_condition_base/data/agreement_type.xml create mode 100644 agreement_sale_condition_base/demo/agreement.csv create mode 100644 agreement_sale_condition_base/demo/agreement_type.xml create mode 100644 agreement_sale_condition_base/models/__init__.py create mode 100644 agreement_sale_condition_base/models/account_invoice.py create mode 100644 agreement_sale_condition_base/models/agreement.py create mode 100644 agreement_sale_condition_base/models/agreement_parameter_value.py create mode 100644 agreement_sale_condition_base/models/agreement_type.py create mode 100644 agreement_sale_condition_base/models/procurement_group.py create mode 100644 agreement_sale_condition_base/models/res_partner.py create mode 100644 agreement_sale_condition_base/models/sale_order.py create mode 100644 agreement_sale_condition_base/readme/CONTRIBUTORS.rst create mode 100644 agreement_sale_condition_base/readme/DESCRIPTION.rst create mode 100644 agreement_sale_condition_base/security/ir.model.access.csv create mode 100644 agreement_sale_condition_base/static/description/index.html create mode 100644 agreement_sale_condition_base/tests/__init__.py create mode 100644 agreement_sale_condition_base/tests/test_agreement_propagation.py create mode 100644 agreement_sale_condition_base/tools.py create mode 100644 agreement_sale_condition_base/views/agreement.xml create mode 100644 agreement_sale_condition_base/views/agreement_parameter_value.xml create mode 100644 agreement_sale_condition_base/views/agreement_type.xml create mode 100644 agreement_sale_condition_base/views/procurement_group.xml create mode 100644 agreement_sale_condition_base/views/res_partner.xml create mode 100644 agreement_sale_condition_base/views/sale_order.xml diff --git a/agreement_sale_condition_base/README.rst b/agreement_sale_condition_base/README.rst new file mode 100644 index 00000000..edeb0a30 --- /dev/null +++ b/agreement_sale_condition_base/README.rst @@ -0,0 +1,102 @@ +========================= +Agreement Sale Conditions +========================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :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/12.0/agreement_sale_condition + :alt: OCA/contract +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/contract-12-0/contract-12-0-agreement_sale_condition + :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/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module builds on the agreement and agreement_sale modules. It defines a +model for agreement parameter values. The intent is that additional modules can +add fields on an agreement which are m2o on to parameter values, and possibly +the allowed values for a given field could depend on the value of another one. + +An agreement type can be linked to a template agreement. When the agreement +type is set on a sale order, a new agreement record is created using the +template agreement as a basis. This agreement specific to the sale order can be +customized by the sales man. + +When the sale order is confirmed, the agreement of the sale order is propagated +to the procurement group of the sale order. This can be used by extension +modules to customize the generation of the stock moves and pickings to match +the agreement. + +In a similar fashion, when an invoice is created from the sale order, the +agreement of the sale order, the agreement is propagated to the invoice. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Alexandre Fayolle + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-gurneyalex| image:: https://github.com/gurneyalex.png?size=40px + :target: https://github.com/gurneyalex + :alt: gurneyalex + +Current `maintainer `__: + +|maintainer-gurneyalex| + +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/agreement_sale_condition_base/__init__.py b/agreement_sale_condition_base/__init__.py new file mode 100644 index 00000000..e47d26c5 --- /dev/null +++ b/agreement_sale_condition_base/__init__.py @@ -0,0 +1,2 @@ +from . import tools +from . import models diff --git a/agreement_sale_condition_base/__manifest__.py b/agreement_sale_condition_base/__manifest__.py new file mode 100644 index 00000000..90e8347d --- /dev/null +++ b/agreement_sale_condition_base/__manifest__.py @@ -0,0 +1,38 @@ +# Copyright 2019 Camptocamp +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Agreement Sale Conditions and Partner Preferences (base module)", + "summary": "Base module to manage sale conditions " + "and partner preferences in agreements", + "version": "12.0.1.0.0", + # see https://odoo-community.org/page/development-status + "development_status": "Alpha", + "category": "Sale", + "author": "Camptocamp, Odoo Community Association (OCA)", + "maintainers": ["gurneyalex"], + "license": "AGPL-3", + "application": False, + "installable": True, + "preloadable": True, + "depends": [ + "agreement_sale", + "stock", + "delivery", + ], + "data": [ + # "security/sale_service_definition_security.xml", + "security/ir.model.access.csv", + "views/agreement.xml", + "views/agreement_type.xml", + "views/agreement_parameter_value.xml", + "views/sale_order.xml", + "views/procurement_group.xml", + "views/res_partner.xml", + "data/agreement_type.xml", + "data/agreement.xml", + ], + 'demo': [ + "demo/agreement_type.xml", + "demo/agreement.csv", + ], +} diff --git a/agreement_sale_condition_base/data/agreement.xml b/agreement_sale_condition_base/data/agreement.xml new file mode 100644 index 00000000..9e52833f --- /dev/null +++ b/agreement_sale_condition_base/data/agreement.xml @@ -0,0 +1,14 @@ + + + + Standard Template + standard_tmpl + + + + + + + diff --git a/agreement_sale_condition_base/data/agreement_type.xml b/agreement_sale_condition_base/data/agreement_type.xml new file mode 100644 index 00000000..4b715e16 --- /dev/null +++ b/agreement_sale_condition_base/data/agreement_type.xml @@ -0,0 +1,8 @@ + + + + + standard + Standard + + diff --git a/agreement_sale_condition_base/demo/agreement.csv b/agreement_sale_condition_base/demo/agreement.csv new file mode 100644 index 00000000..03a734c3 --- /dev/null +++ b/agreement_sale_condition_base/demo/agreement.csv @@ -0,0 +1,5 @@ +id,code,name,partner_id/id,agreement_type_id/id,is_template +agreement_sale_condition_base.agreement_next_day,next_day_tmpl,Template Standard Next Day,base.main_company,agreement_sale_condition_base.agreement_type_next_day,True +agreement_sale_condition_base.agreement_weekly_routine,weekly_routine_tmpl,Template Standard Weekly Routine,base.main_company,agreement_sale_condition_base.agreement_type_weekly_routine,True +agreement_sale_condition_base.agreement_agreed_date,agreed_date_tmpl,Template Standard Agreed Date,base.main_company,agreement_sale_condition_base.agreement_type_agreed_date,True +agreement_sale_condition_base.agreement_pick_up,pickup_tmpl,Template Pick Up,base.main_company,agreement_sale_condition_base.agreement_type_pick_up,True diff --git a/agreement_sale_condition_base/demo/agreement_type.xml b/agreement_sale_condition_base/demo/agreement_type.xml new file mode 100644 index 00000000..5c15b4d2 --- /dev/null +++ b/agreement_sale_condition_base/demo/agreement_type.xml @@ -0,0 +1,25 @@ + + + + + next_day + Next Day + + + + weekly_routine + Weekly Routine + + + + agreed_date + Agreed Date + + + + pick_up + Pick Up + + + + diff --git a/agreement_sale_condition_base/models/__init__.py b/agreement_sale_condition_base/models/__init__.py new file mode 100644 index 00000000..05ab2eda --- /dev/null +++ b/agreement_sale_condition_base/models/__init__.py @@ -0,0 +1,7 @@ +from . import agreement +from . import agreement_type +from . import agreement_parameter_value +from . import res_partner +from . import sale_order +from . import account_invoice +from . import procurement_group diff --git a/agreement_sale_condition_base/models/account_invoice.py b/agreement_sale_condition_base/models/account_invoice.py new file mode 100644 index 00000000..b7034eab --- /dev/null +++ b/agreement_sale_condition_base/models/account_invoice.py @@ -0,0 +1,9 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class AccountInvoice(models.Model): + _inherit = 'account.invoice' + + agreement_id = fields.Many2one('agreement', readonly=True) diff --git a/agreement_sale_condition_base/models/agreement.py b/agreement_sale_condition_base/models/agreement.py new file mode 100644 index 00000000..d796031e --- /dev/null +++ b/agreement_sale_condition_base/models/agreement.py @@ -0,0 +1,135 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, exceptions, fields, models + + +class Agreement(models.Model): + _inherit = 'agreement' + + code = fields.Char(required=False) + sale_ids = fields.One2many( + 'sale.order', 'agreement_id', string="Sale Orders" + ) + is_sale_agreement = fields.Boolean( + 'Sale Agreement?', compute='_compute_is_sale_agreement', store=True + ) + is_customer_requirement = fields.Boolean('Customer requirement?') + + @api.depends('sale_ids') + def _compute_is_sale_agreement(self): + for rec in self: + rec.is_sale_agreement = bool(rec.sale_ids) + + @api.model + def _get_customer_agreement_fields(self): + """return a list of field names which are related to customer preferences""" + # extend the list in modules implementing specific conditions + return [] + + @api.model + def _get_sale_agreement_fields(self): + """return a list of field names which are related to sale conditions""" + # extend the list in modules implementing specific conditions + return [] + + def _get_agreement_fields(self): + return self._get_sale_agreement_fields() + \ + self._get_customer_agreement_fields() + + def template_prepare_agreement_values(self, sale_order): + self.ensure_one() + assert self.is_template, \ + ( + 'Programming error: ' + 'this method must only be called on a template agreement' + ) + fields = ['agreement_type_id'] + self._get_sale_agreement_fields() + tmpl_values = self.read(fields, load='_classic_write')[0] + del tmpl_values['id'] + tmpl_values.update( + { + 'name': self.agreement_type_id.name, + 'is_template': False, + 'is_customer_requirement': False + } + ) + partner = sale_order.partner_id + customer_settings = partner.customer_agreement_settings_id + assert not customer_settings or \ + customer_settings.is_customer_requirement, \ + 'customer_settings must be a customer requirement agreement' + if customer_settings: + fields = self._get_customer_agreement_fields() + cust_vals = customer_settings.read( + fields, load='_classic_write' + )[0] + del cust_vals['id'] + tmpl_values.update(cust_vals) + tmpl_values['name'] = self.agreement_type.name + return tmpl_values + + @api.multi + def write(self, values): + res = super().write(values) + if values.get('agreement_type_id'): + self._check_agreement_type_default_agreement() + return res + + @api.model_create_multi + @api.returns('self', lambda value: value.id) + def create(self, vals_list): + res = super().create(vals_list) + res._check_agreement_type_default_agreement() + return res + + def _check_agreement_type_default_agreement(self): + # if we are setting an agreement type on a template agreement then the + # template agreement becomes the default agreement of the agreement + # type + template_agreements = self.filtered('is_template') + for template in template_agreements: + agreement_type = template.agreement_type_id + if not agreement_type.default_agreement_id: + agreement_type.default_agreement_id = template + elif agreement_type.default_agreement_id == template: + continue + else: + raise exceptions.UserError( + _('It is forbidden to have 2 templates in the ' + 'same agreement type.') + ) + + def _values_for_clear_defaults(self): + values = {} + for field in self._get_customer_agreement_fields(): + values[field] = False + return values + + @api.onchange('is_template') + def onchange_is_template(self): + if self.is_template: + self.unset_customer_defaults() + self.partner_id = False + + def unset_customer_defaults(self): + values = self._values_for_clear_defaults() + for rec in self: + rec.update(values) + + @api.onchange('is_customer_requirement') + def onchange_is_customer(self): + if self.is_customer_requirement: + self.is_template = False + self.set_customer_defaults() + + def _get_customer_defaults_values(self): + ParameterValue = self.env['agreement.parameter.value'] + values = {} + for field in self._get_customer_agreement_fields(): + values[field] = ParameterValue.get_default(field) + return values + + def set_customer_defaults(self): + values = self._get_customer_defaults_values() + for rec in self: + rec.update(values) diff --git a/agreement_sale_condition_base/models/agreement_parameter_value.py b/agreement_sale_condition_base/models/agreement_parameter_value.py new file mode 100644 index 00000000..1efd4d5e --- /dev/null +++ b/agreement_sale_condition_base/models/agreement_parameter_value.py @@ -0,0 +1,51 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models +from ..tools import _default_sequence + + +class AgreementParameterValue(models.Model): + _name = 'agreement.parameter.value' + _description = 'Possible values for agreement m2o parameters' + _order = "parameter, sequence" + + name = fields.Char(required=True, translate=True) + code = fields.Char( + required=True, + index=True, + help="code of the value, can be used to search for it " + "in a language neutral way", + ) + sequence = fields.Integer( + required=True, + default=_default_sequence, + help="order the values for a given parameter", + ) + parameter = fields.Char( + required=True, + help='set to the name of the agreement field which ' + 'can have this value (not strictly required but it helps)', + ) + is_default = fields.Boolean( + string="Is the default value?", + help="Set this on 1 record for a given parameter to flag the value as " + "the default value. If a parameter has no default value, then its " + "default is False", + ) + + @api.model + def get(self, parameter, code): + res = self.search( + [('parameter', '=', parameter), ('code', '=', code)], limit=1 + ) + if res: + return res + else: + raise ValueError((parameter, code)) + + @api.model + def get_default(self, parameter): + res = self.search( + [('parameter', '=', parameter), ('is_default', '=', True)], limit=1 + ) + return res or False diff --git a/agreement_sale_condition_base/models/agreement_type.py b/agreement_sale_condition_base/models/agreement_type.py new file mode 100644 index 00000000..2a3a944b --- /dev/null +++ b/agreement_sale_condition_base/models/agreement_type.py @@ -0,0 +1,30 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, exceptions, fields, models +from ..tools import _default_sequence + + +class AgreementType(models.Model): + _inherit = 'agreement.type' + + _description = 'Types of sale agreement policies' + _order = 'sequence, code' + + code = fields.Char(index=True) + sequence = fields.Integer(index=True, default=_default_sequence) + active = fields.Boolean(default=True) + + default_agreement_id = fields.Many2one( + 'agreement', + string="Default Agreement", + domain=[('is_template', '=', True)], + ) + + @api.constrains('default_agreement_id') + def _check_code_agreement(self): + for rec in self: + if rec.default_agreement_id.agreement_type_id != rec: + msg = _( + 'The type of the default agreement must be %s, not %s' + ) % (rec.name, rec.default_agreement_id.agreement_type_id.name) + raise exceptions.ValidationError(msg) diff --git a/agreement_sale_condition_base/models/procurement_group.py b/agreement_sale_condition_base/models/procurement_group.py new file mode 100644 index 00000000..5409088d --- /dev/null +++ b/agreement_sale_condition_base/models/procurement_group.py @@ -0,0 +1,20 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class ProcurementGroup(models.Model): + _inherit = "procurement.group" + + agreement_id = fields.Many2one('agreement', readonly=True) + + @api.model_create_multi + @api.returns('self', lambda value: value.id) + def create(self, vals_list): + for values in vals_list: + so_id = values.get('sale_id') + if so_id: + agreement = self.env['sale.order'].browse(so_id).agreement_id + values['agreement_id'] = agreement.id + res = super().create(vals_list) + return res diff --git a/agreement_sale_condition_base/models/res_partner.py b/agreement_sale_condition_base/models/res_partner.py new file mode 100644 index 00000000..3b1eac63 --- /dev/null +++ b/agreement_sale_condition_base/models/res_partner.py @@ -0,0 +1,21 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + default_agreement_type_id = fields.Many2one( + comodel_name='agreement.type', + string='Default Agreement Type' + ) + customer_agreement_settings_id = fields.Many2one( + comodel_name='agreement', + domain=[('is_customer_requirement', '=', True)], + string="Agreed customer preferences" + ) + customer_agreements = fields.One2many( + comodel_name='agreement', inverse_name='partner_id', + string="All agreements" + ) diff --git a/agreement_sale_condition_base/models/sale_order.py b/agreement_sale_condition_base/models/sale_order.py new file mode 100644 index 00000000..51bfb4e1 --- /dev/null +++ b/agreement_sale_condition_base/models/sale_order.py @@ -0,0 +1,123 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + agreement_type_id = fields.Many2one( + 'agreement.type', + string='Agreement Type', + required=True, + copy=True, + default=lambda s: s.env['agreement.type'].search([], limit=1), + ) + agreement_id = fields.Many2one( + domain=[ + ('is_template', '=', False), + ('is_customer_requirement', '=', False), + ], + readonly=True, + ) + + @api.onchange('agreement_type_id') + def onchange_agreement_type(self): + self._update_agreement() + + @api.onchange('partner_id') + def onchange_partner_for_agreement(self): + if self.partner_id.default_agreement_type_id: + partner_agreement_type = self.partner_id.default_agreement_type_id + if self.agreement_type_id != partner_agreement_type: + self.agreement_type_id = partner_agreement_type + self._update_agreement() + + def _prepare_sale_agreement_values(self): + return { + 'name': self.agreement_type_id.name, + 'partner_id': self.partner_id.id, + 'agreement_type_id': self.agreement_type_id.id, + 'is_template': False, + 'is_customer_requirement': False, + } + + def _update_agreement(self): + if not self.partner_id: + return + if not self.agreement_id and self.partner_id: + self.agreement_id = self.env['agreement'].create( + self._prepare_sale_agreement_values() + ) + agreement_template = self.agreement_type_id.default_agreement_id + agreement_values = {} + if agreement_template: + if not agreement_template.is_template: + _logger.warn( + 'The default agreement of the agreement type %s ' + 'is not a template agreement', + self.agreement_type_id) + else: + agreement_values = \ + agreement_template.template_prepare_agreement_values( + self + ) + self.agreement_id.write(agreement_values) + + @api.model + def create(self, vals): + rec = super().create(vals) + if rec.agreement_id and not rec.agreement_id.code: + rec.agreement_id.code = rec.name + return rec + + @api.multi + def unlink(self): + agreements = self.mapped('agreement_id') + res = super().unlink() + agreements.sudo().unlink() + return res + + @api.multi + def _action_confirm(self): + self.filtered(lambda r: not r.agreement_id)._update_agreement() + result = super()._action_confirm() + self._apply_agreement() + return result + + @api.multi + def copy_data(self, default=None): + if default is None: + default = {} + if 'agreement_id' not in default and self.agreement_id: + new_agreement = self.agreement_id.copy() + default['agreement_id'] = new_agreement.id + return super().copy_data(default=default) + + def _apply_agreement(self): + # Nothing to do for now on the SO itself + return + + @api.multi + def _prepare_invoice(self): + values = super()._prepare_invoice() + values['agreement_id'] = self.agreement_id.id + return values + + +class SaleOrderLine(models.Model): + _inherit = 'sale.order.line' + + def _prepare_procurement_values(self, group_id=False): + agreement = self.order_id.agreement_id + values = super()._prepare_procurement_values(group_id=group_id) + values['agreement_id'] = agreement.id + # if agreement.delivery_date: + # values['date_planned'] = agreement.delivery_date - timedelta( + # days=self.order_id.company_id.security_lead + # ) + return values diff --git a/agreement_sale_condition_base/readme/CONTRIBUTORS.rst b/agreement_sale_condition_base/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..8785f72c --- /dev/null +++ b/agreement_sale_condition_base/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Alexandre Fayolle diff --git a/agreement_sale_condition_base/readme/DESCRIPTION.rst b/agreement_sale_condition_base/readme/DESCRIPTION.rst new file mode 100644 index 00000000..7f07724f --- /dev/null +++ b/agreement_sale_condition_base/readme/DESCRIPTION.rst @@ -0,0 +1,24 @@ +This module builds on the agreement and agreement_sale modules. It defines a +model for agreement parameter values. The intent is that additional modules can +add fields on an agreement which are m2o on to parameter values, and possibly +the allowed values for a given field could depend on the value of another one. + +An agreement type can be linked to a template agreement. When the agreement +type is set on a sale order, a new agreement record is created using the +template agreement as a basis. This agreement specific to the sale order can be +customized by the sales man. + +A preferred agreement type can be set on on a customer, and some preferences on the +partner. These preferences are defined in a special type of agreement. The +intent is that additional modules can add fields on an agreement which are m2o +on agreement parameter values. The values of customer preferences are meant to +be used in combination of the value of the template agreement of the agreement +type when the sale order is confirmed. + +When the sale order is confirmed, the agreement of the sale order is propagated +to the procurement group of the sale order. This can be used by extension +modules to customize the generation of the stock moves and pickings to match +the agreement. + +In a similar fashion, when an invoice is created from the sale order, the +agreement of the sale order, the agreement is propagated to the invoice. diff --git a/agreement_sale_condition_base/security/ir.model.access.csv b/agreement_sale_condition_base/security/ir.model.access.csv new file mode 100644 index 00000000..e1a139eb --- /dev/null +++ b/agreement_sale_condition_base/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_agreement_type,agrrement.type,model_agreement_type,,1,0,0,0 +access_agreement_parameter_value,agreement.parameter.value,model_agreement_parameter_value,,1,0,0,0 diff --git a/agreement_sale_condition_base/static/description/index.html b/agreement_sale_condition_base/static/description/index.html new file mode 100644 index 00000000..f00a2505 --- /dev/null +++ b/agreement_sale_condition_base/static/description/index.html @@ -0,0 +1,440 @@ + + + + + + +Agreement Sale Conditions + + + +
+

Agreement Sale Conditions

+ + +

Alpha License: AGPL-3 OCA/contract Translate me on Weblate Try me on Runbot

+

This module builds on the agreement and agreement_sale modules. It defines a +model for agreement parameter values. The intent is that additional modules can +add fields on an agreement which are m2o on to parameter values, and possibly +the allowed values for a given field could depend on the value of another one.

+

An agreement type can be linked to a template agreement. When the agreement +type is set on a sale order, a new agreement record is created using the +template agreement as a basis. This agreement specific to the sale order can be +customized by the sales man.

+

When the sale order is confirmed, the agreement of the sale order is propagated +to the procurement group of the sale order. This can be used by extension +modules to customize the generation of the stock moves and pickings to match +the agreement.

+

In a similar fashion, when an invoice is created from the sale order, the +agreement of the sale order, the agreement is propagated to the invoice.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

gurneyalex

+

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/agreement_sale_condition_base/tests/__init__.py b/agreement_sale_condition_base/tests/__init__.py new file mode 100644 index 00000000..cf654842 --- /dev/null +++ b/agreement_sale_condition_base/tests/__init__.py @@ -0,0 +1 @@ +from . import test_agreement_propagation diff --git a/agreement_sale_condition_base/tests/test_agreement_propagation.py b/agreement_sale_condition_base/tests/test_agreement_propagation.py new file mode 100644 index 00000000..b0256097 --- /dev/null +++ b/agreement_sale_condition_base/tests/test_agreement_propagation.py @@ -0,0 +1,75 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo.tests import common + + +class TestAgreementPropagation(common.SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.agreement_type = cls.env['agreement.type'].create( + {'code': 'TST', 'name': 'test'} + ) + cls.agreement = cls.env['agreement'].create( + {'name': 'Test Agreement Template', + 'code': 'TST', + 'is_template': 1, + 'agreement_type_id': cls.agreement_type.id, + } + ) + cls.product = cls.env['product.product'].create( + {'name': 'test product', + 'type': 'consu', + 'uom_id': 1, + } + ) + cls.partner = cls.env['res.partner'].create( + { + 'name': 'Test Customer', + 'customer': True, + } + ) + cls.sale = cls.env['sale.order'].create( + { + 'partner_id': cls.partner.id, + 'agreement_type_id': cls.agreement_type.id, + 'order_line': [ + (0, 0, { + 'product_id': cls.product.id, + 'product_uom': cls.product.uom_id.id, + 'product_uom_qty': 1, + 'price_unit': 10, + } + ) + ], + } + ) + + def test_default_agreement(self): + self.assertEqual(self.agreement_type.default_agreement_id, + self.agreement) + + def test_sale_order_confirm(self): + sale = self.sale + sale.onchange_agreement_type() + self.assertEqual( + sale.agreement_id.agreement_type_id, + sale.agreement_type_id + ) + self.assertNotEqual( + sale.agreement_id, + sale.agreement_type_id.default_agreement_id + ) + self.assertEqual( + sale.agreement_id.partner_id, + sale.partner_id + ) + self.assertTrue(sale.agreement_id.is_sale_agreement) + + def test_agreement_propagation_to_procurement_group(self): + sale = self.sale + sale.action_confirm() + self.assertEqual( + sale.agreement_id, + sale.procurement_group_id.agreement_id + ) diff --git a/agreement_sale_condition_base/tools.py b/agreement_sale_condition_base/tools.py new file mode 100644 index 00000000..c851a6e4 --- /dev/null +++ b/agreement_sale_condition_base/tools.py @@ -0,0 +1,10 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +def _default_sequence(self): + maxrec = self.search([], order='sequence desc', limit=1) + if maxrec: + return maxrec.sequence + 10 + else: + return 0 diff --git a/agreement_sale_condition_base/views/agreement.xml b/agreement_sale_condition_base/views/agreement.xml new file mode 100644 index 00000000..ff201b49 --- /dev/null +++ b/agreement_sale_condition_base/views/agreement.xml @@ -0,0 +1,216 @@ + + + + agreement.tree (template version) + agreement + + + + + + + + + + + + agreement.form (template version) + agreement + + +
+
+ +
+ + + + + + + + + + + + + + + + + + +
+
+
+ + + agreement.tree (customer preferences) + agreement + + + + + + + + + + + + agreement.form (customer preferences) + agreement + + +
+
+ +
+ + + + + + + + + + + + + + + + +
+
+
+ + + + + agreement.form (sale order version) + agreement + + + + 1 + + + 1 + + + 1 + + + + + + + + + + + + + + + 1 + + + 1 + + + + + + + + + + + + agreement.tree (sale order version) + agreement + + + + 1 + + + 1 + + + 1 + + + + + + Agreement Templates + agreement + tree,form + [('is_template', '=', 1)] + {"default_is_template": 1} + + + + + form + + + + + + + tree + + + + + + Customer Requirements + agreement + tree,form + [("is_customer_requirement", "=", 1)] + {"default_is_customer_requirement": 1} + + + + + form + + + + + + + tree + + + + + + + + + +
diff --git a/agreement_sale_condition_base/views/agreement_parameter_value.xml b/agreement_sale_condition_base/views/agreement_parameter_value.xml new file mode 100644 index 00000000..e90a325a --- /dev/null +++ b/agreement_sale_condition_base/views/agreement_parameter_value.xml @@ -0,0 +1,44 @@ + + + + + agreement.parameter.value.search + agreement.parameter.value + + + + + + + + + + + + + + agreement.parameter.value.tree + agreement.parameter.value + + + + + + + + + + + + Agreement Parameter Values + agreement.parameter.value + tree + + + + + + diff --git a/agreement_sale_condition_base/views/agreement_type.xml b/agreement_sale_condition_base/views/agreement_type.xml new file mode 100644 index 00000000..81bf7a3f --- /dev/null +++ b/agreement_sale_condition_base/views/agreement_type.xml @@ -0,0 +1,52 @@ + + + + + agreement.type.form + agreement.type + + + + + + + + + + + + + + + + + agreement.type.tree + agreement.type + + + + + + + + + + + + + agreement.type.search + agreement.type + + + + + + + + + + + diff --git a/agreement_sale_condition_base/views/procurement_group.xml b/agreement_sale_condition_base/views/procurement_group.xml new file mode 100644 index 00000000..2dbb5ce4 --- /dev/null +++ b/agreement_sale_condition_base/views/procurement_group.xml @@ -0,0 +1,13 @@ + + + + procurement.group.form + procurement.group + + + + + + + + diff --git a/agreement_sale_condition_base/views/res_partner.xml b/agreement_sale_condition_base/views/res_partner.xml new file mode 100644 index 00000000..e54fc5f4 --- /dev/null +++ b/agreement_sale_condition_base/views/res_partner.xml @@ -0,0 +1,21 @@ + + + + res.partner + + + + + + + + + + + + + + + + diff --git a/agreement_sale_condition_base/views/sale_order.xml b/agreement_sale_condition_base/views/sale_order.xml new file mode 100644 index 00000000..baa5d161 --- /dev/null +++ b/agreement_sale_condition_base/views/sale_order.xml @@ -0,0 +1,17 @@ + + + + sale.order.agreement.form.view + sale.order + + + + + + + + + + + +