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