diff --git a/addons/website_quotation/__init__.py b/addons/website_quotation/__init__.py
new file mode 100644
index 00000000000..9f86759e32b
--- /dev/null
+++ b/addons/website_quotation/__init__.py
@@ -0,0 +1,2 @@
+import controllers
+import models
diff --git a/addons/website_quotation/__openerp__.py b/addons/website_quotation/__openerp__.py
new file mode 100644
index 00000000000..92547bb7cbe
--- /dev/null
+++ b/addons/website_quotation/__openerp__.py
@@ -0,0 +1,24 @@
+{
+ 'name': 'Online Quotation',
+ 'category': 'Website',
+ 'summary': 'Send Live Quotation',
+ 'version': '1.0',
+ 'description': """
+OpenERP Sale Quote Roller
+==================
+
+ """,
+ 'author': 'OpenERP SA',
+ 'depends': ['website','sale', 'portal_sale', 'mail'],
+ 'data': [
+ 'views/website_quotation.xml',
+ 'views/website_quotation_backend.xml',
+ 'data/website_quotation_data.xml',
+ 'security/ir.model.access.csv',
+ ],
+ 'demo': [
+ 'data/website_quotation_demo.xml'
+ ],
+ 'qweb': ['static/src/xml/*.xml'],
+ 'installable': True,
+}
diff --git a/addons/website_quotation/controllers/__init__.py b/addons/website_quotation/controllers/__init__.py
new file mode 100644
index 00000000000..e11f9ba81bb
--- /dev/null
+++ b/addons/website_quotation/controllers/__init__.py
@@ -0,0 +1,3 @@
+import main
+
+# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/website_quotation/controllers/main.py b/addons/website_quotation/controllers/main.py
new file mode 100644
index 00000000000..f7dd3a26e54
--- /dev/null
+++ b/addons/website_quotation/controllers/main.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-Today OpenERP SA (
).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp import SUPERUSER_ID
+from openerp.addons.web import http
+from openerp.addons.web.http import request
+from openerp.addons.website.models import website
+import werkzeug
+import datetime
+
+from openerp.tools.translate import _
+
+class sale_quote(http.Controller):
+
+ @http.route([
+ "/quote/",
+ "/quote//"
+ ], type='http', auth="public", website=True)
+ def view(self, order_id, token=None, message=False, **post):
+ # use SUPERUSER_ID allow to access/view order for public user
+ order = request.registry.get('sale.order').browse(request.cr, token and SUPERUSER_ID or request.uid, order_id)
+ if token:
+ assert token == order.access_token, 'Access denied!'
+ body=_('Quotation viewed by customer')
+ self.message_post(body, order_id, type='comment')
+ # TODO: if not order.template_id: return to the URL of the portal view of SO
+ values = {
+ 'quotation': order,
+ 'message': message,
+ 'new_post' : request.httprequest.session.get('new_post',False),
+ 'option': self._check_option_len(order),
+ 'date_diff': order.validity_date and (datetime.datetime.now() > datetime.datetime.strptime(order.validity_date , '%Y-%m-%d')) or False,
+ 'salesperson' : False if token else True
+ }
+ return request.website.render('website_quotation.so_quotation', values)
+
+ def _check_option_len(self, order):
+ for option in order.options:
+ if not option.line_id:
+ return True
+ return False
+
+ @http.route(['/quote/accept'], type='json', auth="public", website=True)
+ def accept(self, order_id=None, token=None, signer=None, sign=None, **post):
+ order_obj = request.registry.get('sale.order')
+ order = order_obj.browse(request.cr, SUPERUSER_ID, order_id)
+ assert token == order.access_token, 'Access denied, wrong token!'
+ error = {}
+ if not signer: error['signer'] = 'missing'
+ if not sign: error['sign'] = 'missing'
+ if not error:
+ attachment = {
+ 'name': 'sign.png',
+ 'datas':sign,
+ 'datas_fname': 'sign.png',
+ 'res_model': 'sale.order',
+ 'res_id': order_id,
+ }
+ request.registry['ir.attachment'].create(request.cr, request.uid, attachment, context=request.context)
+ order_obj.write(request.cr, request.uid, [order_id], {'signer_name':signer,'state': 'manual'})
+ return [error]
+
+ @http.route(['/quote///decline'], type='http', auth="public", website=True)
+ def decline(self, order_id, token, **post):
+ message = post.get('decline_message')
+ request.registry.get('sale.order').write(request.cr, request.uid, [order_id], {'state': 'cancel'})
+ if message:
+ self.message_post(message, order_id, type='comment', subtype='mt_comment')
+ return werkzeug.utils.redirect("/quote/%s/%s?message=2" % (order_id, token))
+
+ @http.route(['/quote///post'], type='http', auth="public", website=True)
+ def post(self, order_id, token, **post):
+ # use SUPERUSER_ID allow to access/view order for public user
+ order_obj = request.registry.get('sale.order')
+ order = order_obj.browse(request.cr, SUPERUSER_ID, order_id)
+ message = post.get('comment')
+ assert token == order.access_token, 'Access denied, wrong token!'
+ if message:
+ self.message_post(message, order_id, type='comment', subtype='mt_comment')
+ request.httprequest.session['new_post'] = True
+ return werkzeug.utils.redirect("/quote/%s/%s?message=1" % (order_id, token))
+
+ def message_post(self , message, order_id, type='comment', subtype=False):
+ request.session.body = message
+ cr, uid, context = request.cr, request.uid, request.context
+ user = request.registry['res.users'].browse(cr, SUPERUSER_ID, uid, context=context)
+ if 'body' in request.session and request.session.body:
+ request.registry.get('sale.order').message_post(cr, SUPERUSER_ID, order_id,
+ body=request.session.body,
+ type=type,
+ subtype=subtype,
+ author_id=user.partner_id.id,
+ context=context,
+ )
+ request.session.body = False
+ return True
+
+ @http.route(['/quote/update_line'], type='json', auth="public", website=True)
+ def update(self, line_id=None, remove=False, unlink=False, order_id=None, token=None, **post):
+ order = request.registry.get('sale.order').browse(request.cr, SUPERUSER_ID, int(order_id))
+ assert token == order.access_token, 'Access denied, wrong token!'
+ if unlink:
+ request.registry.get('sale.order.line').unlink(request.cr, SUPERUSER_ID, [int(line_id)], context=request.context)
+ return False
+ val = self._update_order_line(line_id=int(line_id), number=(remove and -1 or 1))
+ return [str(val), str(order.amount_total)]
+
+ def _update_order_line(self, line_id, number):
+ order_line_obj = request.registry.get('sale.order.line')
+ order_line_val = order_line_obj.read(request.cr, SUPERUSER_ID, [line_id], [], context=request.context)[0]
+ quantity = order_line_val['product_uom_qty'] + number
+ order_line_obj.write(request.cr, SUPERUSER_ID, [line_id], {'product_uom_qty': (quantity)}, context=request.context)
+ return quantity
+
+ @http.route(["/template/"], type='http', auth="user", website=True)
+ def template_view(self, quote, **post):
+ values = {
+ 'template': quote,
+ }
+ return request.website.render('website_quotation.so_template', values)
+
+
+ @http.route(["/quote/add_line///"], type='http', auth="public", website=True)
+ def add(self, option_id, order_id, token, **post):
+ vals = {}
+ order = request.registry.get('sale.order').browse(request.cr, SUPERUSER_ID, order_id)
+ assert token == order.access_token, 'Access denied, wrong token!'
+ option_obj = request.registry.get('sale.option.line')
+ option = option_obj.browse(request.cr, SUPERUSER_ID, option_id)
+ vals.update({
+ 'price_unit': option.price_unit,
+ 'website_description': option.website_description,
+ 'name': option.name,
+ 'order_id': order.id,
+ 'product_id' : option.product_id.id,
+ 'product_uom_qty': option.quantity,
+ 'product_uom_id': option.uom_id.id,
+ 'discount': option.discount,
+ })
+ line = request.registry.get('sale.order.line').create(request.cr, SUPERUSER_ID, vals, context=request.context)
+ option_obj.write(request.cr, SUPERUSER_ID, [option.id], {'line_id': line}, context=request.context)
+ return werkzeug.utils.redirect("/quote/%s/%s#pricing" % (order.id, token))
+
+
diff --git a/addons/website_quotation/data/website_quotation_data.xml b/addons/website_quotation/data/website_quotation_data.xml
new file mode 100644
index 00000000000..0b539140a95
--- /dev/null
+++ b/addons/website_quotation/data/website_quotation_data.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+ Hello ${object.partner_id.name},
+
+ Here is your ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'} from ${object.company_id.name}:
+
+
+ You can access this document and pay online via our Customer Portal:
+
+ View ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'}
+
+ ]]>
+
+
+
+
diff --git a/addons/website_quotation/data/website_quotation_demo.xml b/addons/website_quotation/data/website_quotation_demo.xml
new file mode 100644
index 00000000000..0f50a152c99
--- /dev/null
+++ b/addons/website_quotation/data/website_quotation_demo.xml
@@ -0,0 +1,709 @@
+
+
+
+
+ Functional Training
+
+
+ 50000.0
+ 750000.0
+ service
+
+
+ Learn directly from our team and network of OpenERP experts. Choose from the available training sessions for a better functional understanding of OpenERP
+
+
+
+
+ QF11
+
+
+
+ Technical Training
+
+
+ 50000.0
+ 0.0
+ service
+
+
+ Learn directly from our team and network of OpenERP experts. Choose from the available training sessions for a better technical understanding of OpenERP
+
+
+
+
+ QF12
+
+
+
+ Advanced CRM Functional
+
+
+ 50000.0
+ 750000.0
+ service
+
+
+ Learn directly from our team and network of OpenERP experts. Choose from the available training sessions for a better functional understanding of OpenERP
+
+
+
+
+ QF13
+
+
+
+ Advanced Account Functional
+
+
+ 50000.0
+ 750000.0
+ service
+
+
+ Learn directly from our team and network of OpenERP experts. Choose from the available training sessions for a better functional understanding of OpenERP
+
+
+
+
+ QF14
+
+
+
+ Partnership Contract
+
+
+
+
+
+
+
+ Our partnership offer includes all you need to
+ grow your business and deliver quality services
+ with the OpenERP Partner Program.
+
+ It includes discounts on OpenERP
+ Enterprise, technical and/or functional
+ trainings,
+ support services,
+ marketing documents, access to
+ the partner portal, rights to
+ use the trademark, sales support
+ from a dedicated account manager.
+
+
+
+
+
+
+
+
+
+
+
A Valuable Product
+
+
+
+ Deliver strong value added services as you can
+ rely on a leading open source software, with
+ the support of the publisher.
+
+
Grow with your existing customer base
+ by continuously proposing new modules.
+
+
+
+
+
+
+
+
A Strong Demand
+
+
+
+ Enjoy the traction of the fastest growing
+ management software in the world.
+
+ Benefit from the growing customer demand
+ and our OpenERP brand.
+
+
+
+
+
+
+
+
High Margins
+
+
+
+ Get high billing rates as you deliver a
+ highly valuable software.
+
+ Grow by developing a recurring
+ revenue flow from OpenERP
+ Enterprise's commission system.
+
+
+
+
+
+
+
+
+ Enterprise commissions
+
+ As an official partner, you benefit from OpenERP Enterprise
+ commissions, according to your partner grade, as well as
+ dedicated services restricted to partners like the technical
+ support.
+
+
+
+ | Grade |
+ Your Commission |
+
+ | Ready |
+ |
+
+ | Silver |
+ |
+
+ | Gold |
+ |
+
+
+
+
+
+ A Dedicated Account Manager
+
+ We will assign you a dedicated account manager, an
+ experienced sales person, to help you develop your
+ OpenERP business. The account manager helps you get
+ leads, close deals, gives you feedback and best
+ practices, delivers sales training and is your direct
+ point of contact for any request you may have.
+
+
+
+
+
+
+
Get access to our experts
+
+
+
+ For an extra fee, partners can get access to OpenERP's
+ core developers and functional experts. This can help
+ you succeed in delivering more complex or bigger
+ projects by getting the support of highly experienced
+ consultants on demand.
+
+
+
+

+
+
+
+
+
+
+
+
Official certified partner
+
+
+
+ OpenERP promotes its partners through various ways:
+ publication on our website, official communication,
+ publication of your success stories, etc. As soon as
+ you become an OpenERP partner and have followed the
+ official trainings, you will be visible on the partner
+ directory listing.
+
+
+
+

+
+
+
+
+
+
+
+
Access to the Lead
+
+
+
+ Every year, we redirect more than 100,000 customer
+ requests to our official partners. These are prospects
+ that filled a form on the OpenERP website and wanted to
+ use OpenERP. The right partner to fulfill the customer
+ request is selected based on the customer localization
+ (nearest partner) and the grade of the partner.
+
+
+
+

+
+
+
+
+
+ Benefit from the OpenERP branding
+
+ Every year, we redirect more than 100,000 customer
+ requests to our official partners. These are prospects
+ that filled a form on the OpenERP website and wanted to
+ use OpenERP. The right partner to fulfill the customer
+ request is selected based on the customer localization
+ (nearest partner) and the grade of the partner.
+
+
+
+
+ Test developments automatically
+
+
+

+
+
+
+ Save time in your implementation project by having your
+ developments tested automatically by our automated test
+ servers. At every code commit, you get a full OpenERP
+ instance that you can try out online. When this
+ instance is deployed, your code is automatically put
+ through our 2000+ automated unit tests.
+
+ Our automated testing server software is called Runbot,
+ and you can try it out here: http://runbot.openerp.com.
+ A dedicated runbot server is available for every
+ partner.
+
+
+
+
+
+
+
+
+
+ Fuctional Training
+
+ 1
+ 12950.00
+
+
+ Online Training + Certification
+
+
+
+
+
These courses feature the same high quality course content found in our traditional classroom trainings, supplemented with modular sessions and cloud-based labs. Many of our online learning courses also include dozens of recorded webinars and live sessions by our senior instructors. At the end of the training, you can pass the OpenERP Certification exam in one of the 5000+ Pearson VUE test centers worldwide.
+
+
+
Your advantages
+
+ - Modular approach applied to the learning method
+ - New interactive learning experience
+ - Lower training budget for the same quality courses
+ - Better comfort to facilitate your learning process
+
+
+
+
Structure of the Training
+
+
+

+
+
+
There are three components to the training
+
+ - Videos with detailed demos
+ - Hands-on exercises and their solutions
+ - Live Q&A sessions with a trainer
+
+
+
+
+
+
+
+
+
+ Technical Training
+
+ 1
+ 00.00
+
+
+
+
+
+
+
Course Summary
+
+
+
This course is dedicated to developers who need to grasp knowledge of the business applications development process. This course is for new developers or for IT professionals eager to learn more about technical aspects.
+
+
+
+
+
+
+
What you will learn?
+
+
+
+
+
Day 1
+
Introduction to Javascript
+
+
+ - Hello World
+ - Variables & Operators
+ - Dive into Strings
+ - Functions
+ - Loops
+ - Arrays
+
+
+
+
+
+
+
+
Day 2
+
+
OpenERP Web Client
+
+
+ - Introduction to JQuery
+ - Advanced JQuery
+ - Underscore
+ - Introduction to QWeb
+ - Controlers and Views
+ - Bootstrap CSS
+ - Calling the ORM
+
+
+
+
+
+
+
+
Day 3
+
+
Building a Full Application
+
+
+
+ - Modules
+ - Python Objects
+ - Report Engine
+ - Workflows
+ - Training Center Module
+ - Integrated Help
+ - How to Debug
+
+
+
+
+
+
+
+
+
+
Requirements
+
+
+
+
Objectives:
+
+
Having attended this course, participants should be able to:
+
+
- Understand the development concepts and architecture;
+ - Install and administer your own server;
+ - Develop a new module for a particular application.
+
+
+
Our prices include:
+
+
- drinks and lunch;
+ - training material.
+
+
+
Requirements:
+
+
- Bring your own laptop.
+ - Participants are expected to have some knowledge in programming. A basic knowledge of the Python programming is recommended.
+ - Participants preferably have a functional knowledge of our software (see Functional Training).
+
+
+
To get more information, visit the OpenERP Official Website.
+
+
+
+
+
+
+
+
+
+ Advanced CRM Functional
+
+ 1
+
+ 9000.00
+ 10
+
+
+ Advanced CRM Functional
+
+
+
+
+
+
Objectives
+
+
+
Upon completion of the training, the participant will be able to:
+
+ - Install and administer OpenERP.
+ - Become an OpenERP Consultant.
+ - Do the GAP analysis of any Business Process.
+ - Understand the functional concepts, business processes byOpenERP.
+ - Operate/Work with OpenERP Smoothly on regular basis.
+ - Configure OpenERP using the standard modules.
+ - Change the look and feel from the front-end(GUI) rather than aneed of technical knowledge.
+
+
+
+
+
+
+
+
+
What you will learn?
+
+
+
+
+
Day 1
+
+
+ - Introduction
+ - Installation and Configuration
+ - Database management
+ - Module Installation
+ - Company Configuration and Multi Company Management
+ - Multi Language Management
+ - Fetchmail Configuration
+
+
+
+
+
+
+
Day 2
+
+
+ - Product Category & product Configuration with Order point
+ - Customers/Suppliers
+ - Pricelist and auto Segmentation
+ - Convert Lead to Opportunity & Customer management
+ - Schedule Phone Calls and Meetings
+ - Negotiation and Quotation Revisions
+ - Generate Sale Order & direct mail to customer
+
+
+
+
+
+
+
Day 3
+
+
+ - Warehouse Management Introduction
+ - Shop, Location & Warehouse Configuration
+ - Opening Stock and Physical Inventory
+ - Purchase Requisition and Purchase Order Management
+ - Partial and Full Shipment / Delivery
+ - Product Expiry and Warranty
+ - After Sales Service (Helpdesk)
+
+
+
+
+
+
+
+
+
Requirements
+
+
- Bring your own laptop.
+ - Participants are expected to have some knowledge in programming. A basic knowledge of the Python programming is recommended.
+ - Participants preferably have a functional knowledge of our software (see Functional Training).
+
+
+
To get more information, visit the OpenERP Official Website.
+
+
+
+
+
+
+
+
+
+ Functional Webinar
+
+ 1
+
+ 18000.00
+
+
+
+
+
+
+
Course Summary
+
+
+
+
This course is dedicated to developers who need to grasp knowledge of the business applications development process. This course is for new developers or for IT professionals eager to learn more about technical aspects.
+
+
+
+
+
+
+
+
What you will learn?
+
+
+
+
+
+
Day 1
+
+
Introduction to Javascript
+
+
+
+ - Hello World
+ - Variables & Operators
+ - Dive into Strings
+ - Functions
+ - Loops
+ - Arrays
+
+
+
+
+
+
+
+
+
Day 2
+
+
OpenERP Web Client
+
+
+
+ - Introduction to JQuery
+ - Advanced JQuery
+ - Underscore
+ - Introduction to QWeb
+ - Controlers and Views
+ - Bootstrap CSS
+ - Calling the ORM
+
+
+
+
+
+
+
+
+
Day 3
+
+
Building a Full Application
+
+
+
+ - Modules
+ - Python Objects
+ - Report Engine
+ - Workflows
+ - Training Center Module
+ - Integrated Help
+ - How to Debug
+
+
+
+
+
+
+
+
+
+
+
Requirements
+
+
+
+
Objectives:
+
+
Having attended this course, participants should be able to:
+
+
+ - Understand the development concepts and architecture;
+ - Install and administer your own server;
+ - Develop a new module for a particular application.
+
+
+
Our prices include:
+
+
+ - drinks and lunch;
+ - training material.
+
+
+
Requirements:
+
+
+ - Bring your own laptop.
+ - Participants are expected to have some knowledge in programming. A basic knowledge of the Python programming is recommended.
+ - Participants preferably have a functional knowledge of our software (see Functional Training).
+
+
+
To get more information, visit the OpenERP Official Website.
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_quotation/models/__init__.py b/addons/website_quotation/models/__init__.py
new file mode 100644
index 00000000000..49178edb61d
--- /dev/null
+++ b/addons/website_quotation/models/__init__.py
@@ -0,0 +1 @@
+import order
diff --git a/addons/website_quotation/models/order.py b/addons/website_quotation/models/order.py
new file mode 100644
index 00000000000..aa79fbd80c7
--- /dev/null
+++ b/addons/website_quotation/models/order.py
@@ -0,0 +1,194 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-Today OpenERP SA ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.osv import osv, fields
+import uuid
+import time
+import datetime
+
+class sale_quote_template(osv.osv):
+ _name = "sale.quote.template"
+ _description = "Sale Quotation Template"
+ _columns = {
+ 'name': fields.char('Quotation Template', size=256, required=True),
+ 'website_description': fields.html('Description'),
+ 'quote_line': fields.one2many('sale.quote.line', 'quote_id', 'Quote Template Lines'),
+ 'note': fields.text('Terms and conditions'),
+ 'options': fields.one2many('sale.option.line', 'temp_option_id', 'Optional Products Lines'),
+ 'number_of_days': fields.integer('Quotation Period Validity'),
+ }
+
+ def open_template(self, cr, uid, quote_id, context=None):
+ return {
+ 'type': 'ir.actions.act_url',
+ 'target': 'self',
+ 'url': '/template/%d' % quote_id[0]
+ }
+
+
+class sale_quote_line(osv.osv):
+ _name = "sale.quote.line"
+ _description = "Quotation Template Lines"
+ _columns = {
+ 'quote_id': fields.many2one('sale.quote.template', 'Quotation Template Reference', required=True, ondelete='cascade', select=True),
+ 'name': fields.text('Description', required=True),
+ 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
+ 'website_description': fields.html('Line Description'),
+ 'price_unit': fields.float('Unit Price', required=True),
+ 'product_uom_qty': fields.float('Quantity', required=True),
+ }
+ _defaults = {
+ 'product_uom_qty': 1,
+ }
+ def on_change_product_id(self, cr, uid, ids, product, context=None):
+ vals = {}
+ product_obj = self.pool.get('product.product').browse(cr, uid, product, context=context)
+ vals.update({
+ 'price_unit': product_obj.list_price,
+ 'website_description': product_obj.website_description,
+ 'name': product_obj.name,
+ })
+ return {'value': vals}
+
+
+class sale_order_line(osv.osv):
+ _inherit = "sale.order.line"
+ _description = "Sales Order Line"
+ _columns = {
+ 'website_description': fields.html('Line Description'),
+ 'option_line_id':fields.one2many('sale.option.line', 'line_id', 'Optional Products Lines'),
+ }
+ def product_id_change(self, cr, uid, ids, pricelist, product, qty=0, uom=False, qty_uos=0, uos=False, name='', partner_id=False, lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False, context=None):
+ res = super(sale_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty, uom, qty_uos, uos, name, partner_id, lang, update_tax, date_order, packaging, fiscal_position, flag, context)
+ if product:
+ desc = self.pool.get('product.product').browse(cr, uid, product, context).website_description
+ res.get('value').update({'website_description': desc})
+ return res
+
+
+class sale_order(osv.osv):
+ _inherit = 'sale.order'
+
+ def _get_total(self, cr, uid, ids, name, arg, context=None):
+ res = {}
+ for order in self.browse(cr, uid, ids, context=context):
+ total = 0.0
+ for line in order.order_line:
+ total += (line.product_uom_qty * line.price_unit)
+ res[order.id] = total
+ return res
+
+ _columns = {
+ 'access_token': fields.char('Security Token', size=256, required=True),
+ 'template_id': fields.many2one('sale.quote.template', 'Quote Template'),
+ 'website_description': fields.html('Description'),
+ 'options' : fields.one2many('sale.option.line', 'option_id', 'Optional Products Lines'),
+ 'signer_name': fields.char('Signer Name', size=256),
+ 'validity_date': fields.date('Validity Date'),
+ 'before_discount': fields.function(_get_total, string='Amount Before Discount', type="float")
+ }
+ _defaults = {
+ 'access_token': lambda self, cr, uid, ctx={}: str(uuid.uuid4())
+ }
+
+ def open_quotation(self, cr, uid, quote_id, context=None):
+ quote = self.browse(cr, uid, quote_id[0], context=context)
+ return {
+ 'type': 'ir.actions.act_url',
+ 'target': 'self',
+ 'url': '/quote/%s' % (quote.id)
+ }
+
+ def onchange_template_id(self, cr, uid, ids, template_id, context=None):
+ lines = []
+ quote_template = self.pool.get('sale.quote.template').browse(cr, uid, template_id, context=context)
+ for line in quote_template.quote_line:
+ lines.append((0, 0, {
+ 'name': line.name,
+ 'price_unit': line.price_unit,
+ 'product_uom_qty': line.product_uom_qty,
+ 'product_id': line.product_id.id,
+ 'product_uom_id': line.product_id.uom_id.id,
+ 'website_description': line.website_description,
+ 'state': 'draft',
+ }))
+ options = []
+ for option in quote_template.options:
+ options.append((0, 0, {
+ 'product_id': option.product_id.id,
+ 'name': option.name,
+ 'quantity': option.quantity,
+ 'uom_id': option.uom_id.id,
+ 'price_unit': option.price_unit,
+ 'discount': option.discount,
+ 'website_description': option.website_description,
+ }))
+ date = False
+ if quote_template.number_of_days > 0:
+ date = (datetime.datetime.now() + datetime.timedelta(quote_template.number_of_days)).strftime("%Y-%m-%d")
+ data = {'order_line': lines, 'website_description': quote_template.website_description, 'note': quote_template.note, 'options': options, 'validity_date': date}
+ return {'value': data}
+
+ def recommended_products(self, cr, uid, ids, context=None):
+ order_line = self.browse(cr, uid, ids[0], context=context).order_line
+ product_pool = self.pool.get('product.product')
+ products = []
+ for line in order_line:
+ products += line.product_id.product_tmpl_id.recommended_products(context=context)
+ return products
+
+
+class sale_option_line(osv.osv):
+ _name = "sale.option.line"
+ _description = "Sale Options"
+ _columns = {
+ 'option_id': fields.many2one('sale.order', 'Sale Order Reference', ondelete='cascade', select=True),
+ 'temp_option_id': fields.many2one('sale.quote.template', 'Quotation Template Reference', ondelete='cascade', select=True),
+ 'line_id': fields.many2one('sale.order.line', on_delete="set null"),
+ 'name': fields.text('Description', required=True),
+ 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
+ 'website_description': fields.html('Line Description'),
+ 'price_unit': fields.float('Unit Price', required=True),
+ 'discount': fields.float('Discount (%)'),
+ 'uom_id': fields.many2one('product.uom', 'Unit of Measure ', required=True),
+ 'quantity': fields.float('Quantity', required=True),
+ }
+
+ _defaults = {
+ 'quantity': 1,
+ }
+
+ def on_change_product_id(self, cr, uid, ids, product, context=None):
+ vals = {}
+ product_obj = self.pool.get('product.product').browse(cr, uid, product, context=context)
+ vals.update({
+ 'price_unit': product_obj.list_price,
+ 'website_description': product_obj.product_tmpl_id.website_description,
+ 'name': product_obj.name,
+ 'uom_id': product_obj.product_tmpl_id.uom_id.id,
+ })
+ return {'value': vals}
+
+class product_template(osv.Model):
+ _inherit = "product.template"
+ _columns = {
+ 'website_description': fields.html('Description for the website'),
+ }
diff --git a/addons/website_quotation/security/ir.model.access.csv b/addons/website_quotation/security/ir.model.access.csv
new file mode 100644
index 00000000000..c4c444c6965
--- /dev/null
+++ b/addons/website_quotation/security/ir.model.access.csv
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_sale_order_portal,sale.order.portal,model_sale_order,base.group_portal,1,1,1,0
+access_sale_order_public,sale.order.public,model_sale_order,base.group_public,1,1,1,0
+access_sale_order_line_public,sale.order.line.public,model_sale_order_line,base.group_public,1,1,1,0
+access_sale_options_line_public,sale.option.line.public,model_sale_option_line,base.group_public,1,1,1,0
diff --git a/addons/website_quotation/static/lib/jSignature/jSignature.min.js b/addons/website_quotation/static/lib/jSignature/jSignature.min.js
new file mode 100644
index 00000000000..3c5590659a8
--- /dev/null
+++ b/addons/website_quotation/static/lib/jSignature/jSignature.min.js
@@ -0,0 +1,77 @@
+/*
+
+jSignature v2 "2012-11-01T22:48" "commit ID 1c15dfafecc75925c3b7d529356a558b59220edb"
+Copyright (c) 2012 Willow Systems Corp http://willow-systems.com
+Copyright (c) 2010 Brinley Ang http://www.unbolt.net
+MIT License
+
+
+Simplify.js BSD
+(c) 2012, Vladimir Agafonkin
+mourner.github.com/simplify-js
+
+
+base64 encoder
+MIT, GPL
+http://phpjs.org/functions/base64_encode
++ original by: Tyler Akins (http://rumkin.com)
++ improved by: Bayron Guevara
++ improved by: Thunder.m
++ improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
++ bugfixed by: Pellentesque Malesuada
++ improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
++ improved by: Rafal Kukawski (http://kukawski.pl)
+
+
+jSignature v2 jSignature's Undo Button and undo functionality plugin
+
+
+jSignature v2 jSignature's custom "base30" format export and import plugins.
+
+
+jSignature v2 SVG export plugin.
+
+*/
+(function(){function q(b){for(var j,s=b.css("color"),g,b=b[0],c=!1;b&&!g&&!c;){try{j=$(b).css("background-color")}catch(d){j="transparent"}"transparent"!==j&&"rgba(0, 0, 0, 0)"!==j&&(g=j);c=b.body;b=b.parentNode}var b=/rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/,c=/#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/,a;j=void 0;(j=s.match(b))?a={r:parseInt(j[1],10),g:parseInt(j[2],10),b:parseInt(j[3],10)}:(j=s.match(c))&&(a={r:parseInt(j[1],16),g:parseInt(j[2],16),b:parseInt(j[3],16)});var f;g?
+(j=void 0,(j=g.match(b))?f={r:parseInt(j[1],10),g:parseInt(j[2],10),b:parseInt(j[3],10)}:(j=g.match(c))&&(f={r:parseInt(j[1],16),g:parseInt(j[2],16),b:parseInt(j[3],16)})):f=a?127