[MERGE] trunk

bzr revid: sle@openerp.com-20140428161922-phoj5oghmx85rtem
This commit is contained in:
Simon Lejeune 2014-04-28 18:19:22 +02:00
commit 8ac834748d
45 changed files with 1445 additions and 1705 deletions

View File

@ -642,7 +642,7 @@ class account_move_line(osv.osv):
(_check_date, 'The date of your Journal Entry is not in the defined period! You should change the date or remove this constraint from the journal.', ['date']),
(_check_currency, 'The selected account of your Journal Entry forces to provide a secondary currency. You should remove the secondary currency on the account or select a multi-currency view on the journal.', ['currency_id']),
(_check_currency_and_amount, "You cannot create journal items with a secondary currency without recording both 'currency' and 'amount currency' field.", ['currency_id','amount_currency']),
(_check_currency_amount, 'The amount expressed in the secondary currency must be positive when the journal item is a debit and negative when if it is a credit.', ['amount_currency']),
(_check_currency_amount, 'The amount expressed in the secondary currency must be positive when account is debited and negative when account is credited.', ['amount_currency']),
(_check_currency_company, "You cannot provide a secondary currency if it is the same than the company one." , ['currency_id']),
]

View File

@ -384,6 +384,7 @@
<group expand="0" string="Group By...">
<filter string="User" context="{'group_by':'user_id'}" icon="terp-personal"/>
<filter string="Type" context="{'group_by':'type'}" icon="terp-stock_symbol-selection"/>
<filter string="Company" context="{'group_by':'company_id'}" icon="terp-go-home" groups="base.group_multi_company"/>
</group>
</search>
</field>
@ -881,6 +882,7 @@
<field name="price_include"/>
<field name="description"/>
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
<field name="type_tax_use" invisible="1"/>
</tree>
</field>
</record>
@ -891,6 +893,12 @@
<search string="Search Taxes">
<field name="name" filter_domain="['|', ('name','ilike',self), ('description','ilike',self)]" string="Tax"/>
<field name="company_id" groups="base.group_multi_company"/>
<filter string="Sale" domain="[('type_tax_use','=','sale')]" />
<filter string="Purchase" domain="[('type_tax_use','=','purchase')]" />
<group string="Group By...">
<filter string="Company" domain="[]" context="{'group_by':'company_id'}"/>
<filter string="Tax Application" domain="[]" context="{'group_by':'type_tax_use'}"/>
</group>
</search>
</field>
</record>

View File

@ -97,7 +97,7 @@
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<page name="sales_purchases" position="after" version="7.0">
<page string="Accounting" col="4" name="accounting" attrs="{'invisible': [('is_company','=',False),('parent_id','!=',False)]}">
<page string="Accounting" col="4" name="accounting" attrs="{'invisible': [('is_company','=',False),('parent_id','!=',False)]}" groups="account.group_account_invoice">
<group>
<group>
<field name="property_account_position" widget="selection"/>
@ -127,7 +127,7 @@
</tree>
</field>
</page>
<page string="Accounting" name="accounting_disabled" attrs="{'invisible': ['|',('is_company','=',True),('parent_id','=',False)]}">
<page string="Accounting" name="accounting_disabled" attrs="{'invisible': ['|',('is_company','=',True),('parent_id','=',False)]}" groups="account.group_account_invoice">
<div>
<p>Accounting-related settings are managed on <button name="open_commercial_entity" type="object" string="the parent company" class="oe_link"/></p>
</div>

View File

@ -259,6 +259,12 @@ class account_config_settings(osv.osv_memory):
def onchange_tax_rate(self, cr, uid, ids, rate, context=None):
return {'value': {'purchase_tax_rate': rate or False}}
def onchange_multi_currency(self, cr, uid, ids, group_multi_currency, context=None):
res = {}
if not group_multi_currency:
res['value'] = {'income_currency_exchange_account_id': False, 'expense_currency_exchange_account_id': False}
return res
def onchange_start_date(self, cr, uid, id, start_date):
if start_date:
start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")

View File

@ -122,7 +122,7 @@
<label for="id" string="Features"/>
<div>
<div name="group_multi_currency">
<field name="group_multi_currency" class="oe_inline"/>
<field name="group_multi_currency" class="oe_inline" on_change="onchange_multi_currency(group_multi_currency)"/>
<label for="group_multi_currency"/>
</div>
<div>

View File

@ -0,0 +1,62 @@
# Slovak translation for openobject-addons
# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2014-04-26 16:04+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Slovak <sk@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-27 05:58+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: account_anglo_saxon
#: model:ir.model,name:account_anglo_saxon.model_product_category
msgid "Product Category"
msgstr ""
#. module: account_anglo_saxon
#: model:ir.model,name:account_anglo_saxon.model_account_invoice_line
msgid "Invoice Line"
msgstr ""
#. module: account_anglo_saxon
#: model:ir.model,name:account_anglo_saxon.model_purchase_order
msgid "Purchase Order"
msgstr ""
#. module: account_anglo_saxon
#: model:ir.model,name:account_anglo_saxon.model_product_template
msgid "Product Template"
msgstr ""
#. module: account_anglo_saxon
#: field:product.category,property_account_creditor_price_difference_categ:0
#: field:product.template,property_account_creditor_price_difference:0
msgid "Price Difference Account"
msgstr ""
#. module: account_anglo_saxon
#: model:ir.model,name:account_anglo_saxon.model_account_invoice
msgid "Invoice"
msgstr ""
#. module: account_anglo_saxon
#: model:ir.model,name:account_anglo_saxon.model_stock_picking
msgid "Picking List"
msgstr ""
#. module: account_anglo_saxon
#: help:product.category,property_account_creditor_price_difference_categ:0
#: help:product.template,property_account_creditor_price_difference:0
msgid ""
"This account will be used to value price difference between purchase price "
"and cost price."
msgstr ""

View File

@ -189,7 +189,7 @@ class account_voucher(osv.osv):
if not ids:
return []
if context is None: context = {}
return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
return [(r['id'], (r['number'] or _('Voucher'))) for r in self.read(cr, uid, ids, ['number'], context, load='_classic_write')]
def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
mod_obj = self.pool.get('ir.model.data')

View File

@ -0,0 +1,135 @@
# Slovak translation for openobject-addons
# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2014-04-26 16:22+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Slovak <sk@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-27 05:58+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: association
#: field:profile.association.config.install_modules_wizard,wiki:0
msgid "Wiki"
msgstr ""
#. module: association
#: view:profile.association.config.install_modules_wizard:0
msgid "Event Management"
msgstr ""
#. module: association
#: field:profile.association.config.install_modules_wizard,project_gtd:0
msgid "Getting Things Done"
msgstr ""
#. module: association
#: model:ir.module.module,description:association.module_meta_information
msgid "This module is to create Profile for Associates"
msgstr ""
#. module: association
#: field:profile.association.config.install_modules_wizard,progress:0
msgid "Configuration Progress"
msgstr ""
#. module: association
#: view:profile.association.config.install_modules_wizard:0
msgid ""
"Here are specific applications related to the Association Profile you "
"selected."
msgstr ""
#. module: association
#: view:profile.association.config.install_modules_wizard:0
msgid "title"
msgstr ""
#. module: association
#: help:profile.association.config.install_modules_wizard,event_project:0
msgid "Helps you to manage and organize your events."
msgstr ""
#. module: association
#: field:profile.association.config.install_modules_wizard,config_logo:0
msgid "Image"
msgstr ""
#. module: association
#: help:profile.association.config.install_modules_wizard,hr_expense:0
msgid ""
"Tracks and manages employee expenses, and can automatically re-invoice "
"clients if the expenses are project-related."
msgstr ""
#. module: association
#: help:profile.association.config.install_modules_wizard,project_gtd:0
msgid ""
"GTD is a methodology to efficiently organise yourself and your tasks. This "
"module fully integrates GTD principle with OpenERP's project management."
msgstr ""
#. module: association
#: view:profile.association.config.install_modules_wizard:0
msgid "Resources Management"
msgstr ""
#. module: association
#: model:ir.module.module,shortdesc:association.module_meta_information
msgid "Association profile"
msgstr ""
#. module: association
#: field:profile.association.config.install_modules_wizard,hr_expense:0
msgid "Expenses Tracking"
msgstr ""
#. module: association
#: model:ir.actions.act_window,name:association.action_config_install_module
#: view:profile.association.config.install_modules_wizard:0
msgid "Association Application Configuration"
msgstr ""
#. module: association
#: help:profile.association.config.install_modules_wizard,wiki:0
msgid ""
"Lets you create wiki pages and page groups in order to keep track of "
"business knowledge and share it with and between your employees."
msgstr ""
#. module: association
#: help:profile.association.config.install_modules_wizard,project:0
msgid ""
"Helps you manage your projects and tasks by tracking them, generating "
"plannings, etc..."
msgstr ""
#. module: association
#: model:ir.model,name:association.model_profile_association_config_install_modules_wizard
msgid "profile.association.config.install_modules_wizard"
msgstr ""
#. module: association
#: field:profile.association.config.install_modules_wizard,event_project:0
msgid "Events"
msgstr ""
#. module: association
#: view:profile.association.config.install_modules_wizard:0
#: field:profile.association.config.install_modules_wizard,project:0
msgid "Project Management"
msgstr ""
#. module: association
#: view:profile.association.config.install_modules_wizard:0
msgid "Configure"
msgstr ""

View File

@ -38,5 +38,4 @@ Re-implement openerp's file import system:
'static/src/js/import.js',
],
'qweb': ['static/src/xml/import.xml'],
'test': ['static/test/states.js'],
}

View File

@ -1,6 +0,0 @@
$(document).ready(function () {
module('foo');
test('dummy', function () {
ok(42);
});
});

View File

@ -24,9 +24,6 @@ from openerp.addons.web.controllers.main import manifest_list, module_boot, html
drivers = {}
class Proxy(http.Controller):
def __init__(self):
self.scale = 'closed'
self.scale_weight = 0.0
def get_status(self):
statuses = {}
@ -154,40 +151,6 @@ class Proxy(http.Controller):
"""
print "help_canceled"
@http.route('/hw_proxy/weighting_start', type='json', auth='none', cors='*')
def weighting_start(self):
if self.scale == 'closed':
print "Opening (Fake) Connection to Scale..."
self.scale = 'open'
self.scale_weight = 0.0
time.sleep(0.1)
print "... Scale Open."
else:
print "WARNING: Scale already Connected !!!"
@http.route('/hw_proxy/weighting_read_kg', type='json', auth='none', cors='*')
def weighting_read_kg(self):
if self.scale == 'open':
print "Reading Scale..."
time.sleep(0.025)
self.scale_weight += 0.01
print "... Done."
return self.scale_weight
else:
print "WARNING: Reading closed scale !!!"
return 0.0
@http.route('/hw_proxy/weighting_end', type='json', auth='none', cors='*')
def weighting_end(self):
if self.scale == 'open':
print "Closing Connection to Scale ..."
self.scale = 'closed'
self.scale_weight = 0.0
time.sleep(0.1)
print "... Scale Closed."
else:
print "WARNING: Scale already Closed !!!"
@http.route('/hw_proxy/payment_request', type='json', auth='none', cors='*')
def payment_request(self, price):
"""

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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/>.
#
##############################################################################
import controllers
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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/>.
#
##############################################################################
{
'name': 'Weighting Scale Hardware Driver',
'version': '1.0',
'category': 'Hardware Drivers',
'sequence': 6,
'summary': 'Hardware Driver for Weighting Scales',
'description': """
Barcode Scanner Hardware Driver
================================
This module allows the point of sale to connect to a scale using a USB HSM Serial Scale Interface,
such as the Mettler Toledo Ariva.
""",
'author': 'OpenERP SA',
'depends': ['hw_proxy'],
'test': [
],
'installable': True,
'auto_install': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

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

View File

@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
import logging
import os
import time
from os import listdir
from os.path import join
from threading import Thread, Lock
from select import select
from Queue import Queue, Empty
import openerp
import openerp.addons.hw_proxy.controllers.main as hw_proxy
from openerp import http
from openerp.http import request
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
try:
import serial
except ImportError:
_logger.error('OpenERP module hw_scale depends on the pyserial python module')
serial = None
class Scale(Thread):
def __init__(self):
Thread.__init__(self)
self.lock = Lock()
self.scalelock = Lock()
self.status = {'status':'connecting', 'messages':[]}
self.input_dir = '/dev/serial/by-id/'
self.weight = 0
self.weight_info = 'ok'
self.device = None
def lockedstart(self):
with self.lock:
if not self.isAlive():
self.daemon = True
self.start()
def set_status(self, status, message = None):
if status == self.status['status']:
if message != None and message != self.status['messages'][-1]:
self.status['messages'].append(message)
else:
self.status['status'] = status
if message:
self.status['messages'] = [message]
else:
self.status['messages'] = []
if status == 'error' and message:
_logger.error('Scale Error: '+message)
elif status == 'disconnected' and message:
_logger.warning('Disconnected Scale: '+message)
def get_device(self):
try:
devices = [ device for device in listdir(self.input_dir)]
scales = [ device for device in devices if ('mettler' in device.lower()) or ('toledo' in device.lower()) ]
if len(scales) > 0:
print join(self.input_dir,scales[0])
self.set_status('connected','Connected to '+scales[0])
return serial.Serial(join(self.input_dir,scales[0]),
baudrate = 9600,
bytesize = serial.SEVENBITS,
stopbits = serial.STOPBITS_ONE,
parity = serial.PARITY_EVEN,
#xonxoff = serial.XON,
timeout = 0.01,
writeTimeout= 0.01)
else:
self.set_status('disconnected','Scale Not Found')
return None
except Exception as e:
self.set_status('error',str(e))
return None
def get_weight(self):
self.lockedstart()
return self.weight
def get_weight_info(self):
self.lockedstart()
return self.weight_info
def get_status(self):
self.lockedstart()
return self.status
def read_weight(self):
with self.scalelock:
if self.device:
try:
self.device.write('W')
time.sleep(0.1)
answer = []
while True:
char = self.device.read(1)
if not char:
break
else:
answer.append(char)
if '?' in answer:
stat = ord(answer[answer.index('?')+1])
if stat == 0:
self.weight_info = 'ok'
else:
self.weight_info = []
if stat & 1 :
self.weight_info.append('moving')
if stat & 1 << 1:
self.weight_info.append('over_capacity')
if stat & 1 << 2:
self.weight_info.append('negative')
self.weight = 0.0
if stat & 1 << 3:
self.weight_info.append('outside_zero_capture_range')
if stat & 1 << 4:
self.weight_info.append('center_of_zero')
if stat & 1 << 5:
self.weight_info.append('net_weight')
else:
answer = answer[1:-1]
if 'N' in answer:
answer = answer[0:-1]
try:
self.weight = float(''.join(answer))
except ValueError as v:
self.set_status('error','No data Received, please power-cycle the scale');
self.device = None
except Exception as e:
self.set_status('error',str(e))
self.device = None
def set_zero(self):
with self.scalelock:
if self.device:
try:
self.device.write('Z')
except Exception as e:
self.set_status('error',str(e))
self.device = None
def set_tare(self):
with self.scalelock:
if self.device:
try:
self.device.write('T')
except Exception as e:
self.set_status('error',str(e))
self.device = None
def clear_tare(self):
with self.scalelock:
if self.device:
try:
self.device.write('C')
except Exception as e:
self.set_status('error',str(e))
self.device = None
def run(self):
self.device = None
while True:
if self.device:
self.read_weight()
time.sleep(0.05)
else:
with self.scalelock:
self.device = self.get_device()
if not self.device:
time.sleep(5)
s = Scale()
hw_proxy.drivers['scale'] = s
class ScaleDriver(hw_proxy.Proxy):
@http.route('/hw_proxy/scale_read/', type='json', auth='none', cors='*')
def scale_read(self):
return {'weight':s.get_weight(), 'unit':'kg', 'info':s.get_weight_info()}
@http.route('/hw_proxy/scale_zero/', type='json', auth='none', cors='*')
def scale_zero(self):
s.set_zero()
return True
@http.route('/hw_proxy/scale_tare/', type='json', auth='none', cors='*')
def scale_tare(self):
s.set_tare()
return True
@http.route('/hw_proxy/scale_clear_tare/', type='json', auth='none', cors='*')
def scale_clear_tare(self):
s.clear_tare()
return True

View File

@ -259,7 +259,7 @@
<!-- Short thread: Admin ask, Agrolait answer [DEMO: mark thread as done] -->
<record id="msg_discus1" model="mail.message">
<field name="subject">Feedback about our On Site Assistance</field>
<field name="body"><![CDATA[<p>Hi Virginie,</p><p>I writing to you about our <i>On Site Assistance Service</i> that we delivered to Agrolait last week. Do you have any feedback or remark about our service? I noticed you requested new IP phones. Will it be used for new employees, or did you have any issue with the ones we provided?<br />Best regards,</p>]]></field>
<field name="body"><![CDATA[<p>Hi Virginie,</p><p>I wrote to you about our <i>On Site Assistance Service</i> that we delivered to Agrolait last week. Do you have any feedback or remark about our service? I noticed you requested new IP phones. Will it be used for new employees, or did you have any issue with the ones we provided?<br />Best regards,</p>]]></field>
<field name="type">comment</field>
<field name="subtype_id" ref="mt_comment"/>
<field name="author_id" ref="base.partner_root"/>

View File

@ -49,17 +49,11 @@
<field name="name">Members Analysis</field>
<field name="res_model">report.membership</field>
<field name="view_type">form</field>
<field name="view_mode">graph</field>
<field name="search_view_id" ref="view_report_membership_search"/>
<field name="context">{"search_default_year":1,"search_default_member":1, 'search_default_Revenue':1, 'search_default_this_month':1, 'search_default_salesman':1,'group_by_no_leaf':1}</field>
</record>
<record model="ir.actions.act_window.view" id="action_report_membership_tree_view2">
<field name="sequence" eval="3"/>
<field name="view_mode">graph</field>
<field name="view_id" ref="view_report_membership_graph1"/>
<field name="act_window_id" ref="action_report_membership_tree"/>
</record>
<menuitem name="Members Analysis" parent="base.menu_report_association"
action="action_report_membership_tree"
id="menu_report_membership"

View File

@ -739,12 +739,10 @@
</field>
<group string="Features" >
<group>
<field name="iface_cashdrawer" />
<field name="iface_vkeyboard" />
<field name="iface_invoicing" />
<field name="iface_electronic_scale" />
</group>
<group>
<field name="iface_vkeyboard" />
<field name="iface_big_scrollbars" />
</group>
</group>
@ -752,6 +750,8 @@
<field name="proxy_ip" />
<field name="iface_print_via_proxy" />
<field name="iface_scan_via_proxy" />
<field name="iface_electronic_scale" />
<field name="iface_cashdrawer" />
</group>
<group string="Receipt" >
<field name="receipt_header" placeholder="A custom receipt header message"/>

View File

@ -384,47 +384,17 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
return this.message('help_canceled');
},
//the client is starting to weight
weighting_start: function(){
var ret = new $.Deferred();
if(!this.weighting){
this.weighting = true;
this.message('weighting_start').always(function(){
ret.resolve();
});
}else{
console.error('Weighting already started!!!');
ret.resolve();
}
return ret;
},
// the client has finished weighting products
weighting_end: function(){
var ret = new $.Deferred();
if(this.weighting){
this.weighting = false;
this.message('weighting_end').always(function(){
ret.resolve();
});
}else{
console.error('Weighting already ended !!!');
ret.resolve();
}
return ret;
},
//returns the weight on the scale.
// is called at regular interval (up to 10x/sec) between a weighting_start()
// and a weighting_end()
weighting_read_kg: function(){
// returns the weight on the scale.
scale_read: function(){
var self = this;
var ret = new $.Deferred();
this.message('weighting_read_kg',{})
console.log('scale_read');
this.message('scale_read',{})
.then(function(weight){
console.log(weight)
ret.resolve(self.use_debug_weight ? self.debug_weight : weight);
}, function(){ //failed to read weight
ret.resolve(self.use_debug_weight ? self.debug_weight : 0.0);
ret.resolve(self.use_debug_weight ? self.debug_weight : {weight:0.0, unit:'Kg', info:'ok'});
});
return ret;
},

View File

@ -526,12 +526,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
});
queue.schedule(function(){
return self.pos.proxy.weighting_start()
},{ important: true });
queue.schedule(function(){
return self.pos.proxy.weighting_read_kg().then(function(weight){
self.set_weight(weight);
return self.pos.proxy.scale_read().then(function(weight){
self.set_weight(weight.weight);
});
},{duration:50, repeat: true});
@ -584,9 +580,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
$('body').off('keyup',this.hotkey_handler);
this.pos.proxy_queue.clear();
this.pos.proxy_queue.schedule(function(){
self.pos.proxy.weighting_end();
},{ important: true });
},
});

View File

@ -693,7 +693,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
'open_cashbox',
'print_receipt',
'print_pdf_invoice',
'weighting_read_kg',
'scale_read',
'payment_status',
],
minimized: false,
@ -811,12 +811,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.pos.proxy.add_notification('transaction_end',function(){
self.$('.status.transaction').removeClass('on');
});
self.pos.proxy.add_notification('weighting_start',function(){
self.$('.status.weighting').addClass('on');
});
self.pos.proxy.add_notification('weighting_end',function(){
self.$('.status.weighting').removeClass('on');
});
},
});
@ -876,6 +870,14 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
msg += _t('Printer');
}
}
if( this.pos.config.iface_electronic_scale ){
var scale = status.drivers.scale ? status.drivers.scale.status : false;
if( scale != 'connected' && scale != 'connecting' ){
warning = true;
msg = msg ? msg + ' & ' : msg;
msg += _t('Scale');
}
}
msg = msg ? msg + ' ' + _t('Offline') : msg;
this.set_status(warning ? 'warning' : 'connected', msg);
}else{

View File

@ -692,7 +692,7 @@
<li class="event open_cashbox">Open Cashbox</li>
<li class="event print_receipt">Print Receipt</li>
<li class="event print_pdf_invoice">Print Invoice</li>
<li class="event weighting_read_kg">Read Weighting Scale</li>
<li class="event scale_read">Read Weighting Scale</li>
</ul>
</div>
</div>

View File

@ -9,20 +9,6 @@
<!-- Report for Users' Timesheet and Task Hours per Month -->
<record id="view_report_timesheet_task_user_tree" model="ir.ui.view">
<field name="name">report.timesheet.task.user.tree</field>
<field name="model">report.timesheet.task.user</field>
<field name="arch" type="xml">
<tree string="Timesheet/Task hours Report Per Month" >
<field name="name"/>
<field name="year" invisible="1"/>
<field name="month" invisible="1"/>
<field name="user_id"/>
<field name="timesheet_hrs" widget="float_time" />
<field name="task_hrs" widget="float_time"/>
</tree>
</field>
</record>
<record id="view_report_timesheet_task_user_search" model="ir.ui.view">
<field name="name">report.timesheet.task.user.search</field>
<field name="model">report.timesheet.task.user</field>
@ -55,7 +41,7 @@
<field name="name">Task Hours Per Month</field>
<field name="res_model">report.timesheet.task.user</field>
<field name="view_type">form</field>
<field name="view_mode">tree,graph</field>
<field name="view_mode">graph</field>
<field name="context">{'search_default_year':1,'search_default_month':1, 'search_default_group_user_id':1}</field>
</record>
<menuitem id="menu_timesheet_task_user" parent="hr.menu_hr_reporting_timesheet"

View File

@ -2506,7 +2506,7 @@ class stock_move(osv.osv):
source_location = move.location_dest_id
if source_location.usage != 'internal':
#restrict to scrap from a virtual location because it's meaningless and it may introduce errors in stock ('creating' new products from nowhere)
raise osv.except_osv(_('Error!'), _('Forbidden operation: it is not allowed to scrap products from a virtual location.'))
raise osv.except_osv(_('Error!'), _('Operation Forbidden! it is not allowed to scrap products from a virtual location: %s' %(move.location_id.complete_name)))
move_qty = move.product_qty
uos_qty = quantity / move_qty * move.product_uos_qty
default_val = {

View File

@ -1,43 +0,0 @@
.tour-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1009;
background-color: #000;
opacity: 0.8;
}
.tour-step-backdrop {
position: relative;
z-index: 1011;
}
.tour-step-background {
position: absolute;
z-index: 1010;
background: #fff;
border-radius: 6px;
}
.popover[class*="tour-"] .popover-navigation {
padding: 9px 14px;
}
.popover[class*="tour-"] .popover-navigation *[data-role=end] {
float: right;
}
.popover[class*="tour-"] .popover-navigation *[data-role=prev],
.popover[class*="tour-"] .popover-navigation *[data-role=next],
.popover[class*="tour-"] .popover-navigation *[data-role=end] {
cursor: pointer;
}
.popover[class*="tour-"] .popover-navigation *[data-role=prev].disabled,
.popover[class*="tour-"] .popover-navigation *[data-role=next].disabled,
.popover[class*="tour-"] .popover-navigation *[data-role=end].disabled {
cursor: default;
}
.popover[class*="tour-"].orphan {
position: fixed;
margin-top: 0;
}
.popover[class*="tour-"].orphan .arrow {
display: none;
}

View File

@ -1,559 +0,0 @@
/* ===========================================================
# bootstrap-tour - v0.6.1
# http://bootstraptour.com
# ==============================================================
# Copyright 2012-2013 Ulrich Sossou
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
*/
(function() {
(function($, window) {
var Tour, document;
document = window.document;
Tour = (function() {
function Tour(options) {
this._options = $.extend({
name: "tour",
container: "body",
keyboard: true,
storage: window.localStorage,
debug: false,
backdrop: false,
redirect: true,
orphan: false,
basePath: "",
template: "<div class='popover'> <div class='arrow'></div> <h3 class='popover-title'></h3> <div class='popover-content'></div> <nav class='popover-navigation'> <div class='btn-group'> <button class='btn btn-sm btn-default' data-role='prev'>&laquo; Prev</button> <button class='btn btn-sm btn-default' data-role='next'>Next &raquo;</button> </div> <button class='btn btn-sm btn-default' data-role='end'>End tour</button> </nav> </div>",
afterSetState: function(key, value) {},
afterGetState: function(key, value) {},
afterRemoveState: function(key) {},
onStart: function(tour) {},
onEnd: function(tour) {},
onShow: function(tour) {},
onShown: function(tour) {},
onHide: function(tour) {},
onHidden: function(tour) {},
onNext: function(tour) {},
onPrev: function(tour) {}
}, options);
this._steps = [];
this.setCurrentStep();
this.backdrop = {
overlay: null,
$element: null,
$background: null
};
}
Tour.prototype.setState = function(key, value) {
var keyName;
if (this._options.storage) {
keyName = "" + this._options.name + "_" + key;
this._options.storage.setItem(keyName, value);
return this._options.afterSetState(keyName, value);
} else {
if (this._state == null) {
this._state = {};
}
return this._state[key] = value;
}
};
Tour.prototype.removeState = function(key) {
var keyName;
if (this._options.storage) {
keyName = "" + this._options.name + "_" + key;
this._options.storage.removeItem(keyName);
return this._options.afterRemoveState(keyName);
} else {
if (this._state != null) {
return delete this._state[key];
}
}
};
Tour.prototype.getState = function(key) {
var keyName, value;
if (this._options.storage) {
keyName = "" + this._options.name + "_" + key;
value = this._options.storage.getItem(keyName);
} else {
if (this._state != null) {
value = this._state[key];
}
}
if (value === void 0 || value === "null") {
value = null;
}
this._options.afterGetState(key, value);
return value;
};
Tour.prototype.addSteps = function(steps) {
var step, _i, _len, _results;
_results = [];
for (_i = 0, _len = steps.length; _i < _len; _i++) {
step = steps[_i];
_results.push(this.addStep(step));
}
return _results;
};
Tour.prototype.addStep = function(step) {
return this._steps.push(step);
};
Tour.prototype.getStep = function(i) {
if (this._steps[i] != null) {
return $.extend({
id: "step-" + i,
path: "",
placement: "right",
title: "",
content: "<p></p>",
next: i === this._steps.length - 1 ? -1 : i + 1,
prev: i - 1,
animation: true,
container: this._options.container,
backdrop: this._options.backdrop,
redirect: this._options.redirect,
orphan: this._options.orphan,
template: this._options.template,
onShow: this._options.onShow,
onShown: this._options.onShown,
onHide: this._options.onHide,
onHidden: this._options.onHidden,
onNext: this._options.onNext,
onPrev: this._options.onPrev
}, this._steps[i]);
}
};
Tour.prototype.start = function(force) {
var promise,
_this = this;
if (force == null) {
force = false;
}
if (this.ended() && !force) {
return this._debug("Tour ended, start prevented.");
}
$(document).off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=next]").on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=next]:not(.disabled)", function(e) {
e.preventDefault();
return _this.next();
});
$(document).off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=prev]").on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=prev]:not(.disabled)", function(e) {
e.preventDefault();
return _this.prev();
});
$(document).off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=end]").on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=end]", function(e) {
e.preventDefault();
return _this.end();
});
this._onResize(function() {
return _this.showStep(_this._current);
});
this._setupKeyboardNavigation();
promise = this._makePromise(this._options.onStart != null ? this._options.onStart(this) : void 0);
return this._callOnPromiseDone(promise, this.showStep, this._current);
};
Tour.prototype.next = function() {
var promise;
if (this.ended()) {
return this._debug("Tour ended, next prevented.");
}
promise = this.hideStep(this._current);
return this._callOnPromiseDone(promise, this._showNextStep);
};
Tour.prototype.prev = function() {
var promise;
if (this.ended()) {
return this._debug("Tour ended, prev prevented.");
}
promise = this.hideStep(this._current);
return this._callOnPromiseDone(promise, this._showPrevStep);
};
Tour.prototype.goto = function(i) {
var promise;
if (this.ended()) {
return this._debug("Tour ended, goto prevented.");
}
promise = this.hideStep(this._current);
return this._callOnPromiseDone(promise, this.showStep, i);
};
Tour.prototype.end = function() {
var endHelper, hidePromise,
_this = this;
endHelper = function(e) {
$(document).off("click.tour-" + _this._options.name);
$(document).off("keyup.tour-" + _this._options.name);
$(window).off("resize.tour-" + _this._options.name);
_this.setState("end", "yes");
if (_this._options.onEnd != null) {
return _this._options.onEnd(_this);
}
};
hidePromise = this.hideStep(this._current);
return this._callOnPromiseDone(hidePromise, endHelper);
};
Tour.prototype.ended = function() {
return !!this.getState("end");
};
Tour.prototype.restart = function() {
this.removeState("current_step");
this.removeState("end");
this.setCurrentStep(0);
return this.start();
};
Tour.prototype.hideStep = function(i) {
var hideStepHelper, promise, step,
_this = this;
step = this.getStep(i);
promise = this._makePromise(step.onHide != null ? step.onHide(this, i) : void 0);
hideStepHelper = function(e) {
var $element;
$element = _this._isOrphan(step) ? $("body") : $(step.element);
$element.popover("destroy");
if (step.reflex) {
$element.css("cursor", "").off("click.tour-" + _this._options.name);
}
if (step.backdrop) {
_this._hideBackdrop();
}
if (step.onHidden != null) {
return step.onHidden(_this);
}
};
this._callOnPromiseDone(promise, hideStepHelper);
return promise;
};
Tour.prototype.showStep = function(i) {
var promise, showStepHelper, skipToPrevious, step,
_this = this;
step = this.getStep(i);
if (!step) {
return;
}
skipToPrevious = i < this._current;
promise = this._makePromise(step.onShow != null ? step.onShow(this, i) : void 0);
showStepHelper = function(e) {
var current_path, path;
_this.setCurrentStep(i);
path = $.isFunction(step.path) ? step.path.call() : _this._options.basePath + step.path;
current_path = [document.location.pathname, document.location.hash].join("");
if (_this._isRedirect(path, current_path)) {
_this._redirect(step, path);
return;
}
if (_this._isOrphan(step)) {
if (!step.orphan) {
_this._debug("Skip the orphan step " + (_this._current + 1) + ". Orphan option is false and the element doesn't exist or is hidden.");
if (skipToPrevious) {
_this._showPrevStep();
} else {
_this._showNextStep();
}
return;
}
_this._debug("Show the orphan step " + (_this._current + 1) + ". Orphans option is true.");
}
if (step.backdrop) {
_this._showBackdrop(!_this._isOrphan(step) ? step.element : void 0);
}
_this._showPopover(step, i);
if (step.onShown != null) {
step.onShown(_this);
}
return _this._debug("Step " + (_this._current + 1) + " of " + _this._steps.length);
};
return this._callOnPromiseDone(promise, showStepHelper);
};
Tour.prototype.setCurrentStep = function(value) {
if (value != null) {
this._current = value;
return this.setState("current_step", value);
} else {
this._current = this.getState("current_step");
return this._current = this._current === null ? 0 : parseInt(this._current, 10);
}
};
Tour.prototype._showNextStep = function() {
var promise, showNextStepHelper, step,
_this = this;
step = this.getStep(this._current);
showNextStepHelper = function(e) {
return _this.showStep(step.next);
};
promise = this._makePromise((step.onNext != null ? step.onNext(this) : void 0));
return this._callOnPromiseDone(promise, showNextStepHelper);
};
Tour.prototype._showPrevStep = function() {
var promise, showPrevStepHelper, step,
_this = this;
step = this.getStep(this._current);
showPrevStepHelper = function(e) {
return _this.showStep(step.prev);
};
promise = this._makePromise((step.onPrev != null ? step.onPrev(this) : void 0));
return this._callOnPromiseDone(promise, showPrevStepHelper);
};
Tour.prototype._debug = function(text) {
if (this._options.debug) {
return window.console.log("Bootstrap Tour '" + this._options.name + "' | " + text);
}
};
Tour.prototype._isRedirect = function(path, currentPath) {
return (path != null) && path !== "" && path.replace(/\?.*$/, "").replace(/\/?$/, "") !== currentPath.replace(/\/?$/, "");
};
Tour.prototype._redirect = function(step, path) {
if ($.isFunction(step.redirect)) {
return step.redirect.call(this, path);
} else if (step.redirect === true) {
this._debug("Redirect to " + path);
return document.location.href = path;
}
};
Tour.prototype._isOrphan = function(step) {
return (step.element == null) || !$(step.element).length || $(step.element).is(":hidden");
};
Tour.prototype._showPopover = function(step, i) {
var $element, $navigation, $template, $tip, isOrphan, options,
_this = this;
options = $.extend({}, this._options);
$template = $.isFunction(step.template) ? $(step.template(i, step)) : $(step.template);
$navigation = $template.find(".popover-navigation");
isOrphan = this._isOrphan(step);
if (isOrphan) {
step.element = "body";
step.placement = "top";
$template = $template.addClass("orphan");
}
$element = $(step.element);
$template.addClass("tour-" + this._options.name);
if (step.options) {
$.extend(options, step.options);
}
if (step.reflex) {
$element.css("cursor", "pointer").on("click.tour-" + this._options.name, function(e) {
if (_this._current < _this._steps.length - 1) {
return _this.next();
} else {
return _this.end();
}
});
}
if (step.prev < 0) {
$navigation.find("*[data-role=prev]").addClass("disabled");
}
if (step.next < 0) {
$navigation.find("*[data-role=next]").addClass("disabled");
}
step.template = $template.clone().wrap("<div>").parent().html();
$element.popover({
placement: step.placement,
trigger: "manual",
title: step.title,
content: step.content,
html: true,
animation: step.animation,
container: step.container,
template: step.template,
selector: step.element
}).popover("show");
$tip = $element.data("bs.popover") ? $element.data("bs.popover").tip() : $element.data("popover").tip();
$tip.attr("id", step.id);
this._scrollIntoView($tip);
this._reposition($tip, step);
if (isOrphan) {
return this._center($tip);
}
};
Tour.prototype._reposition = function($tip, step) {
var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;
offsetWidth = $tip[0].offsetWidth;
offsetHeight = $tip[0].offsetHeight;
tipOffset = $tip.offset();
originalLeft = tipOffset.left;
originalTop = tipOffset.top;
offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();
if (offsetBottom < 0) {
tipOffset.top = tipOffset.top + offsetBottom;
}
offsetRight = $("html").outerWidth() - tipOffset.left - $tip.outerWidth();
if (offsetRight < 0) {
tipOffset.left = tipOffset.left + offsetRight;
}
if (tipOffset.top < 0) {
tipOffset.top = 0;
}
if (tipOffset.left < 0) {
tipOffset.left = 0;
}
$tip.offset(tipOffset);
if (step.placement === "bottom" || step.placement === "top") {
if (originalLeft !== tipOffset.left) {
return this._replaceArrow($tip, (tipOffset.left - originalLeft) * 2, offsetWidth, "left");
}
} else {
if (originalTop !== tipOffset.top) {
return this._replaceArrow($tip, (tipOffset.top - originalTop) * 2, offsetHeight, "top");
}
}
};
Tour.prototype._center = function($tip) {
return $tip.css("top", $(window).outerHeight() / 2 - $tip.outerHeight() / 2);
};
Tour.prototype._replaceArrow = function($tip, delta, dimension, position) {
return $tip.find(".arrow").css(position, delta ? 50 * (1 - delta / dimension) + "%" : "");
};
Tour.prototype._scrollIntoView = function(tip) {
return $("html, body").stop().animate({
scrollTop: Math.ceil(tip.offset().top - ($(window).height() / 2))
});
};
Tour.prototype._onResize = function(callback, timeout) {
return $(window).on("resize.tour-" + this._options.name, function() {
clearTimeout(timeout);
return timeout = setTimeout(callback, 100);
});
};
Tour.prototype._setupKeyboardNavigation = function() {
var _this = this;
if (this._options.keyboard) {
return $(document).on("keyup.tour-" + this._options.name, function(e) {
if (!e.which) {
return;
}
switch (e.which) {
case 39:
e.preventDefault();
if (_this._current < _this._steps.length - 1) {
return _this.next();
} else {
return _this.end();
}
break;
case 37:
e.preventDefault();
if (_this._current > 0) {
return _this.prev();
}
break;
case 27:
e.preventDefault();
return _this.end();
}
});
}
};
Tour.prototype._makePromise = function(result) {
if (result && $.isFunction(result.then)) {
return result;
} else {
return null;
}
};
Tour.prototype._callOnPromiseDone = function(promise, cb, arg) {
var _this = this;
if (promise) {
return promise.then(function(e) {
return cb.call(_this, arg);
});
} else {
return cb.call(this, arg);
}
};
Tour.prototype._showBackdrop = function(element) {
if (this.backdrop.overlay !== null) {
return;
}
this._showOverlay();
if (element != null) {
return this._showOverlayElement(element);
}
};
Tour.prototype._hideBackdrop = function() {
if (this.backdrop.overlay === null) {
return;
}
if (this.backdrop.$element) {
this._hideOverlayElement();
}
return this._hideOverlay();
};
Tour.prototype._showOverlay = function() {
this.backdrop = $("<div/>", {
"class": "tour-backdrop"
});
return $("body").append(this.backdrop);
};
Tour.prototype._hideOverlay = function() {
this.backdrop.remove();
return this.backdrop.overlay = null;
};
Tour.prototype._showOverlayElement = function(element) {
var $background, $element, offset;
$element = $(element);
$background = $("<div/>");
offset = $element.offset();
offset.top = offset.top;
offset.left = offset.left;
$background.width($element.innerWidth()).height($element.innerHeight()).addClass("tour-step-background").offset(offset);
$element.addClass("tour-step-backdrop");
$("body").append($background);
this.backdrop.$element = $element;
return this.backdrop.$background = $background;
};
Tour.prototype._hideOverlayElement = function() {
this.backdrop.$element.removeClass("tour-step-backdrop");
this.backdrop.$background.remove();
this.backdrop.$element = null;
return this.backdrop.$background = null;
};
return Tour;
})();
return window.Tour = Tour;
})(jQuery, window);
}).call(this);

View File

@ -518,10 +518,30 @@ div.tour-backdrop {
z-index: 2009;
}
.popover.tour {
z-index: 2010;
.popover.tour.orphan .arrow {
display: none;
}
.popover.tour .popover-navigation {
padding: 9px 14px;
}
.popover.tour .popover-navigation *[data-role="end"] {
float: right;
}
.popover.tour .popover-navigation *[data-role="next"], .popover.tour .popover-navigation *[data-role="end"] {
cursor: pointer;
}
.popover.fixed {
position: fixed;
}
.tour-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1100;
background-color: black;
opacity: 0.8;
}

View File

@ -451,9 +451,26 @@ $editorbar_height: 30px
div.tour-backdrop
z-index: 2009
.popover.tour
z-index: 2010
&.orphan .arrow
display: none
.popover-navigation
padding: 9px 14px
*[data-role="end"]
float: right
*[data-role="next"],*[data-role="end"]
cursor: pointer
.popover.fixed
position: fixed
.tour-backdrop
position: fixed
top: 0
right: 0
bottom: 0
left: 0
z-index: 1100
background-color: #000
opacity: 0.8
// }}}

View File

@ -545,7 +545,7 @@
observer.disconnect();
var editor = this.rte.editor;
var root = editor.element.$;
var root = editor.element && editor.element.$;
editor.destroy();
// FIXME: select editables then filter by dirty?
var defs = this.rte.fetch_editables(root)

View File

@ -362,7 +362,6 @@
},
clean_for_save: function () {
var self = this;
$("*[contentEditable], *[attributeEditable]")
.removeAttr('contentEditable')
.removeAttr('attributeEditable');

View File

@ -4,127 +4,116 @@
var website = openerp.website;
var _t = openerp._t;
website.EditorBar.include({
start: function () {
this.registerTour(new website.Tour.Banner(this));
return this._super();
},
});
website.Tour.Banner = website.Tour.extend({
website.Tour.register({
id: 'banner',
name: "Build a page",
name: _t("Build a page"),
path: '/page/website.homepage',
init: function () {
var self = this;
self.steps = [
{
title: _t("Welcome to your website!"),
content: _t("This tutorial will guide you to build your home page. We will start by adding a banner."),
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
},
{
waitNot: '.popover.tour',
element: 'button[data-action=edit]',
placement: 'bottom',
title: _t("Edit this page"),
content: _t("Every page of your website can be modified through the <i>Edit</i> button."),
popover: { fixed: true },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Insert building blocks"),
content: _t("Click here to insert blocks of content in the page."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:first',
placement: 'bottom',
title: _t("Drag & Drop a Banner"),
content: _t("Drag the Banner block and drop it in your page."),
popover: { fixed: true },
},
{
waitFor: '.oe_overlay_options .oe_options:visible',
element: '#wrap .carousel:first div.carousel-content',
placement: 'top',
title: _t("Customize banner's text"),
content: _t("Click in the text and start editing it."),
sampleText: 'Here, a customized text',
},
{
waitNot: '#wrap .carousel:first div.carousel-content:has(h2:'+
'containsExact('+_t('Your Banner Title')+')):has(h3:'+
'containsExact('+_t('Click to customize this text')+'))',
element: '.oe_snippet_parent:visible',
placement: 'bottom',
title: _t("Get banner properties"),
content: _t("Select the parent container to get the global options of the banner."),
popover: { fixed: true },
},
{
element: '.oe_overlay_options .oe_options:visible',
placement: 'left',
title: _t("Customize the banner"),
content: _t("Customize any block through this menu. Try to change the background of the banner."),
popover: { next: _t("Continue") },
},
{
waitNot: '.popover.tour',
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Add Another Block"),
content: _t("Let's add another building block to your page."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(6)',
placement: 'bottom',
title: _t("Drag & Drop This Block"),
content: _t("Drag the <em>'Features'</em> block and drop it below the banner."),
popover: { fixed: true },
},
{
waitFor: '.oe_overlay_options .oe_options:visible',
element: 'button[data-action=save]',
placement: 'right',
title: _t("Save your modifications"),
content: _t("Publish your page by clicking on the <em>'Save'</em> button."),
popover: { fixed: true },
},
{
waitFor: 'button[data-action=edit]:visible',
title: _t("Good Job!"),
content: _t("Well done, you created your homepage."),
popover: { next: _t("Continue") },
},
{
waitNot: '.popover.tour',
element: 'a[data-action=show-mobile-preview]',
placement: 'bottom',
title: _t("Test Your Mobile Version"),
content: _t("Let's check how your homepage looks like on mobile devices."),
popover: { fixed: true },
},
{
element: '.modal:has(#mobile-viewport) button[data-dismiss=modal]',
placement: 'right',
title: _t("Check Mobile Preview"),
content: _t("Scroll to check rendering and then close the mobile preview."),
popover: { next: _t("Continue") },
},
{
waitNot: '.modal',
element: '#content-menu-button',
placement: 'left',
title: _t("Add new pages and menus"),
content: _t("The 'Content' menu allows you to add pages or add the top menu."),
popover: { next: _t("Close Tutorial") },
},
];
return this._super();
},
steps: [
{
title: _t("Welcome to your website!"),
content: _t("This tutorial will guide you to build your home page. We will start by adding a banner."),
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
},
{
waitNot: '.popover.tour',
element: 'button[data-action=edit]',
placement: 'bottom',
title: _t("Edit this page"),
content: _t("Every page of your website can be modified through the <i>Edit</i> button."),
popover: { fixed: true },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Insert building blocks"),
content: _t("Click here to insert blocks of content in the page."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:first',
placement: 'bottom',
title: _t("Drag & Drop a Banner"),
content: _t("Drag the Banner block and drop it in your page."),
popover: { fixed: true },
},
{
waitFor: '.oe_overlay_options .oe_options:visible',
element: '#wrap .carousel:first div.carousel-content',
placement: 'top',
title: _t("Customize banner's text"),
content: _t("Click in the text and start editing it."),
sampleText: 'Here, a customized text',
},
{
waitNot: '#wrap .carousel:first div.carousel-content:has(h2:'+
'containsExact('+_t('Your Banner Title')+')):has(h3:'+
'containsExact('+_t('Click to customize this text')+'))',
element: '.oe_snippet_parent:visible',
placement: 'bottom',
title: _t("Get banner properties"),
content: _t("Select the parent container to get the global options of the banner."),
popover: { fixed: true },
},
{
element: '.oe_overlay_options .oe_options:visible',
placement: 'left',
title: _t("Customize the banner"),
content: _t("Customize any block through this menu. Try to change the background of the banner."),
popover: { next: _t("Continue") },
},
{
waitNot: '.popover.tour',
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Add Another Block"),
content: _t("Let's add another building block to your page."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(6)',
placement: 'bottom',
title: _t("Drag & Drop This Block"),
content: _t("Drag the <em>'Features'</em> block and drop it below the banner."),
popover: { fixed: true },
},
{
waitFor: '.oe_overlay_options .oe_options:visible',
element: 'button[data-action=save]',
placement: 'right',
title: _t("Save your modifications"),
content: _t("Publish your page by clicking on the <em>'Save'</em> button."),
popover: { fixed: true },
},
{
waitFor: 'button[data-action=edit]:visible',
title: _t("Good Job!"),
content: _t("Well done, you created your homepage."),
popover: { next: _t("Continue") },
},
{
waitNot: '.popover.tour',
element: 'a[data-action=show-mobile-preview]',
placement: 'bottom',
title: _t("Test Your Mobile Version"),
content: _t("Let's check how your homepage looks like on mobile devices."),
popover: { fixed: true },
},
{
element: '.modal:has(#mobile-viewport) button[data-dismiss=modal]',
placement: 'right',
title: _t("Check Mobile Preview"),
content: _t("Scroll to check rendering and then close the mobile preview."),
popover: { next: _t("Continue") },
},
{
waitNot: '.modal',
element: '#content-menu-button',
placement: 'left',
title: _t("Add new pages and menus"),
content: _t("The 'Content' menu allows you to add pages or add the top menu."),
popover: { next: _t("Close Tutorial") },
},
]
});
}());

View File

@ -6,7 +6,7 @@ if (typeof openerp === "undefined") {
var error = "openerp is undefined"
+ "\nhref: " + window.location.href
+ "\nreferrer: " + document.referrer
+ "\nlocalStorage: " + JSON.stringify(window.localStorage);
+ "\nlocalStorage: " + window.localStorage.getItem("tour");
if (typeof $ !== "undefined") {
error += '\n\n' + $("body").html();
}
@ -15,7 +15,7 @@ if (typeof openerp === "undefined") {
var website = window.openerp.website;
// don't rewrite website.Tour in test mode
// don't rewrite T in test mode
if (typeof website.Tour !== "undefined") {
return;
}
@ -23,40 +23,26 @@ if (typeof website.Tour !== "undefined") {
// don't need template to use bootstrap Tour in automatic mode
if (typeof QWeb2 !== "undefined") {
website.add_template_file('/website/static/src/xml/website.tour.xml');
}
// don't need to use bootstrap Tour to launch an automatic tour
function bootstrap_tour_stub () {
if (typeof Tour === "undefined") {
window.Tour = function Tour() {};
Tour.prototype.addSteps = function () {};
Tour.prototype.end = function () {};
Tour.prototype.goto = function () {};
}
}
if (website.EditorBar) {
website.EditorBar.include({
tours: [],
start: function () {
var self = this;
var menu = $('#help-menu');
_.each(this.tours, function (tour) {
_.each(T.tours, function (tour) {
if (tour.mode === "test") {
return;
}
var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
$menuItem.click(function () {
tour.reset();
tour.run();
T.reset();
T.run(tour.id);
});
menu.append($menuItem);
});
return this._super();
},
registerTour: function (tour) {
website.Tour.add(tour);
this.tours.push(tour);
}
});
}
@ -94,351 +80,401 @@ $.ajaxSetup({
}
});
website.Tour = openerp.Class.extend({
steps: [],
defaultDelay: 50, //ms
defaultOverLaps: 5000, //ms
localStorage: window.localStorage,
init: function () {},
/////////////////////////////////////////////////
run: function (automatic) {
this.reset();
var localStorage = window.localStorage;
for (var k in this.localStorage) {
if (!k.indexOf("tour-") && k.indexOf("-test") > -1) return;
}
website.Tour.busy = true;
if (automatic) {
this.localStorage.setItem("tour-"+this.id+"-test-automatic", true);
var T = website.Tour = {
tours: {},
defaultDelay: 50,
retryRunningDelay: 1000,
errorDelay: 5000,
state: null,
$element: null,
timer: null,
testtimer: null,
currentTimer: null,
register: function (tour) {
if (tour.mode !== "test") tour.mode = "tutorial";
T.tours[tour.id] = tour;
},
run: function (tour_id, mode) {
var tour = T.tours[tour_id];
this.time = new Date().getTime();
if (tour.path && !window.location.href.match(new RegExp("("+T.getLang()+")?"+tour.path+"#?$", "i"))) {
var href = "/"+T.getLang()+tour.path;
console.log("Tour Begin from run method (redirection to "+href+")");
T.saveState(tour.id, mode || tour.mode, -1);
window.location.href = href;
} else {
this.localStorage.removeItem("tour-"+this.id+"-test-automatic");
console.log("Tour Begin from run method");
T.saveState(tour.id, mode || tour.mode, 0);
T.running();
}
this.automatic = automatic;
if (this.path) {
// redirect to begin of the tour in function of the language
if (!this.testUrl(this.path+"(#.*)?$")) {
var path = this.path.split('#');
window.location.href = "/"+this.getLang()+path[0] + "#tutorial."+this.id+"=true&" + path.slice(1, path.length).join("#");
return;
}
}
var self = this;
this.localStorage.setItem("tour-"+this.id+"-test", 0);
website.Tour.waitReady.call(this, function () {self._running();});
},
running: function () {
var self = this;
if (+this.localStorage.getItem("tour-"+this.id+"-test") >= this.steps.length-1) {
this.endTour();
registerSteps: function (tour) {
if (tour.register) {
return;
}
tour.register = true;
if (website.Tour.is_busy()) return;
for (var index=0, len=tour.steps.length; index<len; index++) {
var step = tour.steps[index];
step.id = index;
// launch tour with url
this.checkRunningUrl();
// mark tour as busy (only one test running)
if (this.localStorage.getItem("tour-"+this.id+"-test") != null) {
website.Tour.busy = true;
this.automatic = !!this.localStorage.getItem("tour-"+this.id+"-test-automatic");
}
if (!this.testPathUrl()) {
if (this.automatic) {
this.timer = setTimeout(function () {
self.reset();
throw new Error("Wrong url for running " + self.id
+ '\ntestPath: ' + self.testPath
+ '\nhref: ' + window.location.href
+ "\nreferrer: " + document.referrer
);
},this.defaultOverLaps);
if (!step.waitNot && index > 0 && tour.steps[index-1] &&
tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
step.waitNot = '.popover.tour.fade.in:visible';
}
return;
}
var self = this;
website.Tour.waitReady.call(this, function () {self._running();});
},
_running: function () {
var stepId = this.localStorage.getItem("tour-"+this.id+"-test");
if (stepId != null) {
this.registerTour();
this.nextStep(stepId, this.automatic ? this.autoNextStep : null, this.automatic ? this.defaultOverLaps : null);
}
},
reset: function () {
website.Tour.busy = false;
for (var k in this.steps) {
this.steps[k].busy = false;
}
clearTimeout(self.timer);
clearTimeout(self.testtimer);
for (var k in this.localStorage) {
if (!k.indexOf("tour-") || !k.indexOf(this.id)) {
this.localStorage.removeItem(k);
}
}
$('.popover.tour').remove();
},
getLang: function () {
return $("html").attr("lang").replace(/-/, '_');
},
testUrl: function (url) {
return new RegExp("(/"+this.getLang()+")?"+url, "i").test(window.location.href);
},
testPathUrl: function () {
if (!this.testPath || this.testUrl(this.testPath)) return true;
},
checkRunningUrl: function () {
if (window.location.hash.indexOf("tutorial."+this.id+"=true") > -1) {
this.localStorage.setItem("tour-"+this.id+"-test", 0);
window.location.hash = window.location.hash.replace(/tutorial.+=true&?/, '');
}
},
registerTour: function () {
if (this.automatic) {
bootstrap_tour_stub();
}
this.tour = new Tour({
name: this.id,
storage: this.tourStorage,
keyboard: false,
template: this.popover(),
onHide: function () {
window.scrollTo(0, 0);
}
});
this.registerSteps();
},
registerSteps: function () {
for (var index=0, len=this.steps.length; index<len; index++) {
var step = this.steps[index];
step.stepId = step.stepId || ""+index;
if (!step.waitNot && index > 0 && this.steps[index-1] &&
this.steps[index-1].popover && this.steps[index-1].popover.next) {
step.waitNot = '.popover.tour:visible';
}
if (!step.waitFor && index > 0 && this.steps[index-1].snippet) {
if (!step.waitFor && index > 0 && tour.steps[index-1].snippet) {
step.waitFor = '.oe_overlay_options .oe_options:visible';
}
step._title = step._title || step.title;
step.title = this.popoverTitle({ title: step._title });
step.template = step.template || this.popover( step.popover );
if (!step.element) step.orphan = true;
if (step.snippet) {
var snippet = step.element && step.element.match(/#oe_snippets (.*) \.oe_snippet_thumbnail/);
if (snippet) {
step.snippet = snippet[1];
} else if (step.snippet) {
step.element = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail';
}
if (!step.element) {
step.element = "body";
step.orphan = true;
step.backdrop = true;
}
}
if (this.steps[index-1] &&
this.steps[index-1].popover && this.steps[index-1].popover.next) {
if (tour.steps[index-1] &&
tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
var step = {
stepId: ""+index,
waitNot: '.popover.tour:visible'
_title: "",
id: index,
waitNot: '.popover.tour.fade.in:visible'
};
this.steps.push(step);
tour.steps.push(step);
}
this.tour.addSteps(this.steps);
// rendering bootstrap tour and popover
if (tour.mode !== "test") {
for (var index=0, len=tour.steps.length; index<len; index++) {
var step = tour.steps[index];
step._title = step._title || step.title;
step.title = T.popoverTitle(tour, { title: step._title });
step.template = step.template || T.popover( step.popover );
}
}
},
popoverTitle: function (options) {
try {
return openerp.qweb.render('website.tour_popover_title', options);
} catch (e) {
if (!this.automatic) throw e;
return options.title;
closePopover: function () {
if (T.$element) {
T.$element.popover('destroy');
T.$element.removeData("tour");
T.$element.removeData("tour-step");
$(".tour-backdrop").remove();
$(".popover.tour").remove();
T.$element = null;
}
},
autoTogglePopover: function () {
var state = T.getState();
var step = state.step;
if (T.$element &&
T.$element.is(":visible") &&
T.$element.data("tour") === state.id &&
T.$element.data("tour-step") === step.id) {
T.repositionPopover();
return;
}
if (step.busy) {
return;
}
T.closePopover();
var $element = $(step.element).first();
if (!step.element || !$element.size() || !$element.is(":visible")) {
return;
}
T.$element = $element;
$element.data("tour", state.id);
$element.data("tour-step", step.id);
$element.popover({
placement: step.placement || "auto",
animation: true,
trigger: "manual",
title: step.title,
content: step.content,
html: true,
container: "body",
template: step.template,
orphan: step.orphan
}).popover("show");
var $tip = $element.data("bs.popover").tip();
// add popover style (orphan, static, backdrop)
if (step.orphan) {
$tip.addClass("orphan");
}
var node = $element[0];
var css;
do {
css = window.getComputedStyle(node);
if (!css || css.position == "fixed") {
$tip.addClass("fixed");
break;
}
} while ((node = node.parentNode) && node !== document);
if (step.backdrop) {
$("body").append('<div class="tour-backdrop"></div>');
}
if (step.backdrop || $element.parents("#website-top-navbar, .modal").size()) {
$tip.css("z-index", 2010);
}
// button click event
$tip.find("button")
.one("click", function () {
step.busy = true;
if (!$(this).is("[data-role='next']")) {
clearTimeout(T.timer);
T.endTour();
}
T.closePopover();
});
T.repositionPopover();
},
repositionPopover: function() {
var popover = T.$element.data("bs.popover");
var $tip = T.$element.data("bs.popover").tip();
if (popover.options.orphan) {
return $tip.css("top", $(window).outerHeight() / 2 - $tip.outerHeight() / 2);
}
var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;
offsetWidth = $tip[0].offsetWidth;
offsetHeight = $tip[0].offsetHeight;
tipOffset = $tip.offset();
originalLeft = tipOffset.left;
originalTop = tipOffset.top;
offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();
if (offsetBottom < 0) {
tipOffset.top = tipOffset.top + offsetBottom;
}
offsetRight = $("html").outerWidth() - tipOffset.left - $tip.outerWidth();
if (offsetRight < 0) {
tipOffset.left = tipOffset.left + offsetRight;
}
if (tipOffset.top < 0) {
tipOffset.top = 0;
}
if (tipOffset.left < 0) {
tipOffset.left = 0;
}
$tip.offset(tipOffset);
if (popover.options.placement === "bottom" || popover.options.placement === "top") {
var left = T.$element.offset().left + T.$element.outerWidth()/2 - tipOffset.left;
$tip.find(".arrow").css("left", left ? left + "px" : "");
} else if (popover.options.placement !== "auto") {
var top = T.$element.offset().top + T.$element.outerHeight()/2 - tipOffset.top;
$tip.find(".arrow").css("top", top ? top + "px" : "");
}
},
popoverTitle: function (tour, options) {
return openerp.qweb ? openerp.qweb.render('website.tour_popover_title', options) : options.title;
},
popover: function (options) {
try {
return openerp.qweb.render('website.tour_popover', options);
} catch (e) {
if (!this.automatic) throw e;
return "";
}
return openerp.qweb ? openerp.qweb.render('website.tour_popover', options) : options.title;
},
getLang: function () {
return $("html").attr("lang").replace(/-/, '_');
},
getState: function () {
var state = JSON.parse(localStorage.getItem("tour") || 'false') || {};
if (state) { this.time = state.time; }
var tour_id,mode,step_id;
if (!state.id && window.location.href.indexOf("#tutorial.") > -1) {
state = {
"id": window.location.href.match(/#tutorial\.(.*)=true/)[1],
"mode": "tutorial",
"step_id": 0
};
window.location.hash = "";
console.log("Tour Begin from url hash");
T.saveState(state.id, state.mode, state.step_id);
}
if (!state.id) {
return;
}
state.tour = T.tours[state.id];
state.step = state.tour && state.tour.steps[state.step_id === -1 ? 0 : state.step_id];
return state;
},
error: function (step, message) {
var state = T.getState();
message += '\n tour: ' + state.id
+ '\n step: ' + step.id + ": '" + (step._title || step.title) + "'"
+ '\n href: ' + window.location.href
+ '\n referrer: ' + document.referrer
+ '\n element: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden")))
+ '\n waitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size())
+ '\n waitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size())
+ "\n localStorage: " + JSON.stringify(localStorage)
+ '\n\n' + $("body").html();
T.reset();
throw new Error(message);
},
lists: function () {
var tour_ids = [];
for (var k in T.tours) {
tour_ids.push(k);
}
return tour_ids;
},
saveState: function (tour_id, mode, step_id) {
localStorage.setItem("tour", JSON.stringify({"id":tour_id, "mode":mode, "step_id":step_id || 0, "time": this.time}));
},
reset: function () {
var state = T.getState();
if (state) {
for (var k in state.tour.steps) {
state.tour.steps[k].busy = false;
}
}
localStorage.removeItem("tour");
clearTimeout(T.timer);
clearTimeout(T.testtimer);
T.closePopover();
},
running: function () {
function run () {
var state = T.getState();
if (!state) return;
if (state.tour) {
console.log("Tour '"+state.id+"' is running");
T.registerSteps(state.tour);
T.nextStep();
} else {
console.log("Tour '"+state.id+"' wait for running (tour undefined)");
setTimeout(T.running, state.mode === "test" ? T.defaultDelay : T.retryRunningDelay);
}
}
setTimeout(function () {
if ($.ajaxBusy) {
$(document).ajaxStop(run);
} else {
run();
}
},0);
},
timer: null,
testtimer: null,
check: function (step) {
return (step &&
(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) &&
(!step.waitNot || !$(step.waitNot).size()) &&
(!step.waitFor || $(step.waitFor).size()));
},
waitNextStep: function (step, callback, overlaps) {
var self = this;
waitNextStep: function () {
var state = T.getState();
var time = new Date().getTime();
var timer;
var next = state.tour.steps[state.step.id+1];
var overlaps = state.mode === "test" ? T.errorDelay : 0;
window.onbeforeunload = function () {
clearTimeout(self.timer);
clearTimeout(self.testtimer);
clearTimeout(T.timer);
clearTimeout(T.testtimer);
};
// check popover activity
$(".popover.tour button")
.off()
.on("click", function () {
var help = $("#help-menu-button");
var offset = help.offset();
var left = (offset.left > 0) ? (offset.left + help.width()) : offset.left;
var top = (help.height() > 0) ? (offset.top + help.height()) : offset.top;
if ($(this).is("[data-role='next']") && step.element) {
$(".popover.tour").remove();
}
if (step.busy) return;
if (!$(this).is("[data-role='next']") || !step.element) {
$('.popover.tour')
.animate({
left: left,
top: top,
width: '1px',
height: '1px',
opacity: 0
}, 800,
function(){
$(".popover.tour").remove();
clearTimeout(self.timer);
step.busy = true;
self.tour.end();
self.endTour(callback);
});
}
});
function checkNext () {
clearTimeout(self.timer);
if (step.busy) return;
if (self.check(step)) {
step.busy = true;
T.autoTogglePopover();
clearTimeout(T.timer);
if (T.check(next)) {
clearTimeout(T.currentTimer);
// use an other timeout for cke dom loading
setTimeout(function () {
self.nextStep(step.stepId, callback, overlaps);
}, self.defaultDelay);
T.nextStep(next);
}, T.defaultDelay);
} else if (!overlaps || new Date().getTime() - time < overlaps) {
if (self.current.element) {
var $popover = $(".popover.tour");
if(!$(self.current.element).is(":visible")) {
$popover.data("hide", true).fadeOut(300);
} else if($popover.data("hide")) {
$popover.data("hide", false).fadeIn(150);
}
}
self.timer = setTimeout(checkNext, self.defaultDelay);
T.timer = setTimeout(checkNext, T.defaultDelay);
} else {
self.reset();
throw new Error("Can't arrive to step " + step.stepId + ": '" + step._title + "'"
+ '\nhref: ' + window.location.href
+ '\nelement: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden")))
+ '\nwaitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size())
+ '\nwaitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size())
+ '\n\n' + $("body").html()
);
T.error(next, "Can't reach the next step");
}
}
checkNext();
},
step: function (stepId) {
var steps = this.steps.slice(0,this.steps.length),
step;
while (step = steps.shift()) {
if (!stepId || step.stepId === stepId)
return step;
nextStep: function (step) {
var state = T.getState();
if (!state) {
return;
}
return null;
},
next: function (stepId) {
var steps = this.steps.slice(0,this.steps.length),
step, next, index=0;
while (step = steps.shift()) {
if (!stepId || step.stepId === stepId) {
// clear popover (fix for boostrap tour if the element is removed before destroy popover)
$(".popover.tour").remove();
// go to step in bootstrap tour
this.tour.goto(index);
if (step.onload) step.onload();
next = steps.shift();
break;
}
index++;
step = step || state.step;
T.saveState(state.id, state.mode, step.id);
if (step.id !== state.step_id) {
console.log("Tour Step: '" + (step._title || step.title) + "' (" + (new Date().getTime() - this.time) + "ms)");
}
return next;
},
nextStep: function (stepId, callback, overlaps) {
var self = this;
if (!this.localStorage.getItem("tour-"+this.id+"-test")) return;
this.localStorage.setItem("tour-"+this.id+"-test", stepId || 0);
T.autoTogglePopover(true);
this.current = this.step(stepId);
var next = this.next(stepId);
if (step.onload) {
step.onload();
}
var next = state.tour.steps[step.id+1];
if (next) {
setTimeout(function () {
self.waitNextStep(next, callback, overlaps);
if (callback) setTimeout(function(){callback.call(self, next);}, self.defaultDelay);
}, next && next.wait || 0);
T.waitNextStep();
if (state.mode === "test") {
setTimeout(function(){
T.autoNextStep(state.tour, step);
}, T.defaultDelay);
}
}, next.wait || 0);
} else {
this.endTour();
T.endTour();
}
},
endTour: function () {
var test = parseInt(this.localStorage.getItem("tour-"+this.id+"-test"),10) >= this.steps.length-1;
this.reset();
var state = T.getState();
var test = state.step.id >= state.tour.steps.length-1;
T.reset();
if (test) {
console.log('ok');
} else {
console.log('error');
}
},
autoNextStep: function () {
var self = this;
clearTimeout(self.testtimer);
autoNextStep: function (tour, step) {
clearTimeout(T.testtimer);
function autoStep () {
var step = self.current;
if (!step) return;
if (step.autoComplete) {
step.autoComplete(tour);
}
var $popover = $(".popover.tour");
if ($popover.find("button[data-role='next']:visible").size()) {
$popover.find("button[data-role='next']:visible").click();
$popover.remove();
}
$(".popover.tour [data-role='next']").click();
var $element = $(step.element);
if (!$element.size()) return;
if (step.snippet) {
var selector = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail';
self.autoDragAndDropSnippet(selector);
} else if (step.element.match(/#oe_snippets .* \.oe_snippet_thumbnail/)) {
self.autoDragAndDropSnippet($element);
T.autoDragAndDropSnippet($element);
} else if ($element.is(":visible")) {
@ -473,7 +509,7 @@ website.Tour = openerp.Class.extend({
}
}
self.testtimer = setTimeout(autoStep, 100);
T.testtimer = setTimeout(autoStep, 100);
},
autoDragAndDropSnippet: function (selector) {
var $thumbnail = $(selector).first();
@ -483,64 +519,11 @@ website.Tour = openerp.Class.extend({
var $dropZone = $(".oe_drop_zone").first();
var dropPosition = $dropZone.position();
$dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top }));
},
});
website.Tour.tours = {};
website.Tour.busy = false;
website.Tour.add = function (tour) {
website.Tour.waitReady(function () {
tour = tour.id ? tour : new tour();
if (!website.Tour.tours[tour.id]) {
website.Tour.tours[tour.id] = tour;
tour.running();
}
});
};
website.Tour.get = function (id) {
return website.Tour.tours[id];
};
website.Tour.each = function (callback) {
website.Tour.waitReady(function () {
for (var k in website.Tour.tours) {
callback.call(website.Tour.tours[k]);
}
});
};
website.Tour.waitReady = function (callback) {
var self = this;
$(document).ready(function () {
if ($.ajaxBusy) {
$(document).ajaxStop(function() {
setTimeout(function () {
callback.call(self);
},0);
});
}
else {
setTimeout(function () {
callback.call(self);
},0);
}
});
};
website.Tour.run_test = function (id) {
website.Tour.waitReady(function () {
if (!website.Tour.is_busy()) {
website.Tour.tours[id].run(true);
}
});
};
website.Tour.is_busy = function () {
for (var k in this.localStorage) {
if (!k.indexOf("tour-")) {
return k;
}
}
return website.Tour.busy;
};
//$(document).ready(T.running);
website.ready().then(T.running);
}());

View File

@ -8,6 +8,6 @@ class TestUi(openerp.tests.HttpCase):
self.phantom_js("/", "console.log('ok')", "openerp.website.editor", login='admin')
def test_04_admin_tour_banner(self):
self.phantom_js("/", "openerp.website.Tour.run_test('banner')", "openerp.website.Tour.tours.banner", login='admin')
self.phantom_js("/", "openerp.website.Tour.run('banner', 'test')", "openerp.website.Tour.tours.banner", login='admin')
# vim:et:

View File

@ -256,7 +256,6 @@
<xpath expr='//script[@src="/web/static/lib/bootstrap/js/bootstrap.js"]' position="before">
<link rel='stylesheet' href='/website/static/src/css/snippets.css'/>
<link rel='stylesheet' href='/website/static/src/css/editor.css'/>
<link rel='stylesheet' href='/website/static/lib/bootstrap-tour/bootstrap-tour.css'/>
<link rel="stylesheet" href="/web/static/lib/select2/select2.css"/>
@ -268,7 +267,6 @@
<script t-if="not translatable" type="text/javascript" src="/website/static/lib/ace/ace.js"></script>
<script type="text/javascript" src="/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<script type="text/javascript" src="/website/static/lib/bootstrap-tour/bootstrap-tour.js"></script>
<!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
<script type="text/javascript" src="/website/static/lib//jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js"></script>
<script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script>

View File

@ -4,117 +4,105 @@
var website = openerp.website;
var _t = openerp._t;
website.EditorBar.include({
start: function () {
this.registerTour(new website.Tour.Blog(this));
return this._super();
},
});
website.Tour.Blog = website.Tour.extend({
id: 'blog',
name: "Create a blog post",
testPath: '/(blog|blogpost)',
init: function () {
var self = this;
self.steps = [
{
title: _t("New Blog Post"),
content: _t("Let's go through the first steps to write beautiful blog posts."),
popover: { next: _t("Start Tutorial"), end: _t("Skip") },
},
{
element: '#content-menu-button',
placement: 'left',
title: _t("Add Content"),
content: _t("Use this <em>'Content'</em> menu to create a new blog post like any other document (page, menu, products, event, ...)."),
popover: { fixed: true },
},
{
element: 'a[data-action=new_blog_post]',
placement: 'left',
title: _t("New Blog Post"),
content: _t("Select this menu item to create a new blog post."),
popover: { fixed: true },
},
{
element: '.modal:has(#editor_new_blog) button.btn-primary',
placement: 'right',
title: _t("Create Blog Post"),
content: _t("Click <em>Continue</em> to create the blog post."),
},
{
waitFor: 'body:has(button[data-action=save]:visible):has(.js_blog)',
title: _t("Blog Post Created"),
content: _t("This is your new blog post. Let's edit it."),
popover: { next: _t("Continue") },
},
{
element: 'h1[data-oe-expression="blog_post.name"]',
placement: 'bottom',
sampleText: 'New Blog',
title: _t("Set a Title"),
content: _t("Click on this area and set a catchy title for your blog post."),
},
{
waitNot: '#wrap h1[data-oe-model="blog.post"]:contains("Blog Post Title")',
element: 'button[data-action=snippet]',
placement: 'left',
title: _t("Layout Your Blog Post"),
content: _t("Use well designed building blocks to structure the content of your blog. Click 'Insert Blocks' to add new content."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(2)',
placement: 'bottom',
title: _t("Drag & Drop a Block"),
content: _t("Drag this block and drop it in your page."),
popover: { fixed: true },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Add Another Block"),
content: _t("Let's add another block to your post."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(4)',
placement: 'bottom',
title: _t("Drag & Drop a block"),
content: _t("Drag this block and drop it below the image block."),
popover: { fixed: true },
},
{
element: '.oe_active .oe_snippet_remove',
placement: 'top',
title: _t("Delete the block"),
content: _t("From this toolbar you can move, duplicate or delete the selected zone. Click on the garbage can image to delete the block. Or click on the Title and delete it."),
},
{
waitNot: '.oe_active .oe_snippet_remove:visible',
element: 'button[data-action=save]',
placement: 'right',
title: _t("Save Your Blog"),
content: _t("Click the <em>Save</em> button to record changes on the page."),
popover: { fixed: true },
},
{
waitFor: 'button[data-action=edit]:visible',
element: 'button.btn-danger.js_publish_btn',
placement: 'top',
title: _t("Publish Your Post"),
content: _t("Your blog post is not yet published. You can update this draft version and publish it once you are ready."),
},
{
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
title: "Thanks!",
content: _t("This tutorial is finished. To discover more features, improve the content of this page and try the <em>Promote</em> button in the top right menu."),
popover: { next: _t("Close Tutorial") },
},
];
return this._super();
},
website.Tour.register({
id: 'blog',
name: _t("Create a blog post"),
steps: [
{
title: _t("New Blog Post"),
content: _t("Let's go through the first steps to write beautiful blog posts."),
popover: { next: _t("Start Tutorial"), end: _t("Skip") },
},
{
element: '#content-menu-button',
placement: 'left',
title: _t("Add Content"),
content: _t("Use this <em>'Content'</em> menu to create a new blog post like any other document (page, menu, products, event, ...)."),
popover: { fixed: true },
},
{
element: 'a[data-action=new_blog_post]',
placement: 'left',
title: _t("New Blog Post"),
content: _t("Select this menu item to create a new blog post."),
popover: { fixed: true },
},
{
element: '.modal:has(#editor_new_blog) button.btn-primary',
placement: 'right',
title: _t("Create Blog Post"),
content: _t("Click <em>Continue</em> to create the blog post."),
},
{
waitFor: 'body:has(button[data-action=save]:visible):has(.js_blog)',
title: _t("Blog Post Created"),
content: _t("This is your new blog post. Let's edit it."),
popover: { next: _t("Continue") },
},
{
element: 'h1[data-oe-expression="blog_post.name"]',
placement: 'bottom',
sampleText: 'New Blog',
title: _t("Set a Title"),
content: _t("Click on this area and set a catchy title for your blog post."),
},
{
waitNot: '#wrap h1[data-oe-model="blog.post"]:contains("Blog Post Title")',
element: 'button[data-action=snippet]',
placement: 'left',
title: _t("Layout Your Blog Post"),
content: _t("Use well designed building blocks to structure the content of your blog. Click 'Insert Blocks' to add new content."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(2)',
placement: 'bottom',
title: _t("Drag & Drop a Block"),
content: _t("Drag this block and drop it in your page."),
popover: { fixed: true },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Add Another Block"),
content: _t("Let's add another block to your post."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(4)',
placement: 'bottom',
title: _t("Drag & Drop a block"),
content: _t("Drag this block and drop it below the image block."),
popover: { fixed: true },
},
{
element: '.oe_active .oe_snippet_remove',
placement: 'top',
title: _t("Delete the block"),
content: _t("From this toolbar you can move, duplicate or delete the selected zone. Click on the garbage can image to delete the block. Or click on the Title and delete it."),
},
{
waitNot: '.oe_active .oe_snippet_remove:visible',
element: 'button[data-action=save]',
placement: 'right',
title: _t("Save Your Blog"),
content: _t("Click the <em>Save</em> button to record changes on the page."),
popover: { fixed: true },
},
{
waitFor: 'button[data-action=edit]:visible',
element: 'button.btn-danger.js_publish_btn',
placement: 'top',
title: _t("Publish Your Post"),
content: _t("Your blog post is not yet published. You can update this draft version and publish it once you are ready."),
},
{
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
title: "Thanks!",
content: _t("This tutorial is finished. To discover more features, improve the content of this page and try the <em>Promote</em> button in the top right menu."),
popover: { next: _t("Close Tutorial") },
},
]
});
}());

View File

@ -2,5 +2,5 @@ import openerp.tests
class TestUi(openerp.tests.HttpCase):
def test_admin(self):
self.phantom_js("/", "openerp.website.Tour.run_test('blog')", "openerp.website.Tour")
self.phantom_js("/", "openerp.website.Tour.run('blog', 'test')", "openerp.website.Tour.tours.blog")

View File

@ -4,115 +4,103 @@
var website = openerp.website;
var _t = openerp._t;
website.EditorBar.include({
start: function () {
this.registerTour(new website.EventTour(this));
return this._super();
},
});
website.EventTour = website.Tour.extend({
id: 'event',
name: "Create an event",
testPath: '/event(/[0-9]+/register)?',
init: function (editor) {
var self = this;
self.steps = [
{
title: _t("Create an Event"),
content: _t("Let's go through the first steps to publish a new event."),
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
},
{
element: '#content-menu-button',
placement: 'left',
title: _t("Add Content"),
content: _t("The <em>Content</em> menu allows you to create new pages, events, menus, etc."),
popover: { fixed: true },
},
{
element: 'a[data-action=new_event]',
placement: 'left',
title: _t("New Event"),
content: _t("Click here to create a new event."),
popover: { fixed: true },
},
{
element: '.modal #editor_new_event input[type=text]',
sampleText: 'Advanced Technical Training',
placement: 'right',
title: _t("Create an Event Name"),
content: _t("Create a name for your new event and click <em>'Continue'</em>. e.g: Technical Training"),
},
{
waitNot: '.modal input[type=text]:not([value!=""])',
element: '.modal button.btn-primary',
placement: 'right',
title: _t("Create Event"),
content: _t("Click <em>Continue</em> to create the event."),
},
{
waitFor: 'body:has(button[data-action=save]:visible):has(.js_event)',
title: _t("New Event Created"),
content: _t("This is your new event page. We will edit the event presentation page."),
popover: { next: _t("Continue") },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Layout your event"),
content: _t("Insert blocks to layout the body of your event."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(2)',
placement: 'bottom',
title: _t("Drag & Drop a block"),
content: _t("Drag the 'Image-Text' block and drop it in your page."),
popover: { fixed: true },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Layout your event"),
content: _t("Insert another block to your event."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(4)',
placement: 'bottom',
title: _t("Drag & Drop a block"),
content: _t("Drag the 'Text Block' in your event page."),
popover: { fixed: true },
},
{
element: 'button[data-action=save]',
placement: 'right',
title: _t("Save your modifications"),
content: _t("Once you click on save, your event is updated."),
popover: { fixed: true },
},
{
waitFor: 'button[data-action=edit]:visible',
element: 'button.btn-danger.js_publish_btn',
placement: 'top',
title: _t("Publish your event"),
content: _t("Click to publish your event."),
},
{
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
element: '.js_publish_management button[data-toggle="dropdown"]',
placement: 'left',
title: _t("Customize your event"),
content: _t("Click here to customize your event further."),
},
{
element: '.js_publish_management ul>li>a:last:visible',
},
];
return this._super();
}
website.Tour.register({
id: 'event',
name: _t("Create an event"),
steps: [
{
title: _t("Create an Event"),
content: _t("Let's go through the first steps to publish a new event."),
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
},
{
element: '#content-menu-button',
placement: 'left',
title: _t("Add Content"),
content: _t("The <em>Content</em> menu allows you to create new pages, events, menus, etc."),
popover: { fixed: true },
},
{
element: 'a[data-action=new_event]',
placement: 'left',
title: _t("New Event"),
content: _t("Click here to create a new event."),
popover: { fixed: true },
},
{
element: '.modal #editor_new_event input[type=text]',
sampleText: 'Advanced Technical Training',
placement: 'right',
title: _t("Create an Event Name"),
content: _t("Create a name for your new event and click <em>'Continue'</em>. e.g: Technical Training"),
},
{
waitNot: '.modal input[type=text]:not([value!=""])',
element: '.modal button.btn-primary',
placement: 'right',
title: _t("Create Event"),
content: _t("Click <em>Continue</em> to create the event."),
},
{
waitFor: 'body:has(button[data-action=save]:visible):has(.js_event)',
title: _t("New Event Created"),
content: _t("This is your new event page. We will edit the event presentation page."),
popover: { next: _t("Continue") },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Layout your event"),
content: _t("Insert blocks to layout the body of your event."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(2)',
placement: 'bottom',
title: _t("Drag & Drop a block"),
content: _t("Drag the 'Image-Text' block and drop it in your page."),
popover: { fixed: true },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Layout your event"),
content: _t("Insert another block to your event."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(4)',
placement: 'bottom',
title: _t("Drag & Drop a block"),
content: _t("Drag the 'Text Block' in your event page."),
popover: { fixed: true },
},
{
element: 'button[data-action=save]',
placement: 'right',
title: _t("Save your modifications"),
content: _t("Once you click on save, your event is updated."),
popover: { fixed: true },
},
{
waitFor: 'button[data-action=edit]:visible',
element: 'button.btn-danger.js_publish_btn',
placement: 'top',
title: _t("Publish your event"),
content: _t("Click to publish your event."),
},
{
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
element: '.js_publish_management button[data-toggle="dropdown"]',
placement: 'left',
title: _t("Customize your event"),
content: _t("Click here to customize your event further."),
},
{
element: '.js_publish_management ul>li>a:last:visible',
},
]
});
}());

View File

@ -2,5 +2,5 @@ import openerp.tests
class TestUi(openerp.tests.HttpCase):
def test_admin(self):
self.phantom_js("/", "openerp.website.Tour.run_test('event')", "openerp.website.Tour")
self.phantom_js("/", "openerp.website.Tour.run('event', 'test')", "openerp.website.Tour.tours.event")

View File

@ -3,77 +3,63 @@
var website = openerp.website;
website.Tour.EventSaleTest = website.Tour.extend({
id: 'event_buy_tickets',
website.Tour.register({
id: 'event_buy_tickets',
name: "Try to buy tickets for event",
path: '/event',
init: function () {
var self = this;
self.steps = [
{
title: "select event",
element: 'a[href*="/event"]:contains("Open Days in Los Angeles")',
mode: 'test',
steps: [
{
title: "select event",
element: 'a[href*="/event"]:contains("Conference on Business Applications"):first',
},
{
waitNot: 'a[href*="/event"]:contains("Conference on Business Applications")',
title: "select 2 Standard tickets",
element: 'select:eq(0)',
sampleText: '2',
},
{
title: "select 3 VIP tickets",
waitFor: 'select:eq(0) option:contains(2):selected',
element: 'select:eq(1)',
sampleText: '3',
},
{
title: "Order Now",
waitFor: 'select:eq(1) option:contains(3):selected',
element: '.btn-primary:contains("Order Now")',
},
{
title: "Complete checkout",
waitFor: '#top_menu .my_cart_quantity:contains(5)',
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
autoComplete: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
if ($("input[name='email']").val() === "")
$("input[name='email']").val("website_event_sale_test_shoptest@websiteeventsaletest.optenerp.com");
$("input[name='phone']").val("123");
$("input[name='street']").val("123");
$("input[name='city']").val("123");
$("input[name='zip']").val("123");
$("select[name='country_id']").val("21");
},
{
title: "go to register page",
waitNot: 'a[href*="/event"]:contains("Functional Webinar")',
onload: function () {
// use onload if website_event_track is installed
if (!$('form:contains("Ticket Type")').size()) {
window.location.href = $('a[href*="/event"][href*="/register"]').attr("href");
}
},
},
{
title: "select 2 Standard tickets",
element: 'select[name="ticket-1"]',
sampleText: '2',
},
{
title: "select 3 VIP tickets",
waitFor: 'select[name="ticket-1"] option:contains(2):selected',
element: 'select[name="ticket-2"]',
sampleText: '3',
},
{
title: "Order Now",
waitFor: 'select[name="ticket-2"] option:contains(3):selected',
element: '.btn-primary:contains("Order Now")',
},
{
title: "Complete checkout",
waitFor: '#top_menu .my_cart_quantity:contains(5)',
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
onload: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
if ($("input[name='email']").val() === "")
$("input[name='email']").val("website_event_sale_test_shoptest@websiteeventsaletest.optenerp.com");
$("input[name='phone']").val("123");
$("input[name='street']").val("123");
$("input[name='city']").val("123");
$("input[name='zip']").val("123");
$("select[name='country_id']").val("21");
},
},
{
title: "select payment",
element: '#payment_method label:has(img[title="transfer"]) input',
},
{
title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="transfer"])',
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
},
{
title: "finish",
waitFor: '.oe_website_sale:contains("Thank you for your order")',
}
];
return this._super();
},
},
{
title: "select payment",
element: '#payment_method label:has(img[title="Wire Transfer"]) input',
},
{
title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])',
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
},
{
title: "finish",
waitFor: '.oe_website_sale:contains("Thank you for your order")',
}
]
});
// for test without editor bar
website.Tour.add(website.Tour.EventSaleTest);
}());

View File

@ -1 +1 @@
#import test_ui
import test_ui

View File

@ -1,19 +1,21 @@
import os
import openerp.tests
inject = [
"./../../../website/static/src/js/website.tour.test.js",
"./../../../website_event_sale/static/src/js/website.tour.event_sale.js",
("openerp.website.Tour", os.path.join(os.path.dirname(__file__), '../../website/static/src/js/website.tour.js')),
("openerp.website.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.event_sale.js")),
]
@openerp.tests.common.at_install(False)
@openerp.tests.common.post_install(True)
class TestUi(openerp.tests.HttpCase):
def test_admin(self):
self.phantom_js("/", "openerp.website.Tour.run_test('event_buy_tickets')", "openerp.website.Tour", inject=inject)
self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", inject=inject)
def test_demo(self):
self.phantom_js("/", "openerp.website.Tour.run_test('event_buy_tickets')", "openerp.website.Tour", login="demo", password="demo", inject=inject);
self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", login="demo", password="demo", inject=inject);
def test_public(self):
self.phantom_js("/", "openerp.website.Tour.run_test('event_buy_tickets')", "openerp.website.Tour", login=None, inject=inject);
self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", login=None, inject=inject);

View File

@ -38,7 +38,6 @@
<xpath expr='//t[@name="layout_head"]' position="before">
<link rel='stylesheet' href='/website/static/src/css/snippets.css'/>
<link rel='stylesheet' href='/website/static/src/css/editor.css'/>
<link rel='stylesheet' href='/website/static/lib/bootstrap-tour/bootstrap-tour.css'/>
<link rel="stylesheet" href="/web/static/lib/select2/select2.css"/>
@ -50,7 +49,6 @@
<script t-if="not translatable" type="text/javascript" src="/website/static/lib/ace/ace.js"></script>
<script type="text/javascript" src="/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<script type="text/javascript" src="/website/static/lib/bootstrap-tour/bootstrap-tour.js"></script>
<!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
<script type="text/javascript" src="/website/static/lib//jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js"></script>
<script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script>

View File

@ -3,92 +3,87 @@
var website = openerp.website;
website.Tour.ShopTest = website.Tour.extend({
id: 'shop_buy_product',
website.Tour.register({
id: 'shop_buy_product',
name: "Try to buy products",
path: '/shop',
init: function () {
var self = this;
self.steps = [
{
title: "select ipod",
element: '.oe_product_cart a:contains("iPod")',
mode: 'test',
steps: [
{
title: "select ipod",
element: '.oe_product_cart a:contains("iPod")',
},
{
title: "select ipod 32Go",
element: 'input[name="product_id"]:not([checked])',
},
{
title: "click on add to cart",
waitFor: 'input[name="product_id"]:eq(1)[checked]',
element: 'form[action="/shop/add_cart"] .btn',
},
{
title: "add suggested",
element: 'form[action="/shop/add_cart"] .btn-link:contains("Add to Cart")',
},
{
title: "add one more iPod",
waitFor: '.my_cart_quantity:contains(2)',
element: '#mycart_products tr:contains("iPod: 32 Gb") a.js_add_cart_json:eq(1)',
},
{
title: "remove Headphones",
waitFor: '#mycart_products tr:contains("iPod: 32 Gb") input.js_quantity[value=2]',
element: '#mycart_products tr:contains("Apple In-Ear Headphones") a.js_add_cart_json:first',
},
{
title: "set one iPod",
waitNot: '#mycart_products tr:contains("Apple In-Ear Headphones")',
element: '#mycart_products input.js_quantity',
sampleText: '1',
},
{
title: "go to checkout",
waitFor: '#mycart_products input.js_quantity[value=1]',
element: 'a[href="/shop/checkout"]',
},
{
title: "test with input error",
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
onload: function (tour) {
$("input[name='phone']").val("");
},
{
title: "select ipod 32Go",
element: 'input[name="product_id"]:not([checked])',
},
{
title: "test without input error",
waitFor: 'form[action="/shop/confirm_order"] .has-error',
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
onload: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
if ($("input[name='email']").val() === "")
$("input[name='email']").val("website_sale_test_shoptest@websitesaletest.optenerp.com");
$("input[name='phone']").val("123");
$("input[name='street']").val("123");
$("input[name='city']").val("123");
$("input[name='zip']").val("123");
$("select[name='country_id']").val("21");
},
{
title: "click on add to cart",
waitFor: 'input[name="product_id"]:eq(1)[checked]',
element: 'form[action="/shop/add_cart"] .btn',
},
{
title: "add suggested",
element: 'form[action="/shop/add_cart"] .btn-link:contains("Add to Cart")',
},
{
title: "add one more iPod",
waitFor: '.my_cart_quantity:contains(2)',
element: '#mycart_products tr:contains("iPod: 32 Gb") a.js_add_cart_json:eq(1)',
},
{
title: "remove Headphones",
waitFor: '#mycart_products tr:contains("iPod: 32 Gb") input.js_quantity[value=2]',
element: '#mycart_products tr:contains("Apple In-Ear Headphones") a.js_add_cart_json:first',
},
{
title: "set one iPod",
waitNot: '#mycart_products tr:contains("Apple In-Ear Headphones")',
element: '#mycart_products input.js_quantity',
sampleText: '1',
},
{
title: "go to checkout",
waitFor: '#mycart_products input.js_quantity[value=1]',
element: 'a[href="/shop/checkout"]',
},
{
title: "test with input error",
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
onload: function (tour) {
$("input[name='phone']").val("");
},
},
{
title: "test without input error",
waitFor: 'form[action="/shop/confirm_order"] .has-error',
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
onload: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
if ($("input[name='email']").val() === "")
$("input[name='email']").val("website_sale_test_shoptest@websitesaletest.optenerp.com");
$("input[name='phone']").val("123");
$("input[name='street']").val("123");
$("input[name='city']").val("123");
$("input[name='zip']").val("123");
$("select[name='country_id']").val("21");
},
},
{
title: "select payment",
element: '#payment_method label:has(img[title="Wire Transfer"]) input',
},
{
title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])',
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
},
{
title: "finish",
waitFor: '.oe_website_sale:contains("Thank you for your order")',
}
];
return this._super();
},
},
{
title: "select payment",
element: '#payment_method label:has(img[title="Wire Transfer"]) input',
},
{
title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])',
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
},
{
title: "finish",
waitFor: '.oe_website_sale:contains("Thank you for your order")',
}
]
});
// for test without editor bar
website.Tour.add(website.Tour.ShopTest);
}());

View File

@ -4,125 +4,114 @@
var website = openerp.website;
var _t = openerp._t;
website.EditorBar.include({
start: function () {
this.registerTour(new website.Tour.Shop(this));
return this._super();
},
});
website.Tour.Shop = website.Tour.extend({
website.Tour.register({
id: 'shop',
name: "Create a product",
testPath: '/shop',
init: function () {
var self = this;
self.steps = [
{
title: _t("Welcome to your shop"),
content: _t("You successfully installed the e-commerce. This guide will help you to create your product and promote your sales."),
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
},
{
element: '#content-menu-button',
placement: 'left',
title: _t("Create your first product"),
content: _t("Click here to add a new product."),
popover: { fixed: true },
},
{
element: 'a[data-action=new_product]',
placement: 'left',
title: _t("Create a new product"),
content: _t("Select 'New Product' to create it and manage its properties to boost your sales."),
popover: { fixed: true },
},
{
element: '.modal #editor_new_product input[type=text]',
sampleText: 'New Product',
placement: 'right',
title: _t("Choose name"),
content: _t("Enter a name for your new product then click 'Continue'."),
},
{
waitNot: '.modal input[type=text]:not([value!=""])',
element: '.modal button.btn-primary',
placement: 'right',
title: _t("Create Product"),
content: _t("Click <em>Continue</em> to create the product."),
},
{
waitFor: 'body:has(button[data-action=save]:visible):has(.js_sale)',
title: _t("New product created"),
content: _t("This page contains all the information related to the new product."),
popover: { next: _t("Continue") },
},
{
element: '.product_price .oe_currency_value',
sampleText: '20.50',
placement: 'left',
title: _t("Change the price"),
content: _t("Edit the price of this product by clicking on the amount."),
},
{
waitNot: '.product_price .oe_currency_value:containsExact(1.00)',
element: '#wrap img.product_detail_img',
placement: 'top',
title: _t("Update image"),
content: _t("Click here to set an image describing your product."),
},
{
element: 'img[alt=ipad]',
placement: 'top',
title: _t("Select an Image"),
content: _t("Let's select an ipad image."),
},
{
waitFor: '.media_selected img[alt=ipad]',
element: '.modal-content button.save',
placement: 'top',
title: _t("Save this Image"),
content: _t("Click on save to add the image to the product decsription."),
},
{
waitNot: '.modal-content:visible',
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Describe the Product"),
content: _t("Insert blocks like text-image, or gallery to fully describe the product."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(7)',
placement: 'bottom',
title: _t("Drag & Drop a block"),
content: _t("Drag the 'Big Picture' block and drop it in your page."),
popover: { fixed: true },
},
{
element: 'button[data-action=save]',
placement: 'right',
title: _t("Save your modifications"),
content: _t("Once you click on save, your product is updated."),
popover: { fixed: true },
name: _t("Create a product"),
steps: [
{
title: _t("Welcome to your shop"),
content: _t("You successfully installed the e-commerce. This guide will help you to create your product and promote your sales."),
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
},
{
element: '#content-menu-button',
placement: 'left',
title: _t("Create your first product"),
content: _t("Click here to add a new product."),
popover: { fixed: true },
},
{
element: 'a[data-action=new_product]',
placement: 'left',
title: _t("Create a new product"),
content: _t("Select 'New Product' to create it and manage its properties to boost your sales."),
popover: { fixed: true },
},
{
element: '.modal #editor_new_product input[type=text]',
sampleText: 'New Product',
placement: 'right',
title: _t("Choose name"),
content: _t("Enter a name for your new product then click 'Continue'."),
},
{
waitNot: '.modal input[type=text]:not([value!=""])',
element: '.modal button.btn-primary',
placement: 'right',
title: _t("Create Product"),
content: _t("Click <em>Continue</em> to create the product."),
},
{
waitFor: 'body:has(button[data-action=save]:visible):has(.js_sale)',
title: _t("New product created"),
content: _t("This page contains all the information related to the new product."),
popover: { next: _t("Continue") },
},
{
element: '.product_price .oe_currency_value',
sampleText: '20.50',
placement: 'left',
title: _t("Change the price"),
content: _t("Edit the price of this product by clicking on the amount."),
},
},
{
waitFor: '#website-top-navbar button[data-action="edit"]:visible',
element: '.js_publish_management button.js_publish_btn.btn-danger',
placement: 'top',
title: _t("Publish your product"),
content: _t("Click to publish your product so your customers can see it."),
},
{
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
title: _t("Congratulations"),
content: _t("Congratulations! You just created and published your first product."),
popover: { next: _t("Close Tutorial") },
},
];
return this._super();
}
{
waitNot: '.product_price .oe_currency_value:containsExact(1.00)',
element: '#wrap img.product_detail_img',
placement: 'top',
title: _t("Update image"),
content: _t("Click here to set an image describing your product."),
},
{
element: 'img[alt=ipad]',
placement: 'top',
title: _t("Select an Image"),
content: _t("Let's select an ipad image."),
},
{
waitFor: '.media_selected img[alt=ipad]',
element: '.modal-content button.save',
placement: 'top',
title: _t("Save this Image"),
content: _t("Click on save to add the image to the product decsription."),
},
{
waitNot: '.modal-content:visible',
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Describe the Product"),
content: _t("Insert blocks like text-image, or gallery to fully describe the product."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(7)',
placement: 'bottom',
title: _t("Drag & Drop a block"),
content: _t("Drag the 'Big Picture' block and drop it in your page."),
popover: { fixed: true },
},
{
element: 'button[data-action=save]',
placement: 'right',
title: _t("Save your modifications"),
content: _t("Once you click on save, your product is updated."),
popover: { fixed: true },
},
{
waitFor: '#website-top-navbar button[data-action="edit"]:visible',
element: '.js_publish_management button.js_publish_btn.btn-danger',
placement: 'top',
title: _t("Publish your product"),
content: _t("Click to publish your product so your customers can see it."),
},
{
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
title: _t("Congratulations"),
content: _t("Congratulations! You just created and published your first product."),
popover: { next: _t("Close Tutorial") },
},
]
});
}());

View File

@ -11,13 +11,13 @@ inject = [
@openerp.tests.common.post_install(True)
class TestUi(openerp.tests.HttpCase):
def test_01_admin_shop_tour(self):
self.phantom_js("/", "openerp.website.Tour.run_test('shop')", "openerp.website.Tour.Shop", login="admin")
self.phantom_js("/", "openerp.website.Tour.run('shop', 'test')", "openerp.website.Tour.tours.shop", login="admin")
def test_02_admin_checkout(self):
self.phantom_js("/", "openerp.website.Tour.run_test('shop_buy_product')", "openerp.website.Tour.ShopTest", login="admin", inject=inject)
self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", login="admin", inject=inject)
def test_03_demo_checkout(self):
self.phantom_js("/", "openerp.website.Tour.run_test('shop_buy_product')", "openerp.website.Tour.ShopTest", login="demo", inject=inject)
self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", login="demo", inject=inject)
def test_04_public_checkout(self):
self.phantom_js("/", "openerp.website.Tour.run_test('shop_buy_product')", "openerp.website.Tour.ShopTest", inject=inject)
self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", inject=inject)