[IMP] Merge Trunk + Deisgn Improvements

bzr revid: fp@tinyerp.com-20140120205229-b9726jvb5tfmj3fc
This commit is contained in:
Fabien Pinckaers 2014-01-20 21:52:29 +01:00
commit cd3be26606
24 changed files with 1513 additions and 5 deletions

View File

@ -50,7 +50,7 @@ class mail_compose_message(osv.TransientModel):
res.update(
self.onchange_template_id(
cr, uid, [], context['default_template_id'], res.get('composition_mode'),
res.get('model'), res.get('res_id'), context=context
res.get('model'), res.get('res_id', context.get('active_id')), context=context
)['value']
)
return res
@ -113,7 +113,6 @@ class mail_compose_message(osv.TransientModel):
values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
else:
values = self.default_get(cr, uid, ['subject', 'body', 'email_from', 'email_to', 'email_cc', 'partner_to', 'reply_to', 'attachment_ids', 'mail_server_id'], context=context)
if values.get('body_html'):
values['body'] = values.pop('body_html')
return {'value': values}
@ -168,7 +167,6 @@ class mail_compose_message(osv.TransientModel):
# filter template values
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments', 'mail_server_id']
values = dict.fromkeys(res_ids, False)
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, context=context)
for res_id in res_ids:
res_id_values = dict((field, template_values[res_id][field]) for field in fields if template_values[res_id].get(field))

View File

@ -65,6 +65,8 @@ Example: 10% for retailers, promotion of 5 EUR on this product, etc."""),
help='This adds the \'Margin\' on sales order.\n'
'This gives the profitability by calculating the difference between the Unit Price and Cost Price.\n'
'-This installs the module sale_margin.'),
'module_website_quotation': fields.boolean("Allow online quotation.",
help='This adds the online quotation'),
'module_sale_journal': fields.boolean("Allow batch invoicing of delivery orders through journals",
help='Allows you to categorize your sales and deliveries (picking lists) between different journals, '
'and perform batch operations on journals.\n'

View File

@ -80,6 +80,10 @@
</group>
</group>
<div name="Sale Features" position="inside">
<div name="module_website_quotation">
<field name="module_website_quotation" class="oe_inline"/>
<label for="module_website_quotation"/>
</div>
<div name="module_sale_margin">
<field name="module_sale_margin" class="oe_inline"/>
<label for="module_sale_margin"/>

View File

@ -454,7 +454,7 @@
<div data-snippet-id="pricing" data-selector-children=".oe_structure, [data-oe-type=html]">
<div class="oe_snippet_thumbnail">
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_comparison.png"/>
<span class="oe_snippet_thumbnail_title">Comparisons</span>
<span class="oe_snippet_thumbnail_title">Comparisons</span>
</div>
<section class="oe_snippet_body">
<div class="container">

View File

@ -0,0 +1,2 @@
import controllers
import models

View File

@ -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','document'],
'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,
}

View File

@ -0,0 +1,3 @@
import main
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
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
class sale_quote(http.Controller):
@http.route(["/quote/<int:order_id>/<token>"], type='http', auth="public", website=True)
def view(self, order_id, token, **post):
# use SUPERUSER_ID allow to access/view order for public user
order = request.registry.get('sale.order').browse(request.cr, SUPERUSER_ID, order_id)
assert token == order.access_token, 'Access denied, wrong token!'
# TODO: if not order.template_id: return to the URL of the portal view of SO
values = {
'quotation': order,
'new_post' : request.httprequest.session.get('new_post',False)
}
return request.website.render('website_quotation.so_quotation', values)
@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!'
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 []
@http.route(['/quote/<int:order_id>/<token>/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)
return werkzeug.utils.redirect("/quote/%s/%s" % (order_id, token))
@http.route(['/quote/<int:order_id>/<token>/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)
request.httprequest.session['new_post'] = True
return werkzeug.utils.redirect("/quote/%s/%s" % (order_id, token))
def message_post(self , message, order_id):
request.session.body = message
cr, uid, context = request.cr, request.uid, request.context
if 'body' in request.session and request.session.body:
request.registry.get('sale.order').message_post(cr, uid, order_id,
body=request.session.body,
type='comment',
subtype='mt_comment',
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/<model('sale.quote.template'):quote>"], type='http', auth="public", 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/<int:option_id>/<int:order_id>/<token>"], 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,
'is_optional': True,
})
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" % (order.id, token))

View File

@ -0,0 +1,26 @@
<openerp>
<data>
<!--
Update Email template to send right quote url
-->
<record id="sale.email_template_edi_sale" model="email.template">
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Hello ${object.partner_id.name},</p>
<p>Here is your ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'} from ${object.company_id.name}: </p>
<p>
You can access this document and pay online via our Customer Portal:
</p>
<a style="display:block; width: 150px; height:20px; margin-left: 120px; color: #DDD; font-family: 'Lucida Grande', Helvetica, Arial, sans-serif; font-size: 13px; font-weight: bold; text-align: center; text-decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat no-repeat;"
href="${object.open_quotation()['url']}">View ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'}</a>
</div>
]]></field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,412 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="product_template_quote_1" model="product.template">
<field name="name">Functional Training</field>
<field name="categ_id" ref="product.product_category_5"/>
<field name="public_categ_id" ref="product.Computer_all_in_one"/>
<field name="standard_price">50000.0</field>
<field name="list_price">750000.0</field>
<field name="type">service</field>
<field name="uom_id" ref="product.product_uom_unit"/>
<field name="uom_po_id" ref="product.product_uom_unit"/>
<field name="description_sale">Learn directly from our team and network of OpenERP experts. Choose from the available training sessions for a better functional understanding of OpenERP</field>
</record>
<record id="product_product_quote_1" model="product.product">
<field name="product_tmpl_id" ref="product_template_quote_1"/>
<field name="default_code">QF11</field>
</record>
<record id="product_template_quote_2" model="product.template">
<field name="name">Technical Training</field>
<field name="categ_id" ref="product.product_category_5"/>
<field name="public_categ_id" ref="product.Computer_all_in_one"/>
<field name="standard_price">50000.0</field>
<field name="list_price">0.0</field>
<field name="type">service</field>
<field name="uom_id" ref="product.product_uom_unit"/>
<field name="uom_po_id" ref="product.product_uom_unit"/>
<field name="description_sale">Learn directly from our team and network of OpenERP experts. Choose from the available training sessions for a better technical understanding of OpenERP</field>
</record>
<record id="product_product_quote_2" model="product.product">
<field name="product_tmpl_id" ref="product_template_quote_2"/>
<field name="default_code">QF12</field>
</record>
<record id="product_template_quote_3" model="product.template">
<field name="name">Advanced CRM Functional</field>
<field name="categ_id" ref="product.product_category_5"/>
<field name="public_categ_id" ref="product.Computer_all_in_one"/>
<field name="standard_price">50000.0</field>
<field name="list_price">750000.0</field>
<field name="type">service</field>
<field name="uom_id" ref="product.product_uom_unit"/>
<field name="uom_po_id" ref="product.product_uom_unit"/>
<field name="description_sale">Learn directly from our team and network of OpenERP experts. Choose from the available training sessions for a better functional understanding of OpenERP</field>
</record>
<record id="product_product_quote_3" model="product.product">
<field name="product_tmpl_id" ref="product_template_quote_3"/>
<field name="default_code">QF13</field>
</record>
<record id="product_template_quote_4" model="product.template">
<field name="name">Advanced Account Functional </field>
<field name="categ_id" ref="product.product_category_6"/>
<field name="public_categ_id" ref="product.Computer_all_in_one"/>
<field name="standard_price">50000.0</field>
<field name="list_price">750000.0</field>
<field name="type">service</field>
<field name="uom_id" ref="product.product_uom_unit"/>
<field name="uom_po_id" ref="product.product_uom_unit"/>
<field name="description_sale">Learn directly from our team and network of OpenERP experts. Choose from the available training sessions for a better functional understanding of OpenERP</field>
</record>
<record id="product_product_quote_4" model="product.product">
<field name="product_tmpl_id" ref="product_template_quote_4"/>
<field name="default_code">QF14</field>
</record>
<record id="website_quote_template_1" model="sale.quote.template">
<field name="name">Partnership Contract</field>
<field name="website_description" type="xml">
<section data-snippet-id="title">
<h1 class="page-header">Our Partnership Offer</h1>
</section>
<section data-snippet-id="text-block">
<div class="row">
<div class="col-md-12">
<p>
Our partnership offer includes all you need to
grow your business and deliver quality services
with the OpenERP Partner Program.
</p><p>
It includes <strong>discounts on OpenERP
Enterprise</strong>, technical and/or functional
<strong>trainings</strong>,
<strong>support</strong> services,
<strong>marketing documents</strong>, access to
the <strong>partner portal</strong>, rights to
<strong>use the trademark</strong>, sales support
from a <strong>dedicated account manager</strong>.
</p>
</div>
</div>
</section>
<section data-snippet-id="pricing">
<div class="row mt32">
<div class="col-md-4">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">A Valuable Product</h3>
</div>
<div class="panel-body">
<p>
Deliver <strong>strong value added services</strong> as you can
rely on a leading open source software, with
the support of the publisher.
<p></p>
<strong>Grow with your existing customer base</strong>
by continuously proposing new modules.
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">A Strong Demand</h3>
</div>
<div class="panel-body">
<p>
Enjoy the traction of the <strong>fastest growing
management software</strong> in the world.
</p><p>
Benefit from the growing customer demand
and our OpenERP brand.
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">High Margins</h3>
</div>
<div class="panel-body">
<p>
<strong>Get high billing rates</strong> as you deliver a
highly valuable software.
</p><p>
Grow by developing a <strong>recurring
revenue flow</strong> from OpenERP
Enterprise's commission system.
</p>
</div>
</div>
</div>
</div>
</section>
<section data-snippet-id="text-block">
<h2>Enterprise commissions</h2>
<p>
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.
</p>
<table class="table table-hover">
<tr>
<th>Grade</th>
<th>Your Commission</th>
</tr><tr>
<td>Ready</td>
<td></td>
</tr><tr>
<td>Silver</td>
<td></td>
</tr><tr>
<td>Gold</td>
<td></td>
</tr>
</table>
</section>
<section data-snippet-id="text-block">
<h2>A Dedicated Account Manager</h2>
<p>
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.
</p>
</section>
<section data-snippet-id="text-block">
<div class="row">
<div class="col-md-12">
<h2>Get access to our experts</h2>
</div>
<div class="col-md-7">
<p>
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.
</p>
</div>
<div class="col-md-5">
<img src="/website_quotation/static/src/img/partner_icon_01.png"/>
</div>
</div>
</section>
<section data-snippet-id="text-block">
<div class="row">
<div class="col-md-12">
<h2>Official certified partner</h2>
</div>
<div class="col-md-7">
<p>
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.
</p>
</div>
<div class="col-md-5">
<img src="/website_quotation/static/src/img/openerp_gold_partner.png"/>
</div>
</div>
</section>
<section data-snippet-id="text-block">
<div class="row">
<div class="col-md-12">
<h2>Access to the Lead</h2>
</div>
<div class="col-md-7">
<p>
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.
</p>
</div>
<div class="col-md-5">
<img src="/website_quotation/static/src/img/partner_icon_02.png" class="img"/>
</div>
</div>
</section>
<section data-snippet-id="text-block">
<h2>Benefit from the OpenERP branding</h2>
<p>
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.
</p>
</section>
<section class="mb32" data-snippet-id="text-block">
<h2>Test developments automatically</h2>
<div class="row">
<div class="col-md-5">
<img src="/website_quotation/static/src/img/partner_sc_01.png" class="img shadow"/>
</div>
<div class="col-md-7">
<p class="mt23">
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.
</p><p>
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.
</p>
</div>
</div>
</section>
</field>
</record>
<record id="website_sale_order_line_1" model="sale.quote.line">
<field name="quote_id" ref="website_quote_template_1"/>
<field name="name">Fuctional Training</field>
<field name="product_id" ref="product_product_quote_1"/>
<field name="product_uom_qty">1</field>
<field name="price_unit">12950.00</field>
<field name="website_description" type="html">
<section data-snippet-id="title">
<h1>Online Training + Certification</h1>
</section>
<section data-snippet-id="text-image">
<div class="row">
<div class="col-md-12 mb32">
<p>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.</p>
</div>
<div class="col-md-offset-1">
<h3>Your advantages</h3>
<ul>
<li>Modular approach applied to the learning method</li>
<li>New interactive learning experience</li>
<li>Lower training budget for the same quality courses</li>
<li>Better comfort to facilitate your learning process</li>
</ul>
</div>
<div class="col-md-12">
<h2>Structure of the Training</h2>
</div>
<div class="col-md-5 col-md-offset-1 mt16 mb16">
<img class="img img-responsive" src="https://www.openerp.com/saas_master/static/site_new/img/layout/online_training.png"/>
</div>
<div class="col-md-6 mt32">
<h4><strong>There are three components to the training</strong></h4>
<ul>
<li>Videos with detailed demos</li>
<li>Hands-on exercises and their solutions</li>
<li>Live Q&amp;A sessions with a trainer</li>
</ul>
</div>
</div>
</section>
</field>
</record>
<record id="website_sale_order_line_2" model="sale.quote.line">
<field name="quote_id" ref="website_quote_template_1"/>
<field name="name">Technical Training</field>
<field name="product_id" ref="product_product_quote_2"/>
<field name="product_uom_qty">1</field>
<field name="price_unit">00.00</field>
<field name="website_description" type="html">
<section data-snippet-id="title">
<h1>Technical Training</h1>
</section>
<section id="whatsuit" data-snippet-id="text-block">
<div class="row">
<div class="col-md-12 mt32">
<h2>Technical Training</h2>
</div>
<div class="col-md-12 mb32">
<p>This course is dedicated to partners, integrators and developers who need to have a grasp of the OpenERP development process. This course is for new developers or for IT professionals eager to learn more about the OpenERP's technical aspects.
This 5-day training takes place in the Headquarters of OpenERP, in Grand-Rosière (Belgium).</p>
</div>
<div class="col-md-12">
<h2>Objectives</h2>
</div>
<div class="col-md-12 mb32">
<h3>Having attended this course, participants should be able to</h3>
<ul>
<li>Videos with detailed demos</li>
<li>Hands-on exercises and their solutions</li>
<li>Live Q&amp;A sessions with a trainer</li>
</ul>
</div>
<div class="col-md-12">
<h3>Structure of the Training</h3>
<h4>There are three components to the training:</h4>
<ul>
<li>Demo in class with the trainer</li>
<li>Hands-on exercises and their corresponding solutions</li>
<li>Q&amp;A sessions</li>
</ul>
</div>
</div>
</section>
</field>
</record>
<record id="website_sale_option_line_1" model="sale.option.line">
<field name="temp_option_id" ref="website_quote_template_1"/>
<field name="name">Advanced CRM Functional</field>
<field name="product_id" ref="product_product_quote_3"/>
<field name="product_uom_qty">1</field>
<field name="uom_id" ref="product.product_uom_unit"/>
<field name="price_unit">9000.00</field>
<field name="website_description" type="html">
<section data-snippet-id="title">
<h1>Advanced CRM Functional</h1>
</section>
<section data-snippet-id="text-image">
</section>
</field>
</record>
<record id="website_sale_option_line_2" model="sale.option.line">
<field name="temp_option_id" ref="website_quote_template_1"/>
<field name="name">Advanced Account Functional</field>
<field name="product_id" ref="product_product_quote_4"/>
<field name="product_uom_qty">1</field>
<field name="uom_id" ref="product.product_uom_unit"/>
<field name="price_unit">18000.00</field>
<field name="website_description" type="html">
<section data-snippet-id="title">
<h1>Advanced Account Functional</h1>
</section>
<section data-snippet-id="text-image">
</section>
</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1 @@
import order

View File

@ -0,0 +1,179 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import osv, fields
import uuid
import time
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'),
}
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'),
'is_optional': fields.boolean('Optional Product')
}
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'
_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),
}
_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/%s' % (quote.id, quote.access_token)
}
def _get_sale_order_line(self, cr, uid, template_id, context=None):
"""create order line from selected quote template line."""
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,
}))
data = {'order_line': lines, 'website_description': quote_template.website_description, 'note': quote_template.note, 'options': options}
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'),
}

View File

@ -0,0 +1,3 @@
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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sale_order_portal sale.order.portal model_sale_order base.group_portal 1 1 1 0
3 access_sale_order_public sale.order.public model_sale_order base.group_public 1 1 1 0

View File

@ -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 <http://www.opensource.org/licenses/mit-license.php>
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<Math.max.apply(null,[a.r,a.g,a.b])?{r:0,g:0,b:0}:{r:255,g:255,b:255}:{r:255,g:255,b:255};j=function(b){return"rgb("+[b.r,b.g,b.b].join(", ")+")"};a&&f?(b=Math.max.apply(null,[a.r,a.g,a.b]),a=Math.max.apply(null,[f.r,f.g,f.b]),a=Math.round(a+-0.75*(a-b)),a={r:a,g:a,b:a}):a?(a=Math.max.apply(null,[a.r,a.g,a.b]),b=1,127<a&&
(b=-1),a=Math.round(a+96*b),a={r:a,g:a,b:a}):a={r:191,g:191,b:191};return{color:s,"background-color":f?j(f):g,"decor-color":j(a)}}function m(b,j){this.x=b;this.y=j;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var s=function(b){return Math.round(b/Math.abs(b))};this.resizeTo=function(b){if(0===this.x&&0===this.y)this._length=0;else if(0===
this.x)this._length=b,this.y=b*s(this.y);else if(0===this.y)this._length=b,this.x=b*s(this.x);else{var j=Math.abs(this.y/this.x),a=Math.sqrt(Math.pow(b,2)/(1+Math.pow(j,2))),j=j*a;this._length=b;this.x=a*s(this.x);this.y=j*s(this.y)}return this};this.angleTo=function(b){var j=this.getLength()*b.getLength();return 0===j?0:Math.acos(Math.min(Math.max((this.x*b.x+this.y*b.y)/j,-1),1))/Math.PI}}function k(b,j){this.x=b;this.y=j;this.getVectorToCoordinates=function(b,j){return new m(b-this.x,j-this.y)};
this.getVectorFromCoordinates=function(b,j){return this.getVectorToCoordinates(b,j).reverse()};this.getVectorToPoint=function(b){return new m(b.x-this.x,b.y-this.y)};this.getVectorFromPoint=function(b){return this.getVectorToPoint(b).reverse()}}function t(b,j,a,g,c){this.data=b;this.context=j;if(b.length)for(var d=b.length,f,l,i=0;i<d;i++){f=b[i];l=f.x.length;a.call(j,f);for(var e=1;e<l;e++)g.call(j,f,e);c.call(j,f)}this.changed=function(){};this.startStrokeFn=a;this.addToStrokeFn=g;this.endStrokeFn=
c;this.inStroke=!1;this._stroke=this._lastPoint=null;this.startStroke=function(b){if(b&&"number"==typeof b.x&&"number"==typeof b.y){this._stroke={x:[b.x],y:[b.y]};this.data.push(this._stroke);this._lastPoint=b;this.inStroke=!0;var j=this._stroke,a=this.startStrokeFn,g=this.context;setTimeout(function(){a.call(g,j)},3);return b}return null};this.addToStroke=function(b){if(this.inStroke&&"number"===typeof b.x&&"number"===typeof b.y&&4<Math.abs(b.x-this._lastPoint.x)+Math.abs(b.y-this._lastPoint.y)){var j=
this._stroke.x.length;this._stroke.x.push(b.x);this._stroke.y.push(b.y);this._lastPoint=b;var a=this._stroke,g=this.addToStrokeFn,s=this.context;setTimeout(function(){g.call(s,a,j)},3);return b}return null};this.endStroke=function(){var b=this.inStroke;this.inStroke=!1;this._lastPoint=null;if(b){var j=this._stroke,a=this.endStrokeFn,g=this.context,s=this.changed;setTimeout(function(){a.call(g,j);s.call(g)},3);return!0}return null}}function r(b,j,a){var g=this.$parent=$(b),b=this.eventTokens={};this.events=
new v(this);var c=$.fn[e]("globalEvents"),d={width:"ratio",height:"ratio",sizeRatio:4,color:"#000","background-color":"#fff","decor-color":"#eee",lineWidth:0,minFatFingerCompensation:-10,showUndoButton:!1,data:[]};$.extend(d,q(g));j&&$.extend(d,j);this.settings=d;for(var f in a)a.hasOwnProperty(f)&&a[f].call(this,f);this.events.publish(e+".initializing");this.$controlbarUpper=$('<div style="padding:0 !important;margin:0 !important;width: 100% !important; height: 0 !important;margin-top:-1em !important;margin-bottom:1em !important;"></div>').appendTo(g);
this.isCanvasEmulator=!1;a=this.canvas=this.initializeCanvas(d);j=$(a);this.$controlbarLower=$('<div style="padding:0 !important;margin:0 !important;width: 100% !important; height: 0 !important;margin-top:-1.5em !important;margin-bottom:1.5em !important;"></div>').appendTo(g);this.canvasContext=a.getContext("2d");j.data(e+".this",this);g=(g=d.lineWidth)?g:Math.max(Math.round(a.width/400),2);d.lineWidth=g;this.lineCurveThreshold=3*d.lineWidth;d.cssclass&&""!=$.trim(d.cssclass)&&j.addClass(d.cssclass);
this.fatFingerCompensation=0;var g=function(b){var j,a,g=function(g){g=g.changedTouches&&0<g.changedTouches.length?g.changedTouches[0]:g;return new k(Math.round(g.pageX+j),Math.round(g.pageY+a)+b.fatFingerCompensation)},d=new y(750,function(){b.dataEngine.endStroke()});this.drawEndHandler=function(j){try{j.preventDefault()}catch(a){}d.clear();b.dataEngine.endStroke()};this.drawStartHandler=function(c){c.preventDefault();var s=$(b.canvas).offset();j=-1*s.left;a=-1*s.top;b.dataEngine.startStroke(g(c));
d.kick()};this.drawMoveHandler=function(j){j.preventDefault();b.dataEngine.inStroke&&(b.dataEngine.addToStroke(g(j)),d.kick())};return this}.call({},this),l=g.drawEndHandler,i=g.drawStartHandler,p=g.drawMoveHandler,h=this.canvas,j=$(h);this.isCanvasEmulator?(j.bind("mousemove."+e,p),j.bind("mouseup."+e,l),j.bind("mousedown."+e,i)):(h.ontouchstart=function(b){h.onmousedown=void 0;h.onmouseup=void 0;h.onmousemove=void 0;this.fatFingerCompensation=d.minFatFingerCompensation&&-3*d.lineWidth>d.minFatFingerCompensation?
-3*d.lineWidth:d.minFatFingerCompensation;i(b);h.ontouchend=l;h.ontouchstart=i;h.ontouchmove=p},h.onmousedown=function(b){h.ontouchstart=void 0;h.ontouchend=void 0;h.ontouchmove=void 0;i(b);h.onmousedown=i;h.onmouseup=l;h.onmousemove=p});b[e+".windowmouseup"]=c.subscribe(e+".windowmouseup",g.drawEndHandler);this.events.publish(e+".attachingEventHandlers");var n=this,b=d.width.toString(10),w=e;if("ratio"===b||"%"===b.split("")[b.length-1])this.eventTokens[w+".parentresized"]=c.subscribe(w+".parentresized",
function(b,j,a){return function(){var g=j.width();if(g!==a){for(var d in b)b.hasOwnProperty(d)&&(c.unsubscribe(b[d]),delete b[d]);var s=n.settings;n.$parent.children().remove();for(d in n)n.hasOwnProperty(d)&&delete n[d];d=s.data;var g=1*g/a,f=[],l,i,e,h,k,p;i=0;for(e=d.length;i<e;i++){p=d[i];l={x:[],y:[]};h=0;for(k=p.x.length;h<k;h++)l.x.push(p.x[h]*g),l.y.push(p.y[h]*g);f.push(l)}s.data=f;j[w](s)}}}(this.eventTokens,this.$parent,this.$parent.width(),1*this.canvas.width/this.canvas.height));this.resetCanvas(d.data);
this.events.publish(e+".initialized");return this}var e="jSignature",y=function(b,j){var a;this.kick=function(){clearTimeout(a);a=setTimeout(j,b)};this.clear=function(){clearTimeout(a)};return this},v=function(b){this.topics={};this.context=b?b:this;this.publish=function(b,a,g,d){if(this.topics[b]){var c=this.topics[b],f=Array.prototype.slice.call(arguments,1),l=[],i,e,h,p;e=0;for(h=c.length;e<h;e++)p=c[e],i=p[0],p[1]&&(p[0]=function(){},l.push(e)),i.apply(this.context,f);e=0;for(h=l.length;e<h;e++)c.splice(l[e],
1)}};this.subscribe=function(b,a,g){this.topics[b]?this.topics[b].push([a,g]):this.topics[b]=[[a,g]];return{topic:b,callback:a}};this.unsubscribe=function(b){if(this.topics[b.topic])for(var a=this.topics[b.topic],g=0,d=a.length;g<d;g++)a[g][0]===b.callback&&a.splice(g,1)}},z=function(b){var a=this.canvasContext,d=b.x[0],b=b.y[0],g=this.settings.lineWidth,c=a.fillStyle;a.fillStyle=a.strokeStyle;a.fillRect(d+g/-2,b+g/-2,g,g);a.fillStyle=c},u=function(b,a){var d=new k(b.x[a-1],b.y[a-1]),g=new k(b.x[a],
b.y[a]),c=d.getVectorToPoint(g);if(1<a){var f=new k(b.x[a-2],b.y[a-2]),l=f.getVectorToPoint(d),i;if(l.getLength()>this.lineCurveThreshold){i=2<a?(new k(b.x[a-3],b.y[a-3])).getVectorToPoint(f):new m(0,0);var e=0.35*l.getLength(),h=l.angleTo(i.reverse()),p=c.angleTo(l.reverse());i=(new m(i.x+l.x,i.y+l.y)).resizeTo(Math.max(0.05,h)*e);var n=(new m(l.x+c.x,l.y+c.y)).reverse().resizeTo(Math.max(0.05,p)*e),l=this.canvasContext,e=f.x,p=f.y,h=d.x,w=d.y,A=f.x+i.x,f=f.y+i.y;i=d.x+n.x;n=d.y+n.y;l.beginPath();
l.moveTo(e,p);l.bezierCurveTo(A,f,i,n,h,w);l.stroke()}}c.getLength()<=this.lineCurveThreshold&&(c=this.canvasContext,f=d.x,d=d.y,i=g.x,g=g.y,c.beginPath(),c.moveTo(f,d),c.lineTo(i,g),c.stroke())},x=function(b){var a=b.x.length-1;if(0<a){var d=new k(b.x[a],b.y[a]),g=new k(b.x[a-1],b.y[a-1]),c=g.getVectorToPoint(d);if(c.getLength()>this.lineCurveThreshold){if(1<a){var b=(new k(b.x[a-2],b.y[a-2])).getVectorToPoint(g),f=(new m(b.x+c.x,b.y+c.y)).resizeTo(c.getLength()/2),c=this.canvasContext,b=g.x,a=g.y,
l=d.x,i=d.y,e=g.x+f.x,g=g.y+f.y,f=d.x,d=d.y;c.beginPath();c.moveTo(b,a);c.bezierCurveTo(e,g,f,d,l,i)}else c=this.canvasContext,b=g.x,g=g.y,a=d.x,d=d.y,c.beginPath(),c.moveTo(b,g),c.lineTo(a,d);c.stroke()}}};r.prototype.resetCanvas=function(b){var a=this.canvas,d=this.settings,c=this.canvasContext,f=this.isCanvasEmulator,l=a.width,i=a.height;c.clearRect(0,0,l+30,i+30);c.shadowColor=c.fillStyle=d["background-color"];f&&c.fillRect(0,0,l+30,i+30);c.lineWidth=Math.ceil(parseInt(d.lineWidth,10));c.lineCap=
c.lineJoin="round";c.strokeStyle=d["decor-color"];c.shadowOffsetX=0;c.shadowOffsetY=0;var h=Math.round(i/5),p=1.5*h,k=i-h,l=l-1.5*h,i=i-h;c.beginPath();c.moveTo(p,k);c.lineTo(l,i);c.stroke();c.strokeStyle=d.color;f||(c.shadowColor=c.strokeStyle,c.shadowOffsetX=0.5*c.lineWidth,c.shadowOffsetY=-0.6*c.lineWidth,c.shadowBlur=0);b||(b=[]);c=this.dataEngine=new t(b,this,z,u,x);d.data=b;$(a).data(e+".data",b).data(e+".settings",d);var n=this.$parent,w=this.events,A=e;c.changed=function(){w.publish(A+".change");
n.trigger("change")};c.changed();return!0};r.prototype.initializeCanvas=function(b){var a=document.createElement("canvas"),c=$(a);b.width===b.height&&"ratio"===b.height&&(b.width="100%");c.css("margin",0).css("padding",0).css("border","none").css("height","ratio"===b.height||!b.height?1:b.height.toString(10)).css("width","ratio"===b.width||!b.width?1:b.width.toString(10));c.appendTo(this.$parent);"ratio"===b.height?c.css("height",Math.round(c.width()/b.sizeRatio)):"ratio"===b.width&&c.css("width",
Math.round(c.height()*b.sizeRatio));c.addClass(e);a.width=c.width();a.height=c.height();b=a;if(b.getContext)b=!1;else{var c=b.ownerDocument.parentWindow,d=c.FlashCanvas?b.ownerDocument.parentWindow.FlashCanvas:"undefined"===typeof FlashCanvas?void 0:FlashCanvas;if(d){b=d.initElement(b);d=1;c&&(c.screen&&c.screen.deviceXDPI&&c.screen.logicalXDPI)&&(d=1*c.screen.deviceXDPI/c.screen.logicalXDPI);if(1!==d)try{$(b).children("object").get(0).resize(Math.ceil(b.width*d),Math.ceil(b.height*d)),b.getContext("2d").scale(d,
d)}catch(f){}b=!0}else throw Error("Canvas element does not support 2d context. jSignature cannot proceed.");}this.isCanvasEmulator=b;a.onselectstart=function(b){b&&b.preventDefault&&b.preventDefault();b&&b.stopPropagation&&b.stopPropagation();return!1};return a};var p=window,h=function(b,a){var c=new Image,d=this;c.onload=function(){d.getContext("2d").drawImage(c,0,0,c.width<d.width?c.width:d.width,c.height<d.height?c.height:d.height)};c.src="data:"+a+","+b},a=function(b){this.find("canvas."+e).add(this.filter("canvas."+
e)).data(e+".this").resetCanvas(b);return this},f=function(b,a){if(void 0===a&&("string"===typeof b&&"data:"===b.substr(0,5))&&(a=b.slice(5).split(",")[0],b=b.slice(6+a.length),a===b))return;var c=this.find("canvas."+e).add(this.filter("canvas."+e));if(n.hasOwnProperty(a))0!==c.length&&n[a].call(c[0],b,a,function(b){return function(){return b.resetCanvas.apply(b,arguments)}}(c.data(e+".this")));else throw Error(e+" is unable to find import plugin with for format '"+String(a)+"'");return this},d=new v,
c=e,l,i=function(){d.publish(c+".parentresized")};$(p).bind("resize."+c,function(){l&&clearTimeout(l);l=setTimeout(i,500)}).bind("mouseup."+c,function(){d.publish(c+".windowmouseup")});var w={},A={"default":function(){return this.toDataURL()},"native":function(b){return b},image:function(){var b=this.toDataURL();if("string"===typeof b&&4<b.length&&"data:"===b.slice(0,5)&&-1!==b.indexOf(",")){var a=b.indexOf(",");return[b.slice(5,a),b.substr(a+1)]}return[]}},n={"native":function(b,a,c){c(b)},image:h,
"image/png;base64":h,"image/jpeg;base64":h,"image/jpg;base64":h},B={"export":A,"import":n,instance:w},C={init:function(b){return this.each(function(){var a,c=!1;for(a=this.parentNode;a&&!c;)c=a.body,a=a.parentNode;!c||new r(this,b,w)})},getSettings:function(){return this.find("canvas."+e).add(this.filter("canvas."+e)).data(e+".this").settings},clear:a,reset:a,addPlugin:function(b,a,c){B.hasOwnProperty(b)&&(B[b][a]=c);return this},listPlugins:function(b){var a=[];if(B.hasOwnProperty(b)){var b=B[b],
c;for(c in b)b.hasOwnProperty(c)&&a.push(c)}return a},getData:function(b){var a=this.find("canvas."+e).add(this.filter("canvas."+e));void 0===b&&(b="default");if(0!==a.length&&A.hasOwnProperty(b))return A[b].call(a.get(0),a.data(e+".data"))},importData:f,setData:f,globalEvents:function(){return d},events:function(){return this.find("canvas."+e).add(this.filter("canvas."+e)).data(e+".this").events}};$.fn[e]=function(b){if(!b||"object"===typeof b)return C.init.apply(this,arguments);if("string"===typeof b&&
C[b])return C[b].apply(this,Array.prototype.slice.call(arguments,1));$.error("Method "+String(b)+" does not exist on jQuery."+e)}})();
(function(){$.fn.jSignature("addPlugin","instance","UndoButton",function(q){this.events.subscribe("jSignature.attachingEventHandlers",function(){if(this.settings[q]){var m=this.settings[q];"function"!==typeof m&&(m=function(){var e=$('<input type="button" value="Undo last stroke" style="position:absolute;display:none;margin:0 !important;top:auto" />').appendTo(this.$controlbarLower),k=e.width();e.css("left",Math.round((this.canvas.width-k)/2));k!==e.width()&&e.width(k);return e});var k=m.call(this),
t=this;t.events.subscribe("jSignature.change",function(){t.dataEngine.data.length?k.show():k.hide()});var r=this,e=(this.events.topics.hasOwnProperty("jSignature.undo")?q:"jSignature")+".undo";k.bind("click",function(){r.events.publish(e)});r.events.subscribe(e,function(){var e=r.dataEngine.data;e.length&&(e.pop(),r.resetCanvas(e))})}})})})();
(function(){for(var q={},m={},k="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX".split(""),t=k.length/2,r=t-1;-1<r;r--)q[k[r]]=k[r+t],m[k[r+t]]=k[r];var e=function(e){for(var e=e.split(""),h=e.length,a=1;a<h;a++)e[a]=q[e[a]];return e.join("")},y=function(k){for(var h=[],a=0,f=1,d=k.length,c,l,i=0;i<d;i++)c=Math.round(k[i]),l=c-a,a=c,0>l&&0<f?(f=-1,h.push("Z")):0<l&&0>f&&(f=1,h.push("Y")),c=Math.abs(l),c>=t?h.push(e(c.toString(t))):h.push(c.toString(t));return h.join("")},v=function(e){for(var h=
[],e=e.split(""),a=e.length,f,d=1,c=[],l=0,i=0;i<a;i++)f=e[i],f in q||"Z"===f||"Y"===f?(0!==c.length&&(c=parseInt(c.join(""),t)*d+l,h.push(c),l=c),"Z"===f?(d=-1,c=[]):"Y"===f?(d=1,c=[]):c=[f]):c.push(m[f]);h.push(parseInt(c.join(""),t)*d+l);return h},z=function(e){for(var h=[],a=e.length,f,d=0;d<a;d++)f=e[d],h.push(y(f.x)),h.push(y(f.y));return h.join("_")},u=function(e){for(var h=[],e=e.split("_"),a=e.length/2,f=0;f<a;f++)h.push({x:v(e[2*f]),y:v(e[2*f+1])});return h},k=function(e){return["image/jsignature;base30",
z(e)]},r=function(e,h,a){"string"===typeof e&&("image/jsignature;base30"===e.substring(0,23).toLowerCase()&&(e=e.substring(24)),a(u(e)))};if(null==this.jQuery)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");var x=this.jQuery.fn.jSignature;x("addPlugin","export","base30",k);x("addPlugin","export","image/jsignature;base30",k);x("addPlugin","import","base30",r);x("addPlugin","import","image/jsignature;base30",r);this.jSignatureDebug&&(this.jSignatureDebug.base30=
{remapTailChars:e,compressstrokeleg:y,uncompressstrokeleg:v,compressstrokes:z,uncompressstrokes:u,charmap:q})}).call("undefined"!==typeof window?window:this);
(function(){function q(a,f){this.x=a;this.y=f;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var d=function(a){return Math.round(a/Math.abs(a))};this.resizeTo=function(a){if(0===this.x&&0===this.y)this._length=0;else if(0===this.x)this._length=a,this.y=a*d(this.y);else if(0===this.y)this._length=a,this.x=a*d(this.x);else{var f=Math.abs(this.y/
this.x),e=Math.sqrt(Math.pow(a,2)/(1+Math.pow(f,2))),f=f*e;this._length=a;this.x=e*d(this.x);this.y=f*d(this.y)}return this};this.angleTo=function(a){var d=this.getLength()*a.getLength();return 0===d?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/d,-1),1))/Math.PI}}function m(a,f){this.x=a;this.y=f;this.getVectorToCoordinates=function(a,c){return new q(a-this.x,c-this.y)};this.getVectorFromCoordinates=function(a,c){return this.getVectorToCoordinates(a,c).reverse()};this.getVectorToPoint=function(a){return new q(a.x-
this.x,a.y-this.y)};this.getVectorFromPoint=function(a){return this.getVectorToPoint(a).reverse()}}function k(a,f){var d=Math.pow(10,f);return Math.round(a*d)/d}function t(a,f,d){var f=f+1,c=new m(a.x[f-1],a.y[f-1]),e=new m(a.x[f],a.y[f]),e=c.getVectorToPoint(e),i=new m(a.x[f-2],a.y[f-2]),c=i.getVectorToPoint(c);return c.getLength()>d?(d=2<f?(new m(a.x[f-3],a.y[f-3])).getVectorToPoint(i):new q(0,0),a=0.35*c.getLength(),i=c.angleTo(d.reverse()),f=e.angleTo(c.reverse()),d=(new q(d.x+c.x,d.y+c.y)).resizeTo(Math.max(0.05,
i)*a),e=(new q(c.x+e.x,c.y+e.y)).reverse().resizeTo(Math.max(0.05,f)*a),e=new q(c.x+e.x,c.y+e.y),["c",k(d.x,2),k(d.y,2),k(e.x,2),k(e.y,2),k(c.x,2),k(c.y,2)]):["l",k(c.x,2),k(c.y,2)]}function r(a,f){var d=a.x.length-1,c=new m(a.x[d],a.y[d]),e=new m(a.x[d-1],a.y[d-1]),c=e.getVectorToPoint(c);if(1<d&&c.getLength()>f){var d=(new m(a.x[d-2],a.y[d-2])).getVectorToPoint(e),e=c.angleTo(d.reverse()),i=0.35*c.getLength(),d=(new q(d.x+c.x,d.y+c.y)).resizeTo(Math.max(0.05,e)*i);return["c",k(d.x,2),k(d.y,2),k(c.x,
2),k(c.y,2),k(c.x,2),k(c.y,2)]}return["l",k(c.x,2),k(c.y,2)]}function e(a,e,d){for(var e=["M",k(a.x[0]-e,2),k(a.y[0]-d,2)],d=1,c=a.x.length-1;d<c;d++)e.push.apply(e,t(a,d,1));0<c?e.push.apply(e,r(a,d,1)):0===c&&e.push.apply(e,["l",1,1]);return e.join(" ")}function y(a){var f=['<?xml version="1.0" encoding="UTF-8" standalone="no"?>','<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'],d,c=a.length,l,i=[],h=[],k=l=d=0,n=0,p=[];if(0!==c){for(d=0;d<c;d++){k=
a[d];n=[];l={x:[],y:[]};for(var m=void 0,b=void 0,m=0,b=k.x.length;m<b;m++)n.push({x:k.x[m],y:k.y[m]});n=simplify(n,0.7,!0);m=0;for(b=n.length;m<b;m++)l.x.push(n[m].x),l.y.push(n[m].y);p.push(l);i=i.concat(l.x);h=h.concat(l.y)}a=Math.min.apply(null,i)-1;c=Math.max.apply(null,i)+1;i=Math.min.apply(null,h)-1;h=Math.max.apply(null,h)+1;k=0>a?0:a;n=0>i?0:i;d=c-a;l=h-i}f.push('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'+d.toString()+'" height="'+l.toString()+'">');d=0;for(c=p.length;d<
c;d++)l=p[d],f.push('<path fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="'+e(l,k,n)+'"/>');f.push("</svg>");return f.join("")}function v(a){return[p,y(a)]}function z(a){return[h,x(y(a))]}var u=window;"use strict";("undefined"!=typeof exports?exports:u).simplify=function(a,e,d){e=void 0!==e?e*e:1;if(!d){for(var c=a.length,l,i=a[0],h=[i],d=1;d<c;d++){l=a[d];var k=l.x-i.x,n=l.y-i.y;k*k+n*n>e&&(h.push(l),i=l)}a=(i!==l&&h.push(l),h)}l=a;var d=l.length,
c=new ("undefined"!=typeof Uint8Array?Uint8Array:Array)(d),i=0,h=d-1,m,p,b=[],j=[],s=[];for(c[i]=c[h]=1;h;){n=0;for(k=i+1;k<h;k++){m=l[k];var g=l[i],r=l[h],q=g.x,t=g.y,g=r.x-q,u=r.y-t,v=void 0;if(0!==g||0!==u)v=((m.x-q)*g+(m.y-t)*u)/(g*g+u*u),1<v?(q=r.x,t=r.y):0<v&&(q+=g*v,t+=u*v);m=(g=m.x-q,u=m.y-t,g*g+u*u);m>n&&(p=k,n=m)}n>e&&(c[p]=1,b.push(i),j.push(p),b.push(p),j.push(h));i=b.pop();h=j.pop()}for(k=0;k<d;k++)c[k]&&s.push(l[k]);return a=s,a};if("function"!==typeof x)var x=function(a){var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(""),
d,c,h,i,k=0,m=0,n="",n=[];do d=a.charCodeAt(k++),c=a.charCodeAt(k++),h=a.charCodeAt(k++),i=d<<16|c<<8|h,d=i>>18&63,c=i>>12&63,h=i>>6&63,i&=63,n[m++]=e[d]+e[c]+e[h]+e[i];while(k<a.length);n=n.join("");a=a.length%3;return(a?n.slice(0,a-3):n)+"===".slice(a||3)};var p="image/svg+xml",h="image/svg+xml;base64";if("undefined"===typeof $)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");u=$.fn.jSignature;u("addPlugin","export","svg",v);u("addPlugin",
"export",p,v);u("addPlugin","export","svgbase64",z);u("addPlugin","export",h,z)})();

View File

@ -0,0 +1,53 @@
.bs-sidebar {
position: fixed;
top: 110px;
z-index : 1;
background-color: #f7f5fa;
border-radius: 5px;
}
.bs-sidenav {
padding-top: 10px;
padding-bottom: 10px;
}
.bs-sidebar .nav > li > a {
display: block;
color: #716b7a;
padding: 5px 20px;
}
.bs-sidebar .nav > .active > a,
.bs-sidebar .nav > .active:hover > a,
.bs-sidebar .nav > .active:focus > a {
font-weight: bold;
color: #563d7c;
background-color: transparent;
border-right: 1px solid #563d7c;
}
.bs-sidebar .nav .nav {
display: none;
margin-bottom: 8px;
}
.bs-sidebar .nav .nav > li > a {
padding-top: 3px;
padding-bottom: 3px;
padding-left: 30px;
font-size: 90%;
}
@media (min-width: 992px) {
.bs-sidebar .nav > .active > ul {
display: block;
}
.bs-sidebar {
width: 213px;
}
}
@media (min-width: 1200px) {
.bs-sidebar {
width: 263px;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,80 @@
$(document).ready(function () {
$('a.js_update_line_json').on('click', function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
var href = $link.attr("href");
var order_id = href.match(/order_id=([0-9]+)/);
var line_id = href.match(/update_line\/([0-9]+)/);
var token = href.match(/token=(.*)/);
openerp.jsonRpc("/quote/update_line/", 'call', {
'line_id': line_id[1],
'order_id': parseInt(order_id[1]),
'token': token[1],
'remove': $link.is('[href*="remove"]'),
'unlink': $link.is('[href*="unlink"]')
})
.then(function (data) {
if(!data){
location.reload();
}
$link.parents('.input-group:first').find('.js_quantity').val(data[0]);
$('[data-id="total_amount"]>span').html(data[1]);
});
return false;
});
$('#modelaccept').on('shown.bs.modal', function (e) {
$("#signature").empty().jSignature();
});
$('#sign_clean').on('click', function (e) {
$("#signature").jSignature('reset');
});
$('form.js_accept_json').submit(function(ev){
ev.preventDefault();
var $link = $(ev.currentTarget);
var href = $link.attr("action");
var order_id = href.match(/accept\/([0-9]+)/);
var token = href.match(/token=(.*)/);
var datapair = $("#signature").jSignature("getData",'image')
openerp.jsonRpc("/quote/accept/", 'call', {
'order_id': parseInt(order_id[1]),
'token': token[1],
'signer': $("#signer").val(),
'sign': JSON.stringify(datapair[1]),
})
.then(function (data) {
$('#modelaccept').modal('hide');
location.reload();
});
return false
});
// automatically generate a menu from h1 and h1 tag in content
var ul = $('[data-id="quote_sidebar"]');
var sub_li = null;
var sub_ul = null;
$("section h1, section h2").each(function() {
switch (this.tagName.toLowerCase()) {
case "h1":
id = _.uniqueId('quote_header_')
$(this.parentNode).attr('id',id)
sub_li = $("<li>").html('<a href="#'+id+'">'+$(this).text()+'</a>').appendTo(ul);
sub_ul = null;
break;
case "h2":
id = _.uniqueId('quote_')
if (sub_li) {
if (!sub_ul) {
sub_ul = $("<ul class='nav'>").appendTo(sub_li);
}
$(this.parentNode).attr('id',id)
$("<li>").html('<a href="#'+id+'">'+$(this).text()+'</a>').appendTo(sub_ul);
}
break;
}
});
//vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
});

View File

@ -0,0 +1,380 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="pricing" name="Price">
<section data-snippet-id="title">
<h1 class="page-header">Pricing</h1>
</section>
<section id="quote">
<table class="table table-hover">
<thead>
<tr>
<th>Products</th>
<th>Quantity</th>
<th>Discount</th>
<th class="text-right">Unit Price</th>
<th class="text-right">Price</th>
</tr>
</thead>
<tbody>
<tr t-foreach="quotation.order_line" t-as="line">
<td>
<div t-field="line.name"/>
</td>
<td>
<div id="quote_qty">
<t t-esc="line.product_uom_qty"/>
</div>
</td>
<td>
<div id="quote_discount" t-if="line.discount">
<t t-esc="line.discount"/>
</div>
</td>
<td>
<strong class="text-right">
<div t-field="line.price_unit"
t-field-options='{"widget": "monetary", "display_currency": "quotation.pricelist_id.currency_id"}'
t-att-style="line.discount and 'text-decoration: line-through' or ''"
t-att-class="line.discount and 'text-danger' or ''"/>
<!-- TODO: apply monetary widget formating -->
<div t-if="line.discount">
<t t-esc="'%.2f' % ((1-line.discount) * line.price_unit)"/>
</div>
</strong>
</td>
<td>
<div class="text-right"
t-field="line.price_subtotal"
t-field-options='{"widget": "monetary", "display_currency": "quotation.pricelist_id.currency_id"}'/>
</td>
<td>
<a t-href="./update_line/#{ line.id }/?order_id=#{ quotation.id }&amp;unlink=True&amp;token=#{ quotation.access_token }" class="mb8 js_update_line_json pull-right hidden-print" t-if="line.is_optional">
<span class="fa fa-trash-o"></span>
</a>
</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td><h3 class="text-right">Total</h3></td>
<td class="text-right" colspan="2">
<h3>
<strong data-id="total_amount" t-field="quotation.amount_total" t-field-options='{"widget": "monetary","display_currency": "quotation.pricelist_id.currency_id"}'/>
</h3>
</td>
</tr>
</tbody>
</table>
</section>
<t t-call="website_quotation.quotation_toolbar"/>
<section id="terms" class="container" t-if="quotation.note">
<h2 class="page-header">Terms &amp; Conditions</h2>
<p t-field="quotation.note"/>
</section>
</template>
<template id="change_quantity" inherit_option_id="website_quotation.pricing" name="Change Quantity">
<xpath expr="//div[@id='quote_qty']" position="replace">
<div class="input-group">
<span class="input-group-addon hidden-print">
<a t-href="./update_line/#{ line.id }/?order_id=#{ quotation.id }&amp;remove=True&amp;token=#{ quotation.access_token }" class="mb8 js_update_line_json">
<span class="fa fa-minus"/>
</a>
</span>
<input type="text" class="js_quantity form-control" t-att-data-id="line.id" t-att-value="line.product_uom_qty"/>
<span class="input-group-addon hidden-print">
<a t-href="./update_line/#{ line.id }/?order_id=#{ quotation.id }&amp;token=#{ quotation.access_token }" class="mb8 js_update_line_json">
<span class="fa fa-plus"/>
</a>
</span>
</div>
</xpath>
</template>
<template id="chatter">
<h1 class="page-header">History</h1>
<ul class="media-list" id="comments-list">
<li t-foreach="quotation.message_ids" t-as="message" class="media">
<div class="media-body">
<img class="media-object pull-left" t-att-src="'/website/image?model=res.partner&amp;field=image_small&amp;id='+str(message.author_id.id)" style="width: 50px; margin-right: 10px;"/>
<div class="media-body">
<h5 class="media-heading">
<span t-field="message.author_id"/> <small>on <span t-field="message.date"/></small>
</h5>
<div t-field="message.body"/>
</div>
</div>
</li>
</ul>
</template>
<!-- Options:Quotation Chatter: user can reply -->
<template id="opt_quotation_chatter_post_complete_comment" name="Allow Comments" inherit_option_id="website_quotation.chatter" inherit_id="website_quotation.chatter">
<xpath expr="//h1" position="after">
<section class="mb32 css_editable_mode_hidden hidden-print">
<form id="comment" t-attf-action="/quote/#{quotation.id}/#{quotation.access_token}/post" method="POST">
<img class="img pull-left img-rounded" t-att-src="'/website/image?model=res.partner&amp;field=image_small&amp;id='+str(user_id.partner_id.id)" style="width: 50px; margin-right: 10px;"/>
<div class="pull-left mb32" style="width: 75%%">
<textarea rows="3" name="comment" class="form-control" placeholder="Write a comment..."></textarea>
<button type="submit" class="btn btn-primary mt8">Post</button>
</div>
</form>
</section>
<div class="clearfix"/>
</xpath>
</template>
<template id="quotation_toolbar">
<div class="text-center hidden-print">
<div class="btn-group btn-group-lg" t-if="quotation.state in ('draft', 'sent', 'waiting_date')">
<a class="btn btn-success fa fa-check" data-toggle="modal" data-target="#modelaccept">
Accept
</a>
<a class="btn btn-info fa fa-comment" type="submit" href="#discussion">
Feedback
</a>
<a class="btn btn-danger fa fa-times" data-toggle="modal" data-target="#modeldecline">
Reject
</a>
</div>
</div>
</template>
<template id="so_quotation" name="Product Quotation">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_quotation/static/src/js/website_quotation.js"></script>
<script type="text/javascript" src="/website_quotation/static/lib/jSignature/jSignature.min.js"></script>
<link rel='stylesheet' href='/website_quotation/static/src/css/website_quotation.css'/>
<t t-raw="head or ''"/>
</t>
<body data-spy="scroll" data-target=".navspy">
<div class="container">
<div class="row mt16">
<div class="col-md-3">
<div class="bs-sidebar">
<div class="text-center hidden-print" t-if="quotation.state in ('draft', 'sent', 'waiting_date')" style="padding: 10px">
<a class="btn btn-primary btn-block fa fa-check" data-toggle="modal" data-target="#modelaccept">
Accept Order
</a>
<a class="btn btn-link" type="submit" href="#discussion">
Ask Changes
</a> or
<a class="btn btn-link" data-toggle="modal" data-target="#modeldecline">
Reject
</a>
</div>
<hr class="mt0 mb0"/>
<div class="hidden-print navspy" role="complementary">
<ul class="nav bs-sidenav" data-id="quote_sidebar">
<li><a href="#introduction">Introduction</a></li>
</ul>
</div>
</div>
</div>
<div class="col-md-9">
<a id="introduction"/>
<h1 class="page-header mt16">
<span t-if="quotation.state in ('draft','sent','cancel')">Your Quotation</span>
<span t-if="quotation.state not in ('draft','sent','cancel')">Your Order</span>
<em t-esc="quotation.name"/>
<small t-field="quotation.state"/>
<div groups="base.group_website_publisher" t-ignore="true" class="pull-right css_editable_mode_hidden">
<a class="btn btn-info" t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s' % (quotation._name, quotation.id)">Update Quote</a>
</div>
</h1>
<div class="modal fade" id="modelaccept" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<form id="accept" method="POST" t-attf-action="/quote/accept/#{quotation.id}/?token=#{quotation.access_token}" class="js_accept_json modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&amp;times;</button>
<h4 class="modal-title">Validate Order</h4>
</div>
<div class="modal-body">
<p>
I agree that by signing this proposal, I
accept it on the behalf of <b t-field="quotation.company_id"/>,
for an amount of
<b data-id="total_amount" t-field="quotation.amount_total"
t-field-options='{"widget": "monetary", "display_currency": "quotation.pricelist_id.currency_id"}'/>
with payment terms: <b t-field="quotation.payment_term"/>.
</p>
<div>
<strong>Your Name:</strong><br/>
<input type="text" id="signer" class="form-control"/>
</div>
<div class="panel panel-default mt16 mb0">
<div class="panel-heading">
<div class="pull-right">
<a id="sign_clean" class="btn btn-xs">Clear</a>
</div>
<strong>Draw your signature</strong>
</div>
<div id="signature" class="panel-body" style="padding: 0"/>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Sign Order</button> or
<button type="button" class="btn btn-link" data-dismiss="modal" style="padding: 0">Cancel</button>
</div>
</form>
</div>
</div>
<div class="alert alert-warning alert-dismissable" t-if="quotation.state == 'cancel'">
<button type="button" class="close hidden-print" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<strong>This quotation has been canceled.</strong> Contact <span t-field="quotation.user_id"/> ( <span t-if="quotation.user_id.email"> <span class="fa fa-envelope" t-field="quotation.user_id.email" /></span> <span t-if="quotation.user_id.phone"> <span class="fa fa-phone" t-field="quotation.user_id.phone"/></span> ) in order to ask a new quote.
</div>
<div class="modal fade" id="modeldecline" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<form id="decline" method="POST" t-attf-action="/quote/#{quotation.id}/#{quotation.access_token}/decline" class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&amp;times;</button>
<h4 class="modal-title">Reject This Quote</h4>
</div>
<div class="modal-body">
<p>
Tell us why you are refusing this quotation, this will help us improve our services.
</p>
<textarea rows="4" name="decline_message" placeholder="Your feedback....." class="form-control"/>
</div>
<div class="modal-footer">
<button type="submit" t-att-id="quotation.id" class="btn btn-primary">Reject</button> or
<button type="button" class="btn btn-link" data-dismiss="modal" style="padding: 0">Cancel</button>
</div>
</form>
</div>
</div>
<div class="row mt32">
<div class="col-md-6">
<div>
<label class="col-sm-3 text-right">Bill To:</label>
<div class="col-sm-9">
<div t-field="quotation.partner_invoice_id" t-field-options='{
"widget": "contact",
"fields": ["address", "name", "phone", "email"]
}'/>
</div>
</div>
<div t-if="quotation.partner_shipping_id.id != quotation.partner_invoice_id.id">
<label class="col-sm-3 text-right">Ship To:</label>
<div class="col-sm-9">
<div t-field="quotation.partner_shipping_id" t-field-options='{
"widget": "contact",
"fields": ["address", "name", "phone"]
}'/>
</div>
</div>
</div>
<div class="col-md-6">
<div class="row">
<label class="col-sm-5 text-right">Your Contact:</label>
<div class="col-sm-7">
<div t-field="quotation.user_id" t-field-options='{
"widget": "contact",
"fields": ["name", "phone", "email"]
}'/>
</div>
</div>
<div class="row">
<label class="col-sm-5 text-right">Quote Date:</label>
<div class="col-sm-7">
<span t-field="quotation.date_order"/>
</div>
<div class="clearfix"/>
<div t-if="quotation.client_order_ref">
<label class="col-sm-5 text-right">Your Reference:</label>
<div class="col-sm-7">
<span t-field="quotation.client_order_ref"/>
</div>
</div>
</div>
</div>
</div>
<a id="offer"/>
<div t-field="quotation.website_description"/>
<t t-foreach="quotation.order_line" t-as="line">
<a t-att-id="line.id"/>
<div t-field="line.website_description"/>
</t>
<a id="pricing"/>
<t t-call="website_quotation.pricing"/>
<a id="options"/>
<t t-call="website_quotation.optional_products"/>
<a id="discussion"/>
<t t-call="website_quotation.chatter"/>
</div>
</div>
</div>
</body>
</t>
</template>
<template id="optional_products">
<div class="container mt32" t-if="quotation.options">
<h3>Options:</h3>
<div class='row mt16'>
<t t-foreach="quotation.options" t-as="option">
<div class='col-md-2 thumbnail' style='width: 170px; margin-right: 16px;' t-if="not option.line_id">
<div class='mt16 text-center'>
<a href="#" t-field="option.product_id.name"/>
<span t-field="option.product_id.image_small" t-field-options='{"widget": "image", "class": "img-rounded"}'/>
</div>
<a t-href="/quote/add_line/#{ option.id }/#{ quotation.id }/#{ quotation.access_token }" class="btn btn-primary btn-xs hidden-print">Add To Quotation
</a>
</div>
</t>
</div>
</div>
</template>
<template id="so_template" name="SO Template">
<t t-call="website.layout">
<div class="container">
<div class="row">
<div class="col-md-4" id="sidebar">
<ul class="nav nav-pills nav-stacked">
<li class="active"><a href="#template_introduction" data-toggle="tab">Contact Data</a></li>
<li><a href="#template_introduction" data-toggle="tab">Our Offer</a></li>
<t t-foreach="template.quote_line" t-as="line">
<li><a t-att-href="'#%s'% line.id" data-toggle="tab"><t t-raw="line.product_id.name_template"/> </a></li>
</t>
<li><a href="#templateterms" data-toggle="tab">Terms &amp; Conditions</a></li>
</ul>
</div>
<div class="col-md-8 tab-content">
<div id="template_introduction" t-field="template.website_description"/>
<t t-foreach="template.quote_line" t-as="line">
<section class="tab-pane" t-att-id="line.id">
<div t-field="line.website_description" class="oe_structure"/>
</section>
</t>
<section id="templateterms" class="tab-pane">
<div class="container panel panel-default">
<div class="row panel-body">
<div class="text-center">
<h2 class="page-header">Terms &amp; Conditions</h2>
<p class="lead" t-field="template.note"/>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</t>
</template>
</data>
</openerp>

View File

@ -0,0 +1,125 @@
<?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='invoice_corrected']" position="after">
<button name="open_quotation" string="View Quotation" type="object"
attrs="{'invisible': [('template_id','=',False)]}"/>
</xpath>
<xpath expr="//page[@string='Order Lines']" position="after">
<page string="Suggested Products">
<label string="Optional Products &amp; Services" for="options"/>
<field name="options">
<tree string="Sales Quote Template Lines" editable="bottom">
<field name="product_id" on_change="on_change_product_id(product_id)"/>
<field name="name"/>
<field name="quantity"/>
<field name="uom_id"/>
<field name="price_unit"/>
<field name="discount"/>
<field name="website_description" invisible="1"/>
</tree>
</field>
</page>
</xpath>
<xpath expr="//field[@name='client_order_ref']" position="after">
<field name="template_id" on_change="onchange_template_id(template_id)"/>
<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>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Sale Quotation Template" version="7.0">
<header>
<button string="Edit Template" type="object" name="open_template" class="oe_highlight"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
</div>
<notebook>
<page string="Lines">
<field name="quote_line">
<form string="Sales Quote Template Lines" version="7.0">
<group>
<group>
<field name="product_id" on_change="on_change_product_id(product_id)"/>
<label for="product_uom_qty"/>
<div>
<field
name="product_uom_qty" class="oe_inline"/>
</div>
<field name="price_unit"/>
</group>
</group>
<notebook colspan="4">
<page string="Description">
<field name="name" />
</page>
<page string="Website Description">
<field name="website_description" />
</page>
</notebook>
</form>
<tree string="Sales Quote Template Lines" editable="bottom">
<field name="product_id" on_change="on_change_product_id(product_id)"/>
<field name="name"/>
<field name="product_uom_qty"/>
<field name="price_unit"/>
<field name="website_description" invisible="1"/>
</tree>
</field>
</page>
<page string="Suggested Products">
<field name="options">
<tree string="Sales Quote Template Lines" editable="bottom">
<field name="product_id" on_change="on_change_product_id(product_id)"/>
<field name="name"/>
<field name="quantity"/>
<field name="uom_id"/>
<field name="price_unit"/>
<field name="discount"/>
<field name="website_description" invisible="1"/>
</tree>
</field>
</page>
</notebook>
<field name="website_description" invisible="1"/>
<field name="note" placeholder="Terms and conditions..." nolable="1"/>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_sale_quote_template_tree">
<field name="name">sale.quote.template.tree</field>
<field name="model">sale.quote.template</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Sale Quote Template">
<field name="name"/>
</tree>
</field>
</record>
<record id="action_sale_quotation_template" model="ir.actions.act_window">
<field name="name">Sales Template</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.quote.template</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem action="action_sale_quotation_template" id="menu_sale_quote_template" parent="base.menu_base_config" sequence="6" groups="base.group_sale_salesman,base.group_sale_manager"/>
</data>
</openerp>

View File

@ -24,4 +24,4 @@
</record>
</data>
</openerp>
</openerp>