diff --git a/HISTORY.rst b/HISTORY.rst index 22980c8bfa4..feac511fb70 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -22,6 +22,8 @@ latest (unreleased) **Features and Improvements** * Add a second user on CRM leads +* Ghosts products and indicative sales quotes: have placeholder products on + sale orders, and have an intermediate state on sales quotations. **Bugfixes** diff --git a/odoo/local-src/specific_sale/__manifest__.py b/odoo/local-src/specific_sale/__manifest__.py index eb713cbfb07..692d9642bd7 100644 --- a/odoo/local-src/specific_sale/__manifest__.py +++ b/odoo/local-src/specific_sale/__manifest__.py @@ -10,6 +10,10 @@ "website": "http://www.camptocamp.com", "license": "GPL-3 or any later version", "category": "Sale", - "data": ['views/sale_order_crm.xml'], + "data": [ + 'views/product_views.xml', + 'views/sale_order_crm.xml', + 'data/res_groups_data.xml', + ], 'installable': True, } diff --git a/odoo/local-src/specific_sale/data/res_groups_data.xml b/odoo/local-src/specific_sale/data/res_groups_data.xml new file mode 100644 index 00000000000..57d869a8d7a --- /dev/null +++ b/odoo/local-src/specific_sale/data/res_groups_data.xml @@ -0,0 +1,17 @@ + + + + + Roctool Engineering Managers + + + + Roctool Process Managers + + + + Roctool System Managers + + + + diff --git a/odoo/local-src/specific_sale/models/__init__.py b/odoo/local-src/specific_sale/models/__init__.py index 853aea6caba..f9cfe4e024a 100644 --- a/odoo/local-src/specific_sale/models/__init__.py +++ b/odoo/local-src/specific_sale/models/__init__.py @@ -3,4 +3,6 @@ # Copyright 2017 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import sale_order \ No newline at end of file +from . import product +from . import sale_order +from . import mail_compose_message diff --git a/odoo/local-src/specific_sale/models/mail_compose_message.py b/odoo/local-src/specific_sale/models/mail_compose_message.py new file mode 100644 index 00000000000..c11bf14444c --- /dev/null +++ b/odoo/local-src/specific_sale/models/mail_compose_message.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Author: Denis Leemann +# Copyright 2017 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models, api + + +class MailComposeMessage(models.TransientModel): + _inherit = 'mail.compose.message' + + @api.multi + def onchange_template_id(self, + template_id, + composition_mode, + model, + res_id): + values = super(MailComposeMessage, self).onchange_template_id( + template_id, composition_mode, model, res_id + ) + + if model != 'sale.order': + return values + + order = self.env['sale.order'].browse(res_id) + if not order.sales_condition: + return values + + # xmlids of email.template in which we join the sales condition + # document + sale_condition_xmlids = ( + 'sale.email_template_edi_sale', + ) + condition_template_ids = [] + for xmlid in sale_condition_xmlids: + template = self.env.ref(xmlid, raise_if_not_found=False) + condition_template_ids.append(template.id) + + # sending a mail quote + if template_id not in condition_template_ids: + return values + + attachment = self.env['ir.attachment'].search( + [('res_model', '=', 'sale.order'), + ('res_field', '=', 'sales_condition'), + ('res_id', '=', order.id), + ], + limit=1, + ) + fname = order.sales_condition_filename + # Replicate what's done in + # addons/mail_template/wizard/mail_compose_message.py + # The attachment should be cleaned by odoo later + data_attach = { + 'name': fname, + 'datas': attachment.datas, + 'datas_fname': fname, + 'res_model': 'mail.compose.message', + 'res_id': 0, + 'type': 'binary', + } + new_attachment = self.env['ir.attachment'].create(data_attach) + value = values['value'] + if 'attachment_ids' in value: + # add the new attachment to the existing command created + # by the super onchange + for cmd in value['attachment_ids']: + if cmd[0] == 6: + ids = cmd[2] + ids.append(new_attachment.id) + else: + raise Exception('unhandled') + else: + value['attachment_id'] = [(6, 0, new_attachment.ids)] + + return values diff --git a/odoo/local-src/specific_sale/models/product.py b/odoo/local-src/specific_sale/models/product.py new file mode 100644 index 00000000000..c9727bcd6c4 --- /dev/null +++ b/odoo/local-src/specific_sale/models/product.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Author: Denis Leemann +# Copyright 2017 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models, fields + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + is_ghost = fields.Boolean( + string='Ghost Product', + ) diff --git a/odoo/local-src/specific_sale/models/sale_order.py b/odoo/local-src/specific_sale/models/sale_order.py index e39b1c44752..d8d18b6e80f 100644 --- a/odoo/local-src/specific_sale/models/sale_order.py +++ b/odoo/local-src/specific_sale/models/sale_order.py @@ -3,7 +3,8 @@ # Copyright 2017 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import models, fields, api +from odoo import models, fields, api, _ +from odoo.exceptions import UserError class SaleOrder(models.Model): @@ -16,9 +17,49 @@ class SaleOrder(models.Model): project_market_id = fields.Many2one(comodel_name='project.market', required=True) + engineering_validation_id = fields.Many2one( + 'res.users', + string='Engineering Validation', + track_visibility=True, + copy=False, + readonly=True, + ) + system_validation_id = fields.Many2one( + 'res.users', + string='System Validation', + track_visibility=True, + copy=False, + readonly=True, + ) + process_validation_id = fields.Many2one( + 'res.users', + string='Process Validation', + track_visibility=True, + copy=False, + readonly=True, + ) + sales_condition = fields.Binary( + string='Sales Condition', + required=True, + attachment=True, + states={'draft': [('required', False)]} + ) + sales_condition_filename = fields.Char() + + @api.model + def _setup_fields(self, partial): + super(SaleOrder, self)._setup_fields(partial) + new_selection = [] + for state, name in self._fields['state'].selection: + new_selection.append((state, name)) + if state == 'draft': + new_selection.append(('final_quote', _('Final Quote'))) + self._fields['state'].selection = new_selection + def _generate_acc_name(self, use_existing_one=None): - """ - generate an analytic account name according to the following structure: + """ Generate analytic account name + + According to the following structure: 123ABCXXYYZZ with 123: number autoincrement (use Odoo sequence) ABC: customer.ref field @@ -79,3 +120,57 @@ def onchange_order_line(self): } option_lines.append((0, 0, data)) self.options = option_lines + + @api.multi + def _check_ghost(self): + for so in self: + ghost_prd = self.order_line.search_read( + [('product_id.is_ghost', '=', True), + ('order_id', '=', self.id)]) + # ghost_prd allowed only on draft + if ghost_prd: + raise UserError(_( + 'Ghost product is allowed only on draft Sale Orders.')) + + @api.multi + def _check_sales_condition(self): + for so in self: + if not self.sales_condition: + raise UserError(_( + 'You need to attach Sales Condition.')) + + @api.multi + def _check_validators(self): + if not (self.engineering_validation_id and + self.system_validation_id and + self.process_validation_id): + raise UserError(_('The Sale Order needs to be reviewed.')) + + def write(self, vals): + # from ' draft you can switch only to 'final_quote' + if (self.state == 'draft' and + vals.get('state', 'final_quote') != 'final_quote'): + raise UserError( + 'A Draft Sale Order can only step to "final_quote" ') + if vals.get('state', 'draft') != 'draft': + self._check_ghost() + self._check_sales_condition() + self._check_validators() + return super(SaleOrder, self).write(vals) + + def action_validate_eng(self): + self.engineering_validation_id = self.env.context['uid'] + + def action_validate_sys(self): + self.system_validation_id = self.env.context['uid'] + + def action_validate_pro(self): + self.process_validation_id = self.env.context['uid'] + + @api.multi + def action_confirm(self): + for order in self: + if order.state == 'draft': + order.state = 'final_quote' + else: + order.action_confirm() diff --git a/odoo/local-src/specific_sale/views/product_views.xml b/odoo/local-src/specific_sale/views/product_views.xml new file mode 100644 index 00000000000..1b9c643d92d --- /dev/null +++ b/odoo/local-src/specific_sale/views/product_views.xml @@ -0,0 +1,32 @@ + + + + + product.ghost + product.template + + +
+
+ +
+
+
+
+ + + product.template.ghost.form + product.template + + +
+
+ +
+
+
+
+ +
diff --git a/odoo/local-src/specific_sale/views/sale_order_crm.xml b/odoo/local-src/specific_sale/views/sale_order_crm.xml index 2cd415169d5..7adaa27b266 100644 --- a/odoo/local-src/specific_sale/views/sale_order_crm.xml +++ b/odoo/local-src/specific_sale/views/sale_order_crm.xml @@ -6,16 +6,48 @@ + +
+ +
+ + + + + + + + + + -
+ diff --git a/odoo/migration.yml b/odoo/migration.yml index ab23428501d..8948c52245c 100644 --- a/odoo/migration.yml +++ b/odoo/migration.yml @@ -125,3 +125,4 @@ migration: addons: upgrade: - specific_crm + - specific_sale