[MOV] website_quote: reorganize addon and split models according to guidelines

This commit is contained in:
Denis Vermylen (dve)
2016-08-05 15:25:26 +02:00
committed by Thibault Delavallée
parent 4c8a5b164a
commit 1fe106ef7a
13 changed files with 263 additions and 235 deletions
+3
View File
@@ -1,2 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import controllers
import models
+8 -5
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Online Proposals',
'category': 'Website',
@@ -11,12 +13,13 @@ Odoo Sale Quote Roller
""",
'depends': ['website', 'sale', 'mail', 'payment', 'website_portal_sale', 'website_mail'],
'data': [
'quotation_report.xml',
'views/website_quotation.xml',
'views/website_quotation_backend.xml',
'views/report_saleorder.xml',
'views/report_quotation.xml',
'data/website_quotation_data.xml',
'report/sale_order_reports.xml',
'report/sale_order_templates.xml',
'report/website_quote_templates.xml',
'views/sale_order_views.xml',
'views/sale_quote_views.xml',
'views/website_quote_templates.xml',
'security/ir.model.access.csv',
],
'demo': [
@@ -1 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import main
+6 -1
View File
@@ -1 +1,6 @@
import order
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import sale_order
import sale_quote
import product_template
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import osv, fields
class product_template(osv.Model):
_inherit = "product.template"
_columns = {
'website_description': fields.html('Description for the website'), # hack, if website_sale is not installed
'quote_description': fields.html('Description for the quote'),
}
@@ -13,100 +13,6 @@ from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
from openerp.tools.translate import _, html_translate
class sale_quote_template(osv.osv):
_name = "sale.quote.template"
_description = "Sale Quotation Template"
_columns = {
'name': fields.char('Quotation Template', required=True),
'website_description': fields.html('Description', translate=html_translate, sanitize=False),
'quote_line': fields.one2many('sale.quote.line', 'quote_id', 'Quotation Template Lines', copy=True),
'note': fields.text('Terms and conditions'),
'options': fields.one2many('sale.quote.option', 'template_id', 'Optional Products Lines', copy=True),
'number_of_days': fields.integer('Quotation Duration', help='Number of days for the validity date computation of the quotation'),
'require_payment': fields.selection([
(0, 'Not mandatory on website quote validation'),
(1, 'Immediate after website order validation'),
(2, 'Immediate after website order validation and save a token'),
], 'Payment', help="Require immediate payment by the customer when validating the order from the website quote"),
'mail_template_id': fields.many2one('mail.template', 'Confirmation Mail', help="This e-mail template will be sent on confirmation. Leave empty to send nothing.")
}
def open_template(self, cr, uid, quote_id, context=None):
return {
'type': 'ir.actions.act_url',
'target': 'self',
'url': '/quote/template/%d' % quote_id[0]
}
class sale_quote_line(osv.osv):
_name = "sale.quote.line"
_description = "Quotation Template Lines"
_columns = {
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sale quote lines."),
'quote_id': fields.many2one('sale.quote.template', 'Quotation Template Reference', required=True, ondelete='cascade', select=True),
'name': fields.text('Description', required=True, translate=True),
'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True),
'layout_category_id': fields.many2one('sale.layout_category', string='Section'),
'website_description': fields.related('product_id', 'product_tmpl_id', 'quote_description', string='Line Description', type='html', translate=True),
'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Discount')),
'product_uom_qty': fields.float('Quantity', required=True, digits_compute= dp.get_precision('Product UoS')),
'product_uom_id': fields.many2one('product.uom', 'Unit of Measure ', required=True),
}
_order = 'sequence, id'
_defaults = {
'product_uom_qty': 1,
'discount': 0.0,
'sequence': 10,
}
def on_change_product_id(self, cr, uid, ids, product, uom_id=None, context=None):
vals, domain = {}, []
product_obj = self.pool.get('product.product').browse(cr, uid, product, context=context)
name = product_obj.name
if product_obj.description_sale:
name += '\n' + product_obj.description_sale
vals.update({
'price_unit': product_obj.lst_price,
'product_uom_id': product_obj.uom_id.id,
'website_description': product_obj and (product_obj.quote_description or product_obj.website_description) or '',
'name': name,
'product_uom_id': uom_id or product_obj.uom_id.id,
})
uom_obj = self.pool.get('product.uom')
if vals['product_uom_id'] != product_obj.uom_id.id:
selected_uom = uom_obj.browse(cr, uid, vals['product_uom_id'], context=context)
new_price = uom_obj._compute_price(cr, uid, [product_obj.uom_id.id], vals['price_unit'], selected_uom)
vals['price_unit'] = new_price
if not uom_id:
domain = {'product_uom_id': [('category_id', '=', product_obj.uom_id.category_id.id)]}
return {'value': vals, 'domain': domain}
def product_uom_change(self, cr, uid, ids, product, uom_id, context=None):
context = context or {}
if not uom_id:
return {'value': {'price_unit': 0.0, 'uom_id': False}}
return self.on_change_product_id(cr, uid, ids, product, uom_id=uom_id, context=context)
def _inject_quote_description(self, cr, uid, values, context=None):
values = dict(values or {})
if not values.get('website_description') and values.get('product_id'):
product = self.pool['product.product'].browse(cr, uid, values['product_id'], context=context)
values['website_description'] = product.quote_description or product.website_description or ''
return values
def create(self, cr, uid, values, context=None):
values = self._inject_quote_description(cr, uid, values, context)
ret = super(sale_quote_line, self).create(cr, uid, values, context=context)
# hack because create don t make the job for a related field
if values.get('website_description'):
self.write(cr, uid, ret, {'website_description': values['website_description']}, context=context)
return ret
def write(self, cr, uid, ids, values, context=None):
values = self._inject_quote_description(cr, uid, values, context)
return super(sale_quote_line, self).write(cr, uid, ids, values, context=context)
class sale_order_line(osv.osv):
_inherit = "sale.order.line"
_description = "Sales Order Line"
@@ -321,74 +227,6 @@ class sale_order(osv.osv):
else:
return 'form'
class sale_quote_option(osv.osv):
_name = "sale.quote.option"
_description = "Quotation Option"
_columns = {
'template_id': fields.many2one('sale.quote.template', 'Quotation Template Reference', ondelete='cascade', select=True, required=True),
'name': fields.text('Description', required=True, translate=True),
'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True),
'layout_category_id': fields.many2one('sale.layout_category', string='Section'),
'website_description': fields.html('Option Description', translate=html_translate, sanitize=False),
'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Discount')),
'uom_id': fields.many2one('product.uom', 'Unit of Measure ', required=True),
'quantity': fields.float('Quantity', required=True, digits_compute= dp.get_precision('Product UoS')),
}
_defaults = {
'quantity': 1,
}
@api.onchange('product_id')
def _onchange_product_id(self):
if not self.product_id:
return
product = self.product_id
self.price_unit = product.list_price
self.website_description = product.product_tmpl_id.quote_description
self.name = product.name
self.uom_id = product.uom_id
domain = {'uom_id': [('category_id', '=', self.product_id.uom_id.category_id.id)]}
return {'domain': domain}
@api.onchange('uom_id')
def _onchange_product_uom(self):
if not self.product_id:
return
if not self.uom_id:
self.price_unit = 0.0
return
if self.uom_id.id != self.product_id.uom_id.id:
new_price = self.product_id.uom_id._compute_price(self.price_unit, self.uom_id)
self.price_unit = new_price
def on_change_product_id(self, cr, uid, ids, product, uom_id=None, context=None):
vals, domain = {}, []
product_obj = self.pool.get('product.product').browse(cr, uid, product, context=context)
name = product_obj.name
if product_obj.description_sale:
name += '\n' + product_obj.description_sale
vals.update({
'price_unit': product_obj.list_price,
'website_description': product_obj.product_tmpl_id.quote_description,
'name': name,
'uom_id': uom_id or product_obj.uom_id.id,
})
uom_obj = self.pool.get('product.uom')
if vals['uom_id'] != product_obj.uom_id.id:
selected_uom = uom_obj.browse(cr, uid, vals['uom_id'], context=context)
new_price = uom_obj._compute_price(cr, uid, [product_obj.uom_id.id],
vals['price_unit'], selected_uom)
vals['price_unit'] = new_price
if not uom_id:
domain = {'uom_id': [('category_id', '=', product_obj.uom_id.category_id.id)]}
return {'value': vals, 'domain': domain}
def product_uom_change(self, cr, uid, ids, product, uom_id, context=None):
if not uom_id:
return {'value': {'price_unit': 0.0, 'uom_id': False}}
return self.on_change_product_id(cr, uid, ids, product, uom_id=uom_id, context=context)
class sale_order_option(osv.osv):
_name = "sale.order.option"
@@ -492,12 +330,3 @@ class sale_order_option(osv.osv):
self.env['sale.order.line']._compute_tax_id()
self.env['sale.order.option'].write({'line_id': line})
return {'type':'ir.actions.client', 'tag':'reload'}
class product_template(osv.Model):
_inherit = "product.template"
_columns = {
'website_description': fields.html('Description for the website', sanitize=False, translate=html_translate), # hack, if website_sale is not installed
'quote_description': fields.html('Description for the quote', sanitize=False, translate=html_translate),
}
+171
View File
@@ -0,0 +1,171 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import api
from openerp.osv import osv, fields
import openerp.addons.decimal_precision as dp
from openerp.tools.translate import html_translate
class sale_quote_template(osv.osv):
_name = "sale.quote.template"
_description = "Sale Quotation Template"
_columns = {
'name': fields.char('Quotation Template', required=True),
'website_description': fields.html('Description', translate=html_translate, sanitize=False),
'quote_line': fields.one2many('sale.quote.line', 'quote_id', 'Quotation Template Lines', copy=True),
'note': fields.text('Terms and conditions'),
'options': fields.one2many('sale.quote.option', 'template_id', 'Optional Products Lines', copy=True),
'number_of_days': fields.integer('Quotation Duration', help='Number of days for the validity date computation of the quotation'),
'require_payment': fields.selection([
(0, 'Not mandatory on website quote validation'),
(1, 'Immediate after website order validation'),
(2, 'Immediate after website order validation and save a token'),
], 'Payment', help="Require immediate payment by the customer when validating the order from the website quote"),
'mail_template_id': fields.many2one('mail.template', 'Confirmation Mail', help="This e-mail template will be sent on confirmation. Leave empty to send nothing.")
}
def open_template(self, cr, uid, quote_id, context=None):
return {
'type': 'ir.actions.act_url',
'target': 'self',
'url': '/quote/template/%d' % quote_id[0]
}
class sale_quote_line(osv.osv):
_name = "sale.quote.line"
_description = "Quotation Template Lines"
_columns = {
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sale quote lines."),
'quote_id': fields.many2one('sale.quote.template', 'Quotation Template Reference', required=True, ondelete='cascade', select=True),
'name': fields.text('Description', required=True, translate=True),
'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True),
'layout_category_id': fields.many2one('sale.layout_category', string='Section'),
'website_description': fields.related('product_id', 'product_tmpl_id', 'quote_description', string='Line Description', type='html', translate=True),
'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Discount')),
'product_uom_qty': fields.float('Quantity', required=True, digits_compute= dp.get_precision('Product UoS')),
'product_uom_id': fields.many2one('product.uom', 'Unit of Measure ', required=True),
}
_order = 'sequence, id'
_defaults = {
'product_uom_qty': 1,
'discount': 0.0,
'sequence': 10,
}
def on_change_product_id(self, cr, uid, ids, product, uom_id=None, context=None):
vals, domain = {}, []
product_obj = self.pool.get('product.product').browse(cr, uid, product, context=context)
name = product_obj.name
if product_obj.description_sale:
name += '\n' + product_obj.description_sale
vals.update({
'price_unit': product_obj.lst_price,
'product_uom_id': product_obj.uom_id.id,
'website_description': product_obj and (product_obj.quote_description or product_obj.website_description) or '',
'name': name,
'product_uom_id': uom_id or product_obj.uom_id.id,
})
uom_obj = self.pool.get('product.uom')
if vals['product_uom_id'] != product_obj.uom_id.id:
selected_uom = uom_obj.browse(cr, uid, vals['product_uom_id'], context=context)
new_price = uom_obj._compute_price(cr, uid, [product_obj.uom_id.id], vals['price_unit'], selected_uom)
vals['price_unit'] = new_price
if not uom_id:
domain = {'product_uom_id': [('category_id', '=', product_obj.uom_id.category_id.id)]}
return {'value': vals, 'domain': domain}
def product_uom_change(self, cr, uid, ids, product, uom_id, context=None):
context = context or {}
if not uom_id:
return {'value': {'price_unit': 0.0, 'uom_id': False}}
return self.on_change_product_id(cr, uid, ids, product, uom_id=uom_id, context=context)
def _inject_quote_description(self, cr, uid, values, context=None):
values = dict(values or {})
if not values.get('website_description') and values.get('product_id'):
product = self.pool['product.product'].browse(cr, uid, values['product_id'], context=context)
values['website_description'] = product.quote_description or product.website_description or ''
return values
def create(self, cr, uid, values, context=None):
values = self._inject_quote_description(cr, uid, values, context)
ret = super(sale_quote_line, self).create(cr, uid, values, context=context)
# hack because create don t make the job for a related field
if values.get('website_description'):
self.write(cr, uid, ret, {'website_description': values['website_description']}, context=context)
return ret
def write(self, cr, uid, ids, values, context=None):
values = self._inject_quote_description(cr, uid, values, context)
return super(sale_quote_line, self).write(cr, uid, ids, values, context=context)
class sale_quote_option(osv.osv):
_name = "sale.quote.option"
_description = "Quotation Option"
_columns = {
'template_id': fields.many2one('sale.quote.template', 'Quotation Template Reference', ondelete='cascade', select=True, required=True),
'name': fields.text('Description', required=True, translate=True),
'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True),
'layout_category_id': fields.many2one('sale.layout_category', string='Section'),
'website_description': fields.html('Option Description', translate=html_translate, sanitize=False),
'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Discount')),
'uom_id': fields.many2one('product.uom', 'Unit of Measure ', required=True),
'quantity': fields.float('Quantity', required=True, digits_compute= dp.get_precision('Product UoS')),
}
_defaults = {
'quantity': 1,
}
@api.onchange('product_id')
def _onchange_product_id(self):
if not self.product_id:
return
product = self.product_id
self.price_unit = product.list_price
self.website_description = product.product_tmpl_id.quote_description
self.name = product.name
self.uom_id = product.uom_id
domain = {'uom_id': [('category_id', '=', self.product_id.uom_id.category_id.id)]}
return {'domain': domain}
@api.onchange('uom_id')
def _onchange_product_uom(self):
if not self.product_id:
return
if not self.uom_id:
self.price_unit = 0.0
return
if self.uom_id.id != self.product_id.uom_id.id:
new_price = self.product_id.uom_id._compute_price(self.price_unit, self.uom_id)
self.price_unit = new_price
def on_change_product_id(self, cr, uid, ids, product, uom_id=None, context=None):
vals, domain = {}, []
product_obj = self.pool.get('product.product').browse(cr, uid, product, context=context)
name = product_obj.name
if product_obj.description_sale:
name += '\n' + product_obj.description_sale
vals.update({
'price_unit': product_obj.list_price,
'website_description': product_obj.product_tmpl_id.quote_description,
'name': name,
'uom_id': uom_id or product_obj.uom_id.id,
})
uom_obj = self.pool.get('product.uom')
if vals['uom_id'] != product_obj.uom_id.id:
selected_uom = uom_obj.browse(cr, uid, vals['uom_id'], context=context)
new_price = uom_obj._compute_price(cr, uid, [product_obj.uom_id.id],
vals['price_unit'], selected_uom)
vals['price_unit'] = new_price
if not uom_id:
domain = {'uom_id': [('category_id', '=', product_obj.uom_id.category_id.id)]}
return {'value': vals, 'domain': domain}
def product_uom_change(self, cr, uid, ids, product, uom_id, context=None):
if not uom_id:
return {'value': {'price_unit': 0.0, 'uom_id': False}}
return self.on_change_product_id(cr, uid, ids, product, uom_id=uom_id, context=context)
@@ -0,0 +1,60 @@
<?xml version="1.0"?>
<odoo>
<record model="ir.ui.view" id="sale_order_form_quote">
<field name="name">sale.order.form.payment</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//header/button[@name='action_confirm'][1]" position="after">
<button name="open_quotation" type="object"
class="o_open_quotation" string="Preview"
attrs="{'invisible': [('template_id', '=', False)]}"/>
</xpath>
<xpath expr="//page/field[@name='order_line']/.." position="after">
<page string="Suggested Products">
<field name="quote_viewed" invisible="1"/>
<field name="options">
<tree string="Sales Quotation Template Lines" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="product_id"/>
<field name="layout_category_id" groups="sale.group_sale_layout"/>
<field name="name"/>
<field name="quantity"/>
<field name="uom_id" groups="product.group_uom"/>
<field name="price_unit"/>
<field name="discount" groups="sale.group_discount_per_so_line"/>
<button name="button_add_to_order" class="oe_link" icon="fa-shopping-cart" string="Add to order lines" type="object"/>
<field name="website_description" invisible="1"/>
</tree>
</field>
</page>
</xpath>
<xpath expr="//field[@name='partner_shipping_id']" position="after">
<label for="template_id"/>
<div>
<field name="template_id" context="{'company_id': company_id}"
on_change="onchange_template_id(template_id, partner_id, fiscal_position_id, pricelist_id)"
options="{'no_create': True, 'no_open': True}"/>
</div>
</xpath>
<xpath expr="//field[@name='client_order_ref']" position="after">
<field name="require_payment" widget="radio"/>
<field name="website_description" invisible="1"/>
</xpath>
</field>
</record>
<record id="sale.action_quotations" model="ir.actions.act_window">
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Create a Quotation, the first step of a new sale.
</p><p>
Your next actions should flow efficiently: confirm the Quotation
to a Sale Order, then create the Invoice and collect the Payment.
</p><p>
Note that once a Quotation becomes a Sale Order, it will be moved
from the Quotations list to the Sales Order list.
</p>
</field>
</record>
</odoo>
@@ -1,50 +1,6 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="sale_order_form_quote">
<field name="name">sale.order.form.payment</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//header/button[@name='action_confirm'][1]" position="after">
<button name="open_quotation" type="object"
class="o_open_quotation" string="Preview"
attrs="{'invisible': [('template_id', '=', False)]}"/>
</xpath>
<xpath expr="//page/field[@name='order_line']/.." position="after">
<page string="Suggested Products">
<field name="quote_viewed" invisible="1"/>
<field name="options">
<tree string="Sales Quotation Template Lines" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="product_id"/>
<field name="layout_category_id" groups="sale.group_sale_layout"/>
<field name="name"/>
<field name="quantity"/>
<field name="uom_id" groups="product.group_uom"/>
<field name="price_unit"/>
<field name="discount" groups="sale.group_discount_per_so_line"/>
<button name="button_add_to_order" class="oe_link" icon="fa-shopping-cart" string="Add to order lines" type="object"/>
<field name="website_description" invisible="1"/>
</tree>
</field>
</page>
</xpath>
<xpath expr="//field[@name='partner_shipping_id']" position="after">
<label for="template_id"/>
<div>
<field name="template_id" context="{'company_id': company_id}"
on_change="onchange_template_id(template_id, partner_id, fiscal_position_id, pricelist_id)"
options="{'no_create': True, 'no_open': True}"/>
</div>
</xpath>
<xpath expr="//field[@name='client_order_ref']" position="after">
<field name="require_payment" widget="radio"/>
<field name="website_description" invisible="1"/>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="view_sale_quote_template_form">
<field name="name">sale.quote.template.form</field>
<field name="model">sale.quote.template</field>
@@ -176,20 +132,6 @@
</field>
</record>
<record id="sale.action_quotations" model="ir.actions.act_window">
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Create a Quotation, the first step of a new sale.
</p><p>
Your next actions should flow efficiently: confirm the Quotation
to a Sale Order, then create the Invoice and collect the Payment.
</p><p>
Note that once a Quotation becomes a Sale Order, it will be moved
from the Quotations list to the Sales Order list.
</p>
</field>
</record>
<menuitem action="action_sale_quotation_template" id="menu_sale_quote_template" parent="sales_team.menu_sales_config" sequence="1" name="Quotation Templates" groups="sales_team.group_sale_salesman,sales_team.group_sale_manager"/>
</data>
</openerp>