summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2015-08-24 18:19:19 +0200
committerChristophe Simonis <chs@odoo.com>2015-08-24 18:19:19 +0200
commit88c0ce0692ccf3b1302e15c4ebe34dbf2040be40 (patch)
tree5bb1749f69f96df22a92a747052dc2965e83430e
parent878c3c9291ad69b530d24227cb25b38dbf10229b (diff)
parente16d495d8965522d90ddf5d5da234b1650a695f9 (diff)
[MERGE] forward port of branch saas-6 up to e16d495
-rw-r--r--addons/account/models/account_invoice.py1
-rw-r--r--addons/account_analytic_plans/security/ir.model.access.csv2
-rw-r--r--addons/delivery/stock.py1
-rw-r--r--addons/hr_timesheet_invoice/hr_timesheet_invoice.py8
-rw-r--r--addons/mass_mailing/wizard/test_mailing.py1
-rw-r--r--addons/payment_ogone/models/ogone.py11
-rw-r--r--addons/point_of_sale/tools/posbox/overwrite_before_init/etc/default/keyboard10
-rwxr-xr-xaddons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh10
-rw-r--r--addons/point_of_sale/tools/posbox/overwrite_before_init/etc/udev/rules.d/99-usb.rules2
-rw-r--r--addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/.inputrc53
-rw-r--r--addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/.vimrc27
-rw-r--r--addons/purchase/purchase.py12
-rw-r--r--addons/purchase_analytic_plans/purchase_analytic_plans_view.xml4
-rw-r--r--addons/purchase_requisition/static/src/js/web_addons.js2
-rw-r--r--addons/sale_contract/models/sale_contract.py1
-rw-r--r--addons/stock/product_view.xml2
-rw-r--r--addons/stock/report/report_stock_forecast.py46
-rw-r--r--addons/stock/stock.py4
-rw-r--r--addons/stock_dropshipping/stock_dropshipping.py9
-rw-r--r--addons/web/static/src/js/views/list_view.js1
-rw-r--r--addons/web/static/src/js/views/search_view.js2
-rw-r--r--addons/web_editor/static/lib/summernote/src/js/editing/History.js2
-rw-r--r--addons/web_editor/static/src/js/editor.js2
-rw-r--r--addons/web_editor/static/src/js/snippets.editor.js85
-rw-r--r--addons/web_editor/static/src/js/snippets.options.js26
-rw-r--r--addons/web_editor/static/src/js/summernote.js29
-rw-r--r--addons/web_editor/static/src/js/widgets.js18
-rw-r--r--addons/website/static/src/js/website.snippets.editor.js12
-rw-r--r--addons/website/static/src/js/website.snippets.gallery.js21
-rw-r--r--addons/website/static/src/js/website.theme.js54
-rw-r--r--addons/website_sale/models/product.py6
-rw-r--r--addons/website_sale/static/src/css/website_sale.css4
-rw-r--r--addons/website_sale/views/templates.xml8
-rw-r--r--doc/cla/corporate/ITLibertas.md17
-rw-r--r--doc/cla/individual/flh.md11
-rw-r--r--openerp/addons/base/ir/ir_http.py8
-rw-r--r--openerp/addons/base/rng/view.rng5
-rw-r--r--openerp/addons/base/tests/test_search.py62
-rw-r--r--openerp/models.py34
39 files changed, 446 insertions, 167 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 804660ede8d..ea80678840a 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -574,6 +574,7 @@ class AccountInvoice(models.Model):
else:
line['currency_id'] = False
line['amount_currency'] = False
+ line['price'] = self.currency_id.round(line['price'])
if self.type in ('out_invoice', 'in_refund'):
total += line['price']
total_currency += line['amount_currency'] or line['price']
diff --git a/addons/account_analytic_plans/security/ir.model.access.csv b/addons/account_analytic_plans/security/ir.model.access.csv
index 9ba40d5c1c3..7a8d6e29067 100644
--- a/addons/account_analytic_plans/security/ir.model.access.csv
+++ b/addons/account_analytic_plans/security/ir.model.access.csv
@@ -2,5 +2,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_analytic_plan,account.analytic.plan,model_account_analytic_plan,account.group_account_user,1,1,1,1
access_account_analytic_plan_line,account.analytic.plan.line,model_account_analytic_plan_line,account.group_account_user,1,1,1,1
access_account_analytic_plan_instance,account.analytic.plan.instance,model_account_analytic_plan_instance,account.group_account_user,1,1,1,1
+access_account_analytic_plan_instance_group_user,account.analytic.plan.instance,model_account_analytic_plan_instance,base.group_user,1,0,0,0
access_account_analytic_plan_instance_line,account.analytic.plan.instance.line,model_account_analytic_plan_instance_line,account.group_account_user,1,1,1,1
+access_account_analytic_plan_instance_line_group_user,account.analytic.plan.instance.line,model_account_analytic_plan_instance_line,base.group_user,1,0,0,0
access_account_analytic_plan_line_invoice,account.analytic.plan.line.invoice,model_account_analytic_plan_line,account.group_account_user,1,1,1,1
diff --git a/addons/delivery/stock.py b/addons/delivery/stock.py
index 69238eee0be..1b028032432 100644
--- a/addons/delivery/stock.py
+++ b/addons/delivery/stock.py
@@ -48,6 +48,7 @@ class stock_picking(osv.osv):
"""
carrier_obj = self.pool.get('delivery.carrier')
grid_obj = self.pool.get('delivery.grid')
+ fpos_obj = self.pool['account.fiscal.position']
currency_obj = self.pool.get('res.currency')
if not picking.carrier_id or \
any(inv_line.product_id.id == picking.carrier_id.product_id.id
diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
index 7dffc8c042b..f040bf815d2 100644
--- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
+++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
@@ -249,8 +249,10 @@ class account_analytic_line(osv.osv):
# key_id is either an account.analytic.account, either a res.partner
# don't really care, what's important is the analytic lines that
# will be used to create the invoice lines
-
- partner = analytic_line_ids[0].account_id.partner_id # will be the same for every line
+ account = analytic_lines[0].account_id
+ partner = account.partner_id # will be the same for every line
+ if (not partner) or not (currency_id):
+ raise UserError(_('Contract incomplete. Please fill in the Customer and Pricelist fields for %s.') % (account.name))
curr_invoice = self._prepare_cost_invoice(cr, uid, partner, company_id, currency_id, analytic_line_ids, group_by_partner, context=context)
invoice_context = dict(context,
@@ -265,8 +267,6 @@ class account_analytic_line(osv.osv):
invoice_lines_grouping = {}
for analytic_line in analytic_line_ids:
account = analytic_line.account_id
- if (not partner) or not (account.pricelist_id):
- raise UserError(_('Contract incomplete. Please fill in the Customer and Pricelist fields for %s.') % (account.name))
if not analytic_line.to_invoice:
raise UserError(_('Trying to invoice non invoiceable line for %s.') % (analytic_line.product_id.name))
diff --git a/addons/mass_mailing/wizard/test_mailing.py b/addons/mass_mailing/wizard/test_mailing.py
index 237e1521c19..c0601ea29d5 100644
--- a/addons/mass_mailing/wizard/test_mailing.py
+++ b/addons/mass_mailing/wizard/test_mailing.py
@@ -33,6 +33,7 @@ class TestMassMailing(osv.TransientModel):
'body_html': '',
'notification': True,
'mailing_id': mailing.id,
+ 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
}
mail_mail_obj = Mail.browse(cr, uid, Mail.create(cr, uid, mail_values, context=context), context=context)
unsubscribe_url = Mail._get_unsubscribe_url(cr, uid, mail_mail_obj, test_mail, context=context)
diff --git a/addons/payment_ogone/models/ogone.py b/addons/payment_ogone/models/ogone.py
index 40bd0e2a974..a70dda7d35f 100644
--- a/addons/payment_ogone/models/ogone.py
+++ b/addons/payment_ogone/models/ogone.py
@@ -249,6 +249,9 @@ class PaymentTxOgone(osv.Model):
_logger.info(error_msg)
raise ValidationError(error_msg)
+ if not tx.acquirer_reference:
+ tx.acquirer_reference = pay_id
+
# alias was created on ogone server, store it
if alias:
method_obj = self.pool['payment.method']
@@ -380,7 +383,7 @@ class PaymentTxOgone(osv.Model):
_logger.info('Ogone: trying to validate an already validated tx (ref %s)', tx.reference)
return True
- status = int(tree.get('STATUS', '0'))
+ status = int(tree.get('STATUS') or 0)
if status in self._ogone_valid_tx_status:
tx.write({
'state': 'done',
@@ -401,11 +404,11 @@ class PaymentTxOgone(osv.Model):
'acquirer_reference': tree.get('PAYID'),
'html_3ds': str(tree.HTML_ANSWER).decode('base64')
})
- elif status in self._ogone_wait_tx_status and tries > 0:
- time.sleep(1500)
+ elif (not status or status in self._ogone_wait_tx_status) and tries > 0:
+ time.sleep(500)
tx.write({'acquirer_reference': tree.get('PAYID')})
tree = self._ogone_s2s_get_tx_status(tx)
- return self.ogone_s2s_validate(tx, tree, tries - 1)
+ return self._ogone_s2s_validate_tree(tx, tree, tries - 1)
else:
error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % {
'error_str': tree.get('NCERRORPLUS'),
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/default/keyboard b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/default/keyboard
new file mode 100644
index 00000000000..2609b092bcc
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/default/keyboard
@@ -0,0 +1,10 @@
+# KEYBOARD CONFIGURATION FILE
+
+# Consult the keyboard(5) manual page.
+
+XKBMODEL="pc105"
+XKBLAYOUT="us"
+XKBVARIANT=""
+XKBOPTIONS=""
+
+BACKSPACE="guess" \ No newline at end of file
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
index 68b64fb94ca..781b599d51f 100755
--- a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
@@ -31,7 +31,7 @@ apt-get -y autoremove
apt-get update
apt-get -y dist-upgrade
-PKGS_TO_INSTALL="adduser postgresql-client python python-dateutil python-decorator python-docutils python-feedparser python-imaging python-jinja2 python-ldap python-libxslt1 python-lxml python-mako python-mock python-openid python-passlib python-psutil python-psycopg2 python-pybabel python-pychart python-pydot python-pyparsing python-pypdf python-reportlab python-requests python-simplejson python-tz python-unittest2 python-vatnumber python-vobject python-werkzeug python-xlwt python-yaml postgresql python-gevent python-serial python-pip python-dev localepurge vim mc mg"
+PKGS_TO_INSTALL="adduser postgresql-client python python-dateutil python-decorator python-docutils python-feedparser python-imaging python-jinja2 python-ldap python-libxslt1 python-lxml python-mako python-mock python-openid python-passlib python-psutil python-psycopg2 python-pybabel python-pychart python-pydot python-pyparsing python-pypdf python-reportlab python-requests python-simplejson python-tz python-unittest2 python-vatnumber python-vobject python-werkzeug python-xlwt python-yaml postgresql python-gevent python-serial python-pip python-dev localepurge vim mc mg screen"
apt-get -y install ${PKGS_TO_INSTALL}
@@ -48,14 +48,22 @@ pip install pyusb==1.0.0b1
pip install qrcode
pip install evdev
+groupadd usbusers
+usermod -a -G usbusers pi
usermod -a -G lp pi
sudo -u postgres createuser -s pi
mkdir /var/log/odoo
chown pi:pi /var/log/odoo
+echo "* * * * * rm /var/run/odoo/sessions/*" | crontab -
+
update-rc.d odoo defaults
+# https://www.raspberrypi.org/forums/viewtopic.php?p=79249
+# to not have "setting up console font and keymap" during boot take ages
+setupcon
+
# create dirs for ramdisks
create_ramdisk_dir () {
mkdir "${1}_ram"
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/udev/rules.d/99-usb.rules b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/udev/rules.d/99-usb.rules
new file mode 100644
index 00000000000..7a282a8e6e1
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/udev/rules.d/99-usb.rules
@@ -0,0 +1,2 @@
+SUBSYSTEM=="usb", GROUP="usbusers", MODE="0660"
+SUBSYSTEMS=="usb", GROUP="usbusers", MODE="0660" \ No newline at end of file
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/.inputrc b/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/.inputrc
new file mode 100644
index 00000000000..131b24148c2
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/.inputrc
@@ -0,0 +1,53 @@
+#
+set meta-flag on
+set convert-meta off
+set input-meta on
+set output-meta on
+set show-all-if-ambiguous on
+set visible-stats on
+set bell-style none
+# set bell-style visible
+#"\M-k": kill-whole-line
+#----------------------------------------------------------
+# arrows left/right/up/down
+"\e[C": forward-char
+"\e[D": backward-char
+"\e[A": previous-history
+"\e[B": next-history
+# ---------------------------------------------------------
+# pgup/pgdn
+"\e[6~": history-search-forward
+"\e[5~": history-search-backward
+#----------------------------------------------------------
+# insert,delete
+#"\e[2~": quoted-insert
+"\e[3~": delete-char
+# esc+delete, esc+backspace
+"\e\e[3~": kill-word
+"\e\C-h": backward-kill-word
+#----------------------------------------------------------
+# ctrl left/right (rxvt)
+"\eOc": forward-word
+"\eOd": backward-word
+# ctrl left/right (xterm)
+"\e[1;5C": forward-word
+"\e[1;5D": backward-word
+# ctrl left/right (unknown)
+#"\e[5C": forward-word
+#"\e[5D": backward-word
+#"\e\e[C": forward-word
+#"\e\e[D": backward-word
+# ---------------------------------------------------------
+# home/end (linux console)
+"\e[1~": beginning-of-line
+"\e[4~": end-of-line
+# home/end (rxvt)
+"\e[7~": beginning-of-line
+"\e[8~": end-of-line
+# home/end (xterm, freebsd console)
+"\e[H": beginning-of-line
+"\e[F": end-of-line
+# home/end (non RH/Debian xterm, unknown)
+"\eOH": beginning-of-line
+"\eOF": end-of-line
+"\eOw": end-of-line
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/.vimrc b/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/.vimrc
new file mode 100644
index 00000000000..9e65af338fa
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/.vimrc
@@ -0,0 +1,27 @@
+set all&
+set autoindent
+set backspace=2
+set nobackup
+set nocompatible
+set noerrorbells
+set noexpandtab
+set expandtab
+set number
+set hidden
+set history=500
+set hlsearch
+set ignorecase
+set modeline
+set ruler
+set shiftwidth=4
+set scrolloff=5
+set showcmd
+set showmode
+set tabstop=4
+set textwidth=0
+set visualbell
+set t_vb=
+set wrap
+set list
+set listchars=tab:~.,trail:.,extends:>,precedes:<
+set viminfo="NONE"
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 47fd47ef794..d7a0b109a38 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -746,8 +746,12 @@ class purchase_order(osv.osv):
#we don't round the price_unit, as we may want to store the standard price with more digits than allowed by the currency
price_unit = self.pool.get('res.currency').compute(cr, uid, order.currency_id.id, order.company_id.currency_id.id, price_unit, round=False, context=context)
res = []
+ if order.location_id.usage == 'customer':
+ name = order_line.product_id.with_context(dict(context or {}, lang=order.dest_address_id.lang)).name
+ else:
+ name = order_line.name or ''
move_template = {
- 'name': order_line.name or '',
+ 'name': name,
'product_id': order_line.product_id.id,
'product_uom': order_line.product_uom.id,
'product_uos': order_line.product_uom.id,
@@ -1478,6 +1482,9 @@ class procurement_order(osv.osv):
return qty, price
+ def update_origin_po(self, cr, uid, po, proc, context=None):
+ pass
+
def _get_grouping_dicts(self, cr, uid, ids, context=None):
"""
It will group the procurements according to the pos they should go into. That way, lines going to the same
@@ -1610,6 +1617,9 @@ class procurement_order(osv.osv):
po_obj.message_post(cr, uid, [add_purchase], body=msg, context=context)
po_obj.write(cr, uid, [add_purchase], {'order_line': line_values},context=context)
+ for procurement in procurements:
+ self.update_origin_po(cr, uid, po, procurement, context=context)
+
# Create new purchase orders
partner_obj = self.pool.get("res.partner")
diff --git a/addons/purchase_analytic_plans/purchase_analytic_plans_view.xml b/addons/purchase_analytic_plans/purchase_analytic_plans_view.xml
index 28ca76e8430..ea16ae891b3 100644
--- a/addons/purchase_analytic_plans/purchase_analytic_plans_view.xml
+++ b/addons/purchase_analytic_plans/purchase_analytic_plans_view.xml
@@ -8,7 +8,7 @@
<field name="inherit_id" ref="purchase.purchase_order_line_form"/>
<field name="arch" type="xml">
<field name="account_analytic_id" position="replace">
- <field name="analytics_id" context="{'journal_id':parent.journal_id, 'model': 'purchase_order'}" domain="[('plan_id','&lt;&gt;',False)]"/>
+ <field name="analytics_id" groups="purchase.group_analytic_accounting" context="{'journal_id':parent.journal_id, 'model': 'purchase_order'}" domain="[('plan_id','&lt;&gt;',False)]"/>
</field>
</field>
</record>
@@ -19,7 +19,7 @@
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="arch" type="xml">
<field name="account_analytic_id" position="replace">
- <field name="analytics_id" context="{'journal_id':parent.journal_id}" domain="[('plan_id','&lt;&gt;',False)]"/>
+ <field name="analytics_id" groups="purchase.group_analytic_accounting" context="{'journal_id':parent.journal_id}" domain="[('plan_id','&lt;&gt;',False)]"/>
</field>
</field>
</record>
diff --git a/addons/purchase_requisition/static/src/js/web_addons.js b/addons/purchase_requisition/static/src/js/web_addons.js
index d67c8ec146d..f2ea43423f0 100644
--- a/addons/purchase_requisition/static/src/js/web_addons.js
+++ b/addons/purchase_requisition/static/src/js/web_addons.js
@@ -24,7 +24,7 @@ var CompareListView = ListView.extend({
generate_purchase_order: function () {
var self = this;
new Model(self.dataset.model).call("generate_po",[self.dataset.context.tender_id,self.dataset.context]).then(function(result) {
- self.ViewManager.ActionManager.history_back();
+ self.ViewManager.action_manager.history_back();
});
},
});
diff --git a/addons/sale_contract/models/sale_contract.py b/addons/sale_contract/models/sale_contract.py
index 7fcd9f51bc5..f47b4b0673a 100644
--- a/addons/sale_contract/models/sale_contract.py
+++ b/addons/sale_contract/models/sale_contract.py
@@ -673,6 +673,7 @@ class account_analytic_account(osv.osv):
'fiscal_position_id': fpos_id,
'payment_term_id': partner_payment_term,
'company_id': contract.company_id.id or False,
+ 'user_id': contract.manager_id.id or uid,
'comment': _("This invoice covers the following period: %s - %s") % (next_date.date(), new_date.date()),
}
return invoice
diff --git a/addons/stock/product_view.xml b/addons/stock/product_view.xml
index 17162086c9f..4c717702d5c 100644
--- a/addons/stock/product_view.xml
+++ b/addons/stock/product_view.xml
@@ -139,7 +139,7 @@
</group>
</group>
<field name="product_manager" position="attributes">
- <attribute name="context">{'default_groups_ref': ['base.group_user', 'base.group_sale_manager', 'stock.group_stock_manager']}</attribute>
+ <attribute name="context" translation="off">{'default_groups_ref': ['base.group_user', 'base.group_sale_manager', 'stock.group_stock_manager']}</attribute>
</field>
<group name="procurement" position="after" >
<group string="Supply Chain Information" attrs="{'invisible': [('type', '=', 'service')]}" groups="base.group_user">
diff --git a/addons/stock/report/report_stock_forecast.py b/addons/stock/report/report_stock_forecast.py
index 61397d5a25c..1161e5fc209 100644
--- a/addons/stock/report/report_stock_forecast.py
+++ b/addons/stock/report/report_stock_forecast.py
@@ -17,36 +17,24 @@ class report_stock_forecast(models.Model):
def init(self, cr):
tools.drop_view_if_exists(cr, 'report_stock_forecast')
cr.execute("""CREATE or REPLACE VIEW report_stock_forecast AS (SELECT
- max(id) as id,
+ MIN(id) as id,
product_id as product_id,
date as date,
sum(product_qty) AS quantity,
- sum(sum(product_qty)) OVER(PARTITION BY product_id ORDER BY date ) AS cumulative_quantity
+ sum(sum(product_qty)) OVER (PARTITION BY product_id ORDER BY date) AS cumulative_quantity
FROM
(SELECT
- max(id) as id,
+ MIN(id) as id,
MAIN.product_id as product_id,
SUB.date as date,
CASE WHEN MAIN.date = SUB.date THEN sum(MAIN.product_qty) ELSE 0 END as product_qty
FROM
(SELECT
- max(sq.id) as id,
+ MIN(sq.id) as id,
sq.product_id,
- CASE WHEN sm.date < CURRENT_DATE
- THEN to_date(to_char(sm.date, 'YYYY/MM/DD'), 'YYYY/MM/DD')
- ELSE to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')
- END AS date,
+ date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) as date,
SUM(sq.qty) AS product_qty
FROM
- (SELECT min(date_expected) AS date FROM stock_move AS sm
- LEFT JOIN
- stock_location source_location ON sm.location_id = source_location.id
- LEFT JOIN
- stock_location dest_location ON sm.location_dest_id = dest_location.id
- WHERE
- sm.state IN ('confirmed','assigned','waiting') AND
- (dest_location.usage = 'internal' AND source_location.usage != 'internal')
- or (source_location.usage = 'internal' AND dest_location.usage != 'internal')) AS sm,
stock_quant as sq
LEFT JOIN
product_product ON product_product.id = sq.product_id
@@ -57,9 +45,12 @@ class report_stock_forecast(models.Model):
GROUP BY date, sq.product_id
UNION ALL
SELECT
- max(sm.id) as id,
+ MIN(-sm.id) as id,
sm.product_id,
- to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD') AS date,
+ CASE WHEN sm.date_expected > CURRENT_DATE
+ THEN date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD'))
+ ELSE date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) END
+ AS date,
SUM(sm.product_qty) AS product_qty
FROM
stock_move as sm
@@ -75,9 +66,12 @@ class report_stock_forecast(models.Model):
GROUP BY sm.date_expected,sm.product_id
UNION ALL
SELECT
- max(sm.id) as id,
+ MIN(-sm.id) as id,
sm.product_id,
- to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD') AS date,
+ CASE WHEN sm.date_expected > CURRENT_DATE
+ THEN date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD'))
+ ELSE date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) END
+ AS date,
SUM(-(sm.product_qty)) AS product_qty
FROM
stock_move as sm
@@ -96,18 +90,18 @@ class report_stock_forecast(models.Model):
(SELECT DISTINCT date
FROM
(
- SELECT CURRENT_DATE AS DATE
+ SELECT date_trunc('week', CURRENT_DATE) AS DATE
UNION ALL
- SELECT to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD') AS date
+ SELECT date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD')) AS date
FROM stock_move sm
LEFT JOIN
stock_location source_location ON sm.location_id = source_location.id
LEFT JOIN
stock_location dest_location ON sm.location_dest_id = dest_location.id
WHERE
- sm.state IN ('confirmed','assigned','waiting') and
- (dest_location.usage = 'internal' AND source_location.usage != 'internal')
- or (source_location.usage = 'internal' AND dest_location.usage != 'internal')) AS DATE_SEARCH)
+ sm.state IN ('confirmed','assigned','waiting') and sm.date_expected > CURRENT_DATE and
+ ((dest_location.usage = 'internal' AND source_location.usage != 'internal')
+ or (source_location.usage = 'internal' AND dest_location.usage != 'internal'))) AS DATE_SEARCH)
SUB ON (SUB.date IS NOT NULL)
GROUP BY MAIN.product_id,SUB.date, MAIN.date
) AS FINAL
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index dad23b73223..1409ce1e907 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -3711,9 +3711,13 @@ class stock_warehouse(osv.osv):
self._handle_renaming(cr, uid, warehouse, name, vals.get('code', warehouse.code), context=context_with_inactive)
if warehouse.in_type_id:
seq_obj.write(cr, uid, warehouse.in_type_id.sequence_id.id, {'name': name + _(' Sequence in'), 'prefix': vals.get('code', warehouse.code) + '\IN\\'}, context=context)
+ if warehouse.out_type_id:
seq_obj.write(cr, uid, warehouse.out_type_id.sequence_id.id, {'name': name + _(' Sequence out'), 'prefix': vals.get('code', warehouse.code) + '\OUT\\'}, context=context)
+ if warehouse.pack_type_id:
seq_obj.write(cr, uid, warehouse.pack_type_id.sequence_id.id, {'name': name + _(' Sequence packing'), 'prefix': vals.get('code', warehouse.code) + '\PACK\\'}, context=context)
+ if warehouse.pick_type_id:
seq_obj.write(cr, uid, warehouse.pick_type_id.sequence_id.id, {'name': name + _(' Sequence picking'), 'prefix': vals.get('code', warehouse.code) + '\PICK\\'}, context=context)
+ if warehouse.int_type_id:
seq_obj.write(cr, uid, warehouse.int_type_id.sequence_id.id, {'name': name + _(' Sequence internal'), 'prefix': vals.get('code', warehouse.code) + '\INT\\'}, context=context)
if vals.get('resupply_wh_ids') and not vals.get('resupply_route_ids'):
for cmd in vals.get('resupply_wh_ids'):
diff --git a/addons/stock_dropshipping/stock_dropshipping.py b/addons/stock_dropshipping/stock_dropshipping.py
index d7f2fc54137..96a22e5dfae 100644
--- a/addons/stock_dropshipping/stock_dropshipping.py
+++ b/addons/stock_dropshipping/stock_dropshipping.py
@@ -43,3 +43,12 @@ class purchase_order(models.Model):
if not self.env.context.get('no_invoice_policy_check'):
self._check_invoice_policy()
super(purchase_order, self).wkf_confirm_order()
+
+class procurement_order(models.Model):
+ _inherit = 'procurement.order'
+
+ @api.model
+ def update_origin_po(self, po, proc):
+ super(procurement_order, self).update_origin_po(po, proc)
+ if proc.sale_line_id and not (proc.origin in po.origin):
+ po.sudo().write({'origin': po.origin+', '+proc.origin})
diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js
index 63940726fca..34a483f8aa9 100644
--- a/addons/web/static/src/js/views/list_view.js
+++ b/addons/web/static/src/js/views/list_view.js
@@ -128,6 +128,7 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ {
// the view's number of records per page (|| section)
this._limit = (this.options.limit ||
this.defaults.limit ||
+ parseInt(this.fields_view.arch.attrs.limit, 10) ||
(this.getParent().action || {}).limit ||
80);
},
diff --git a/addons/web/static/src/js/views/search_view.js b/addons/web/static/src/js/views/search_view.js
index cd0735d24ed..dba0f8045fa 100644
--- a/addons/web/static/src/js/views/search_view.js
+++ b/addons/web/static/src/js/views/search_view.js
@@ -423,7 +423,7 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{
if (this.$buttons) {
var fields_def = new Model(this.dataset.model).call('fields_get', {
- context: this.dataset.context
+ context: this.dataset.get_context()
});
this.groupby_menu = new GroupByMenu(this, this.groupbys, fields_def);
diff --git a/addons/web_editor/static/lib/summernote/src/js/editing/History.js b/addons/web_editor/static/lib/summernote/src/js/editing/History.js
index 89834d52cb1..dc301bf957b 100644
--- a/addons/web_editor/static/lib/summernote/src/js/editing/History.js
+++ b/addons/web_editor/static/lib/summernote/src/js/editing/History.js
@@ -9,7 +9,7 @@ define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) {
var makeSnapshot = function () {
var rng = range.create();
- var emptyBookmark = {s: {path: [0], offset: 0}, e: {path: [0], offset: 0}};
+ var emptyBookmark = {s: {path: [], offset: 0}, e: {path: [], offset: 0}};
return {
contents: $editable.html(),
diff --git a/addons/web_editor/static/src/js/editor.js b/addons/web_editor/static/src/js/editor.js
index f9255716697..4480101bf6a 100644
--- a/addons/web_editor/static/src/js/editor.js
+++ b/addons/web_editor/static/src/js/editor.js
@@ -144,6 +144,8 @@ editor.Class = Widget.extend({
d.resolve();
}).on('hidden.bs.modal', function () {
d.reject();
+ }).on('keydown.dismiss.bs.modal', function (event) {
+ event.stopImmediatePropagation();
});
d.always(function () {
$dialog.remove();
diff --git a/addons/web_editor/static/src/js/snippets.editor.js b/addons/web_editor/static/src/js/snippets.editor.js
index cc380833074..3de9b71d5f8 100644
--- a/addons/web_editor/static/src/js/snippets.editor.js
+++ b/addons/web_editor/static/src/js/snippets.editor.js
@@ -531,7 +531,8 @@ var BuildingBlock = Widget.extend({
this.snippets.push($snippet.get(0));
}
this.$active_snipped_id = $snippet;
- this.create_overlay(this.$active_snipped_id);
+ this.create_snippet_editor(this.$active_snipped_id);
+ this.cover_target($snippet.data('overlay'), $snippet);
this.snippet_focus($snippet);
}
this.$snippet.trigger('snippet-activated', $snippet);
@@ -539,13 +540,11 @@ var BuildingBlock = Widget.extend({
$snippet.trigger('snippet-activated', $snippet);
}
},
- create_overlay: function ($snippet) {
+ create_snippet_editor: function ($snippet) {
if (typeof $snippet.data("snippet-editor") === 'undefined') {
- var $targets = this.activate_overlay_zones($snippet);
- if (!$targets.length) return;
+ if (!this.activate_overlay_zones($snippet).length) return;
$snippet.data("snippet-editor", new Editor(this, $snippet));
}
- this.cover_target($snippet.data('overlay'), $snippet);
},
// activate drag and drop for the snippets in the snippet toolbar
@@ -657,22 +656,12 @@ var BuildingBlock = Widget.extend({
animation.start(true, $target);
- // drop_and_build_snippet
- self.create_overlay($target);
- if ($target.data("snippet-editor")) {
- $target.data("snippet-editor").drop_and_build_snippet();
- }
- for (var k in self.templateOptions) {
- self.templateOptions[k].selector.all($target).each(function () {
- var $snippet = $(this);
- self.create_overlay($snippet);
- if ($snippet.data("snippet-editor")) {
- $snippet.data("snippet-editor").drop_and_build_snippet();
- }
- });
- }
+ self.call_for_all_snippets($target, function (editor, $snippet) {
+ editor.drop_and_build_snippet();
+ });
+ self.create_snippet_editor($target);
+ self.cover_target($target.data('overlay'), $target);
$target.closest(".o_editable").trigger("content_changed");
- // end
self.make_active($target);
},0);
@@ -683,6 +672,20 @@ var BuildingBlock = Widget.extend({
});
},
+ // call a method on a snippet and all his children
+ call_for_all_snippets: function ($snippet, callback) {
+ var self = this;
+ $snippet.add(globalSelector.all($snippet)).each(function () {
+ var $snippet = $(this);
+ setTimeout(function () {
+ self.create_snippet_editor($snippet);
+ if ($snippet.data("snippet-editor")) {
+ callback.call(self, $snippet.data("snippet-editor"), $snippet);
+ }
+ });
+ });
+ },
+
// return the original snippet in the editor bar from a snippet id (string)
get_snippet_from_id: function(id){
return $('.oe_snippet').filter(function(){
@@ -845,14 +848,12 @@ var BuildingBlock = Widget.extend({
var timer;
$target.closest('.o_editable').on("content_changed", function (event) {
- if ($target.data('overlay') && $target.data('overlay').hasClass("oe_active")) {
- clearTimeout(timer);
- timer = setTimeout(function () {
- if ($target.data('overlay')) {
- self.cover_target($target.data('overlay'), $target);
- }
- },50);
- }
+ clearTimeout(timer);
+ timer = setTimeout(function () {
+ if ($target.data('overlay') && $target.data('overlay').hasClass("oe_active")) {
+ self.cover_target($target.data('overlay'), $target);
+ }
+ },50);
});
var resize = function () {
@@ -985,7 +986,9 @@ var Editor = Class.extend({
self.buildingBlock.editor_busy = false;
self.get_parent_block();
- setTimeout(function () {self.buildingBlock.create_overlay(self.$target);},0);
+ setTimeout(function () {
+ self.BuildingBlock.cover_target(self.$target.data('overlay'), self.$target);
+ },0);
},
load_style_options: function () {
@@ -1022,7 +1025,10 @@ var Editor = Class.extend({
get_parent_block: function () {
var self = this;
var $button = this.$overlay.find('.oe_snippet_parent');
- var $parent = globalSelector.closest(this.$target.parent());
+ var $parent = this.$target.parents().filter(function () { return $(this).data("snippet-editor"); });
+ if (!$parent.length) {
+ $parent = globalSelector.closest(this.$target.parent());
+ }
if ($parent.length) {
$button.removeClass("hidden");
$button.off("click").on('click', function (event) {
@@ -1056,12 +1062,13 @@ var Editor = Class.extend({
var $clone = this.$target.clone(false);
this.buildingBlock.parent.rte.historyRecordUndo(this.$target);
-
+
this.$target.after($clone);
- for (var i in this.styles){
- this.styles[i].on_clone($clone);
- }
- this.buildingBlock.create_overlay(this.$target);
+ this.BuildingBlock.call_for_all_snippets($clone, function (editor, $snippet) {
+ for (var i in editor.styles){
+ editor.styles[i].on_clone($snippet);
+ }
+ });
return false;
},
@@ -1072,9 +1079,11 @@ var Editor = Class.extend({
this.buildingBlock.parent.rte.historyRecordUndo(this.$target);
var index = _.indexOf(this.buildingBlock.snippets, this.$target.get(0));
- for (var i in this.styles){
- this.styles[i].on_remove();
- }
+ this.BuildingBlock.call_for_all_snippets(this.$target, function (editor, $snippet) {
+ for (var i in editor.styles){
+ editor.styles[i].on_remove();
+ }
+ });
delete this.buildingBlock.snippets[index];
var $editable = this.$target.closest(".o_editable");
diff --git a/addons/web_editor/static/src/js/snippets.options.js b/addons/web_editor/static/src/js/snippets.options.js
index 4dce2e358ed..df4ec303894 100644
--- a/addons/web_editor/static/src/js/snippets.options.js
+++ b/addons/web_editor/static/src/js/snippets.options.js
@@ -319,20 +319,20 @@ var colorpicker = Option.extend({
var background = Option.extend({
start: function ($change_target) {
- this.$bg = $change_target || this.$target;
+ this.$target = $change_target || this.$target;
this._super();
- var src = this.$bg.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
- if (this.$bg.hasClass('oe_custom_bg')) {
+ var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
+ if (this.$target.hasClass('oe_custom_bg')) {
this.$el.find('li[data-choose_image]').data("background", src).attr("data-background", src);
}
},
background: function(type, value, $li) {
if (value && value.length) {
- this.$bg.attr("style", 'background-image: url(' + value + '); ' + (this.$bg.attr("style") || '').replace(/background-image:[^;]+/, '') );
- this.$bg.addClass("oe_img_bg");
+ this.$target.attr("style", 'background-image: url(' + value + '); ' + (this.$target.attr("style") || '').replace(/background-image:[^;]+/, '') );
+ this.$target.addClass("oe_img_bg");
} else {
- this.$bg.css("background-image", "");
- this.$bg.removeClass("oe_img_bg").removeClass("oe_custom_bg");
+ this.$target.css("background-image", "");
+ this.$target.removeClass("oe_img_bg").removeClass("oe_custom_bg");
}
},
select_class : function(type, value, $li) {
@@ -345,7 +345,7 @@ var background = Option.extend({
var self = this;
var $image = $('<img class="hidden"/>');
$image.attr("src", value);
- $image.appendTo(self.$bg);
+ $image.appendTo(self.$target);
var _editor = new widget.MediaDialog(null, $image[0]);
_editor.appendTo(document.body);
@@ -356,8 +356,8 @@ var background = Option.extend({
$image.remove();
self.$el.find('li[data-choose_image]').data("background", value).attr("data-background", value);
self.background(type, value,$li);
- self.$bg.addClass('oe_custom_bg');
- self.$bg.trigger("snippet-option-change", [self]);
+ self.$target.addClass('oe_custom_bg');
+ self.$target.trigger("snippet-option-change", [self]);
self.set_active();
});
_editor.on('cancel', self, function () {
@@ -366,9 +366,13 @@ var background = Option.extend({
},
set_active: function () {
var self = this;
- var src = this.$bg.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
+ var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
this._super();
+ if (this.$target.hasClass('oe_custom_bg')) {
+ this.$el.find('li[data-choose_image]').data("background", src).attr("data-background", src);
+ }
+
this.$el.find('li[data-background]:not([data-background=""])')
.removeClass("active")
.each(function () {
diff --git a/addons/web_editor/static/src/js/summernote.js b/addons/web_editor/static/src/js/summernote.js
index 46b21aeec88..235bd6c3500 100644
--- a/addons/web_editor/static/src/js/summernote.js
+++ b/addons/web_editor/static/src/js/summernote.js
@@ -26,7 +26,7 @@ dom.hasContentAfter = function (node) {
var next;
while (node.nextSibling) {
next = node.nextSibling;
- if (next.tagName || next.textContent.match(/\S|\u00A0/) || dom.isBR(next)) return next;
+ if (next.tagName || dom.isVisibleText(next) || dom.isBR(next)) return next;
node = next;
}
};
@@ -34,7 +34,7 @@ dom.hasContentBefore = function (node) {
var prev;
while (node.previousSibling) {
prev = node.previousSibling;
- if (prev.tagName || prev.textContent.match(/\S|\u00A0/) || dom.isBR(prev)) return prev;
+ if (prev.tagName || dom.isVisibleText(prev) || dom.isBR(prev)) return prev;
node = prev;
}
};
@@ -138,7 +138,7 @@ dom.mergeFilter = function (prev, cur, parent) {
if (prev && prev.tagName === "P" && dom.isText(cur)) {
return true;
}
- if (prev && dom.isText(cur) && !cur.textContent.match(/\S|\u00A0/) && (dom.isText(prev) || prev.textContent.match(/\S|\u00A0/))) {
+ if (prev && dom.isText(cur) && !dom.isVisibleText(cur) && (dom.isText(prev) || dom.isVisibleText(prev))) {
return true;
}
if (prev && !dom.isBR(prev) && dom.isEqual(prev, cur) &&
@@ -382,7 +382,7 @@ dom.removeSpace = function (node, begin, so, end, eo) {
if (cur === end) add = false;
// remove begin empty text node
- if (node.childNodes.length > 1 && dom.isText(cur) && !cur.textContent.match(/\S|\u00A0/)) {
+ if (node.childNodes.length > 1 && dom.isText(cur) && !dom.isVisibleText(cur)) {
removed = true;
if (cur === begin) {
so = 0;
@@ -579,7 +579,7 @@ dom.removeBetween = function (sc, so, ec, eo, towrite) {
}
eo = so;
- if(!dom.isBR(sc) && !sc.textContent.match(/\S|\u00A0/) && !dom.isText(dom.hasContentBefore(sc)) && !dom.isText(dom.hasContentAfter(sc))) {
+ if(!dom.isBR(sc) && !dom.isVisibleText(sc) && !dom.isText(dom.hasContentBefore(sc)) && !dom.isText(dom.hasContentAfter(sc))) {
ancestor = dom.node(sc);
var text = document.createTextNode('\u00A0');
$(sc).before(text);
@@ -699,6 +699,9 @@ dom.isContentEditable = function (node) {
return $(node).closest('[contenteditable]').is('[contenteditable="true"]');
};
+dom.isPre = function (node) {
+ return node.tagName === "PRE";
+};
dom.isFont = function (node) {
var nodeName = node && node.nodeName.toUpperCase();
return node && (nodeName === "FONT" ||
@@ -1025,6 +1028,8 @@ $.summernote.pluginEvents.enter = function (event, editor, layoutInfo) {
}
var node = dom.node(r.sc);
+ var exist = r.sc.childNodes[r.so] || r.sc;
+ exist = dom.isVisibleText(exist) || dom.isBR(exist) ? exist : dom.hasContentAfter(exist) || (dom.hasContentBefore(exist) || exist);
var last = node;
while (node && dom.isSplitable(node) && !dom.isList(node)) {
last = node;
@@ -1042,7 +1047,7 @@ $.summernote.pluginEvents.enter = function (event, editor, layoutInfo) {
} while (node && dom.isBR(node));
// create an other br because the user can't see the new line with only br in a block
- if (!node) {
+ if (!node && (!br.nextElementSibling || !dom.isBR(br.nextElementSibling))) {
$(br).before($("<br/>")[0]);
}
node = br.nextSibling || br;
@@ -1051,7 +1056,11 @@ $.summernote.pluginEvents.enter = function (event, editor, layoutInfo) {
node = br;
} else if (!r.so && r.isOnList() && !r.sc.textContent.length && !dom.ancestor(r.sc, dom.isLi).nextElementSibling) {
// double enter on the end of a list = new line out of the list
- node = $('<p></p>').append(br).insertAfter(dom.ancestor(r.sc, dom.isList))[0];
+ $('<p></p>').append(br).insertAfter(dom.ancestor(r.sc, dom.isList));
+ node = br;
+ } else if (dom.isBR(exist) && $(r.sc).closest('blockquote, pre').length && !dom.hasContentAfter($(exist.parentNode).closest('blockquote *, pre *').length ? exist.parentNode : exist)) {
+ // double enter on the end of a blockquote & pre = new line out of the list
+ $('<p></p>').append(br).insertAfter($(r.sc).closest('blockquote, pre'));
node = br;
} else if (last === r.sc) {
if (dom.isBR(last)) {
@@ -1071,7 +1080,7 @@ $.summernote.pluginEvents.enter = function (event, editor, layoutInfo) {
$(cur).html(br);
}
}
- if (!node.textContent.match(/\S|\u00A0/)) {
+ if (!dom.isVisibleText(node)) {
node = dom.firstChild(node);
$(dom.node( dom.isBR(node) ? node.parentNode : node )).html(br);
node = br;
@@ -1105,7 +1114,7 @@ $.summernote.pluginEvents.visible = function (event, editor, layoutInfo) {
while (node.parentNode) {
if (dom.isForbiddenNode(node)) {
var text = node.previousSibling;
- if (text && dom.isText(text) && text.textContent.match(/\S|\u00A0/)) {
+ if (text && dom.isText(text) && dom.isVisibleText(text)) {
range.create(text, text.textContent.length, text, text.textContent.length).select();
} else {
text = node.parentNode.insertBefore(document.createTextNode( "." ), node);
@@ -1715,7 +1724,7 @@ $.summernote.pluginEvents.formatBlock = function (event, editor, layoutInfo, sTa
// fix by odoo because if you select a style in a li with no p tag all the ul is wrapped by the style tag
var nodes = dom.listBetween(r.sc, r.ec);
for (var i=0; i<nodes.length; i++) {
- if (dom.isBR(nodes[i]) || (dom.isText(nodes[i]) && nodes[i].textContent.match(/\S|\u00A0/)) || dom.isB(nodes[i]) || dom.isU(nodes[i]) || dom.isS(nodes[i]) || dom.isI(nodes[i]) || dom.isFont(nodes[i])) {
+ if (dom.isBR(nodes[i]) || (dom.isText(nodes[i]) && dom.isVisibleText(nodes[i])) || dom.isB(nodes[i]) || dom.isU(nodes[i]) || dom.isS(nodes[i]) || dom.isI(nodes[i]) || dom.isFont(nodes[i])) {
var ancestor = dom.ancestor(nodes[i], isFormatNode);
if (!ancestor) {
dom.wrap(nodes[i], sTagName);
diff --git a/addons/web_editor/static/src/js/widgets.js b/addons/web_editor/static/src/js/widgets.js
index acd8c859535..5e852a1c5ef 100644
--- a/addons/web_editor/static/src/js/widgets.js
+++ b/addons/web_editor/static/src/js/widgets.js
@@ -16,6 +16,7 @@ var dom = $.summernote.core.dom;
var Dialog = Widget.extend({
events: {
'hidden.bs.modal': 'destroy',
+ 'keydown.dismiss.bs.modal': 'stop_escape',
'click button.save': 'save',
'click button[data-dismiss="modal"]': 'cancel',
},
@@ -38,6 +39,11 @@ var Dialog = Widget.extend({
close: function () {
this.$el.modal('hide');
},
+ stop_escape: function(event) {
+ if($(".modal.in").length>0 && event.which == 27){
+ event.stopPropagation();
+ }
+ }
});
/**
@@ -251,6 +257,7 @@ var ImageDialog = Widget.extend({
//'change select.image-style': 'preview_image',
'click .existing-attachments [data-src]': 'select_existing',
'click .existing-attachment-remove': 'try_remove',
+ 'keydown.dismiss.bs.modal': function(){},
}),
init: function (parent, media, options) {
this._super();
@@ -339,7 +346,7 @@ var ImageDialog = Widget.extend({
return this.media;
},
clear: function () {
- this.media.className = this.media.className.replace(/(^|\s)(img(\s|$)|img-[^\s]*)/g, ' ');
+ this.media.className = this.media.className.replace(/(^|\s+)(img(\s|$)|img-(?!circle|rounded|thumbnail)[^\s]*)/g, ' ');
},
cancel: function () {
this.trigger('cancel');
@@ -609,6 +616,7 @@ var fontIconsDialog = Widget.extend({
$(".font-icons-icon").removeClass("font-icons-selected");
$(event.target).addClass("font-icons-selected");
},
+ 'keydown.dismiss.bs.modal': function(){},
}),
// extract list of font (like awsome) from the cheatsheet.
@@ -662,12 +670,14 @@ var fontIconsDialog = Widget.extend({
var icons = this.icons;
var style = this.media.attributes.style ? this.media.attributes.style.value : '';
var classes = (this.media.className||"").split(/\s+/);
+ var custom_classes = /^fa(-[1-5]x|spin|rotate-(9|18|27)0|flip-(horizont|vertic)al|fw|border)?$/;
var non_fa_classes = _.reject(classes, function (cls) {
- return self.getFont(cls);
+ return self.getFont(cls) || custom_classes.test(cls);
});
var final_classes = non_fa_classes.concat(this.get_fa_classes());
if (this.media.tagName !== "SPAN") {
var media = document.createElement('span');
+ $(media).data($(this.media).data());
$(this.media).replaceWith(media);
this.media = media;
style = style.replace(/\s*width:[^;]+/, '');
@@ -828,7 +838,8 @@ var VideoDialog = Widget.extend({
'change input#urlvideo': 'change_input',
'keyup input#urlvideo': 'change_input',
'change input#embedvideo': 'change_input',
- 'keyup input#embedvideo': 'change_input'
+ 'keyup input#embedvideo': 'change_input',
+ 'keydown.dismiss.bs.modal': function(){},
}),
init: function (parent, media) {
this._super();
@@ -842,7 +853,6 @@ var VideoDialog = Widget.extend({
if ($media.hasClass("media_iframe_video")) {
var src = $media.data('src');
this.$("input#urlvideo").val(src);
- this.$("#autoplay").attr("checked", src.indexOf('autoplay=1') != -1);
this.get_video();
} else {
this.add_class = "pull-left";
diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js
index 9cc40247b89..61a7622ecbf 100644
--- a/addons/website/static/src/js/website.snippets.editor.js
+++ b/addons/website/static/src/js/website.snippets.editor.js
@@ -81,7 +81,7 @@ options.registry.slider = options.Class.extend({
this.$indicators.append('<li data-target="#' + this.id + '" data-slide-to="' + cycle + '"></li>');
// clone the best candidate from template to use new features
- var $snippets = this.BuildingBlock.$snippets;
+ var $snippets = this.buildingBlock.$snippets;
//since saas-6, all snippets must start by s_
var selection = this.$target.closest('[class*="s_"');
if (_.isUndefined(selection)) {
@@ -174,7 +174,7 @@ options.registry.carousel = options.registry.slider.extend({
};
this.$target.on('slid.bs.carousel', function () {
if(self.editor && self.editor.styles.background) {
- self.editor.styles.background.$bg = self.$target.find(".item.active");
+ self.editor.styles.background.$target = self.$target.find(".item.active");
self.editor.styles.background.set_active();
}
self.$target.carousel("pause");
@@ -264,9 +264,13 @@ options.registry.marginAndResize = options.Class.extend({
} else {
var xy = event['page'+XY];
var current = resize[2] || 0;
+ var margin_dir = {s:'bottom', n: 'top', w: 'left', e: 'right'}[compass];
+ var real_margin = parseInt(self.$target.css('margin-'+margin_dir));
_.each(resize[0], function (val, key) {
if (self.$target.hasClass(val)) {
current = key;
+ } else if (resize[1][key] === real_margin) {
+ current = key;
}
});
var begin = current;
@@ -362,12 +366,12 @@ options.registry.marginAndResize = options.Class.extend({
if (offset === 0) overlay_class+= " block-w-left";
var mb = _class.match(/mb([0-9-]+)/i);
- mb = mb ? +mb[1] : 0;
+ mb = mb ? +mb[1] : parseInt(this.$target.css('margin-bottom'));
if (mb >= 128) overlay_class+= " block-s-bottom";
else if (!mb) overlay_class+= " block-s-top";
var mt = _class.match(/mt([0-9-]+)/i);
- mt = mt ? +mt[1] : 0;
+ mt = mt ? +mt[1] : parseInt(this.$target.css('margin-top'));
if (mt >= 128) overlay_class+= " block-n-top";
else if (!mt) overlay_class+= " block-n-bottom";
diff --git a/addons/website/static/src/js/website.snippets.gallery.js b/addons/website/static/src/js/website.snippets.gallery.js
index 4eb09a4837b..fd6005456e2 100644
--- a/addons/website/static/src/js/website.snippets.gallery.js
+++ b/addons/website/static/src/js/website.snippets.gallery.js
@@ -38,13 +38,9 @@ options.registry.gallery = options.Class.extend({
this._temp_col = this.$el.find("data-columns").data("columns");
},
drop_and_build_snippet: function() {
- var uuid = 0;
- $(".carousel").each(function () {
- var id = parseInt(($(this).attr('id') || '0').replace(/[^0-9]/, ''));
- if (id > uuid) uuid = id;
- });
- this.$target.find('.carousel').attr('id', 'slideshow_'+ (uuid+1));
- this.$target.find('[data-target]').attr('data-target', '#slideshow_'+ (uuid+1));
+ var uuid = new Date().getTime();
+ this.$target.find('.carousel').attr('id', 'slideshow_' + uuid);
+ this.$target.find('[data-target]').attr('data-target', '#slideshow_' + uuid);
},
styling : function(type, value) {
var classes = this.$el.find('li[data-styling]').map(function () {
@@ -194,18 +190,13 @@ options.registry.gallery = options.Class.extend({
var self = this,
$imgs = $(this.get_imgs()),
- urls = $imgs.map(function() { return $(this).attr("src"); } ).get(),
- uuid = 0;
- $(".carousel").each(function () {
- var id = parseInt(($(this).attr('id') || '0').replace(/[^0-9]/, ''));
- if (id > uuid) uuid = id;
- });
+ urls = $imgs.map(function() { return $(this).attr("src"); } ).get();
var params = {
srcs : urls,
- index: 1,
+ index: 0,
title: "",
interval : this.$target.data("interval") || false,
- id: "slideshow_" + (uuid+1)
+ id: "slideshow_" + new Date().getTime()
},
$slideshow = $(qweb.render('website.gallery.slideshow', params));
this.replace($slideshow);
diff --git a/addons/website/static/src/js/website.theme.js b/addons/website/static/src/js/website.theme.js
index f0d45c169ac..7b10fd7ea13 100644
--- a/addons/website/static/src/js/website.theme.js
+++ b/addons/website/static/src/js/website.theme.js
@@ -29,6 +29,12 @@ var Theme = Widget.extend({
template: 'website.theme_customize',
events: {
'change input[data-xmlid],input[data-enable],input[data-disable]': 'change_selection',
+ 'click label:has(input[data-xmlid],input[data-enable],input[data-disable])': function (event) {
+ var self = this;
+ this.time_select = _.defer(function () {
+ self.on_select($(event.target).find('input'));
+ });
+ },
'click .close': 'close',
'click': 'click',
},
@@ -39,7 +45,7 @@ var Theme = Widget.extend({
this.flag = false;
this.$el.addClass("theme_customize_modal");
this.active_select_tags();
- this.$inputs = $("input[data-xmlid],input[data-enable],input[data-disable]");
+ this.$inputs = this.$("input[data-xmlid],input[data-enable],input[data-disable]");
setTimeout(function () {self.$el.addClass('in');}, 0);
this.keydown_escape = function (event) {
if (event.keyCode === 27) {
@@ -92,10 +98,10 @@ var Theme = Widget.extend({
self.$inputs.filter('[data-xmlid=""]').prop("checked", true).change();
self.$inputs.filter('[data-xmlid]:not([data-xmlid=""])').each(function () {
if (!_.difference(self.get_xml_ids($(this)), data[1]).length) {
- $(this).prop("checked", false).change();
+ $(this).prop("checked", false).trigger("change", true);
}
if (!_.difference(self.get_xml_ids($(this)), data[0]).length) {
- $(this).prop("checked", true).change();
+ $(this).prop("checked", true).trigger("change", true);
}
});
}).fail(function (d, error) {
@@ -174,9 +180,8 @@ var Theme = Widget.extend({
return $.Deferred();
}
},
- enable_disable: function (data, enable) {
- if (!data) return;
- this.$('#'+data.split(",").join(", #")).each(function () {
+ enable_disable: function ($inputs, enable) {
+ $inputs.each(function () {
var check = $(this).prop("checked");
var $label = $(this).closest("label");
$(this).prop("checked", enable);
@@ -187,21 +192,31 @@ var Theme = Widget.extend({
}
});
},
- change_selection: function (event) {
+ change_selection: function (event, init_mode) {
var self = this;
- if (this.$el.hasClass("loading")) return;
+ clearTimeout(this.time_select);
+ if (this.$el.hasClass("loading")) return; // prevent to change selection when css is loading
+
var $option = $(event.target),
+ $options = $option,
checked = $option.prop("checked");
if (checked) {
- this.enable_disable($option.data('enable'), true);
- this.enable_disable($option.data('disable'), false);
+ if($option.data('enable')) {
+ var $inputs = this.get_inputs($option.data('enable'));
+ $options = $options.add($inputs.filter(':not(:checked)'));
+ this.enable_disable($inputs, true);
+ }
+ if($option.data('disable')) {
+ var $inputs = this.get_inputs($option.data('disable'));
+ $options = $options.add($inputs.filter(':checked'));
+ this.enable_disable($inputs, false);
+ }
$option.closest("label").addClass("checked");
} else {
$option.closest("label").removeClass("checked");
}
- $option.prop("checked", checked);
var $enable = this.$inputs.filter('[data-xmlid]:checked');
$enable.closest("label").addClass("checked");
@@ -236,14 +251,25 @@ var Theme = Widget.extend({
clearTimeout(this.timer);
if (this.flag) {
- this.timer = setTimeout(function () {
+ this.timer = _.defer(function () {
+ if (!init_mode) self.on_select($options);
self.update_style(self.get_xml_ids($enable), self.get_xml_ids($disable), self.reload);
self.reload = false;
- },0);
+ });
} else {
- this.timer = setTimeout(function () { self.reload = false; },0);
+ this.timer = _.defer(function () {
+ if (!init_mode) self.on_select($options);
+ self.reload = false;
+ });
}
},
+ /* Method call when the user change the selection or click on an input
+ * @values: all changed inputs
+ */
+ on_select: function ($inputs) {
+ clearTimeout(this.time_select);
+ console.log($inputs.get());
+ },
click: function (event) {
if (!$(event.target).closest("#theme_customize_modal > *").length) {
this.close();
diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py
index 4c2e4244608..c4fe2c28e0a 100644
--- a/addons/website_sale/models/product.py
+++ b/addons/website_sale/models/product.py
@@ -161,7 +161,7 @@ class product_template(osv.Model):
class product_product(osv.Model):
_inherit = "product.product"
- # Wrapper for call_kw with inherits
+ # Wrappers for call_kw with inherits
def open_website_url(self, cr, uid, ids, context=None):
template_id = self.browse(cr, uid, ids, context=context).product_tmpl_id.id
return self.pool['product.template'].open_website_url(cr, uid, [template_id], context=context)
@@ -170,6 +170,10 @@ class product_product(osv.Model):
template_id = self.browse(cr, uid, ids, context=context).product_tmpl_id.id
return self.pool['product.template'].website_publish_button(cr, uid, [template_id], context=context)
+ def website_publish_button(self, cr, uid, ids, context=None):
+ template_id = self.browse(cr, uid, ids, context=context).product_tmpl_id.id
+ return self.pool['product.template'].website_publish_button(cr, uid, [template_id], context=context)
+
class product_attribute(osv.Model):
_inherit = "product.attribute"
_columns = {
diff --git a/addons/website_sale/static/src/css/website_sale.css b/addons/website_sale/static/src/css/website_sale.css
index cc569925f5e..9f696a920d1 100644
--- a/addons/website_sale/static/src/css/website_sale.css
+++ b/addons/website_sale/static/src/css/website_sale.css
@@ -29,6 +29,10 @@
}
/* ---- Default Styles ---- */
+
+.o_alternative_product {
+ margin: auto;
+}
.oe_product {
position: relative;
}
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 804da7e61bf..5768de061f4 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -517,12 +517,12 @@
<h3>Suggested alternatives:</h3>
<div class='row mt16' style="">
<t t-foreach="product.alternative_product_ids" t-as="product">
- <div class='col-md-2 thumbnail' style='width: 170px; height:130px; float:left; display:inline; margin-right: 10px;'>
- <div class='mt16 text-center'>
- <span t-field="product.image_small" t-field-options='{"widget": "image", "class": "img-rounded shadow" }'/>
+ <div class='col-md-2 thumbnail' style='width: 170px; height:130px; float:left; display:inline; margin-right: 10px; overflow:hidden;'>
+ <div class='mt16 text-center' style="height: 100%;">
+ <div t-field="product.image_small" t-field-options='{"widget": "image", "class": "img-rounded shadow o_alternative_product" }'/>
<h5>
<a t-attf-href="/shop/product/#{ slug(product) }" style="display: block">
- <span t-field='product.name' style="display: block"/>
+ <span t-att-title='product.name' t-field='product.name' style="display: block;"/>
</a>
</h5>
</div>
diff --git a/doc/cla/corporate/ITLibertas.md b/doc/cla/corporate/ITLibertas.md
new file mode 100644
index 00000000000..06626c6d5ad
--- /dev/null
+++ b/doc/cla/corporate/ITLibertas.md
@@ -0,0 +1,17 @@
+Russia, 2015-08-14
+
+IT Libertas agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+IT Libertas LLC info@itlibertas.com https://github.com/itlibertas
+
+List of contributors:
+
+Denis Baranov baranov@itlibertas.com https://github.com/barachka
+Tatiana Deribina deribina@itlibertas.com https://github.com/Sapfiriana
+Aleksandr Shevelev shevelev@itlibertas.com https://github.com/alexalv
diff --git a/doc/cla/individual/flh.md b/doc/cla/individual/flh.md
new file mode 100644
index 00000000000..90860f3e0c4
--- /dev/null
+++ b/doc/cla/individual/flh.md
@@ -0,0 +1,11 @@
+France, 2015-08-18
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Florian Hatat github@hatat.me https://github.com/flh
diff --git a/openerp/addons/base/ir/ir_http.py b/openerp/addons/base/ir/ir_http.py
index 577bed483be..a30f0da1850 100644
--- a/openerp/addons/base/ir/ir_http.py
+++ b/openerp/addons/base/ir/ir_http.py
@@ -107,11 +107,9 @@ class ir_http(osv.AbstractModel):
datas = attach[0]['datas'] or ''
name = attach[0]['name']
- if not datas:
- if name.startswith(('http://', 'https://', '/')):
- return werkzeug.utils.redirect(name, 301)
- else:
- return werkzeug.wrappers.Response(status=204) # NO CONTENT
+ if (not datas and name != request.httprequest.path and
+ name.startswith(('http://', 'https://', '/'))):
+ return werkzeug.utils.redirect(name, 301)
response = werkzeug.wrappers.Response()
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
diff --git a/openerp/addons/base/rng/view.rng b/openerp/addons/base/rng/view.rng
index 3eee1860024..f2bcd74bc8a 100644
--- a/openerp/addons/base/rng/view.rng
+++ b/openerp/addons/base/rng/view.rng
@@ -260,6 +260,11 @@
<rng:optional><rng:attribute name="decoration-success"/></rng:optional>
<rng:optional><rng:attribute name="decoration-warning"/></rng:optional>
<rng:optional>
+ <rng:attribute name="limit">
+ <rng:data type="int"/>
+ </rng:attribute>
+ </rng:optional>
+ <rng:optional>
<rng:attribute name="editable">
<rng:choice>
<rng:value>top</rng:value>
diff --git a/openerp/addons/base/tests/test_search.py b/openerp/addons/base/tests/test_search.py
index bd9d5089356..6cb8490546d 100644
--- a/openerp/addons/base/tests/test_search.py
+++ b/openerp/addons/base/tests/test_search.py
@@ -5,6 +5,18 @@ import openerp.tests.common as common
class test_search(common.TransactionCase):
+ def patch_order(self, model, order):
+ m_e = self.env[model]
+ m_r = self.registry(model)
+
+ old_order = m_e._order
+
+ @self.addCleanup
+ def cleanup():
+ m_r._order = type(m_e)._order = old_order
+
+ m_r._order = type(m_e)._order = order
+
def test_00_search_order(self):
registry, cr, uid = self.registry, self.cr, self.uid
@@ -96,17 +108,13 @@ class test_search(common.TransactionCase):
self.assertEqual(test_user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
# Do: order on many2one, but not by specifying in order parameter of search, but by overriding _order of res_users
- users_class = type(users_obj)
- users_order = users_class._order
- users_class._order = 'country_id desc, name asc, login desc'
- self.addCleanup(setattr, users_class, '_order', users_order)
-
+ self.patch_order('res.users', 'country_id desc, name asc, login desc')
user_ids = users_obj.search(cr, search_user, [])
expected_ids = [search_user, c, b, a]
test_user_ids = filter(lambda x: x in expected_ids, user_ids)
self.assertEqual(test_user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
- def test_11_indirect_inherits_m2order(self):
+ def test_11_indirect_inherits_m2o_order(self):
registry, cr, uid = self.registry, self.cr, self.uid
Cron = registry('ir.cron')
Users = registry('res.users')
@@ -121,6 +129,48 @@ class test_search(common.TransactionCase):
expected_ids = [cron_ids[l] for l in 'ABC']
self.assertEqual(ids, expected_ids)
+ def test_12_m2o_order_loop_self(self):
+ registry, cr, uid = self.registry, self.cr, self.uid
+
+ Cats = registry('ir.module.category')
+ ids = {}
+ def create(name, **kw):
+ ids[name] = Cats.create(cr, uid, dict(kw, name=name))
+
+ self.patch_order('ir.module.category', 'parent_id desc, name')
+
+ create('A')
+ create('B', parent_id=ids['A'])
+ create('C', parent_id=ids['A'])
+ create('D')
+ create('E', parent_id=ids['D'])
+ create('F', parent_id=ids['D'])
+
+ expected_order = [ids[x] for x in 'ADEFBC']
+ domain = [('id', 'in', ids.values())]
+ search_result = Cats.search(cr, uid, domain)
+ self.assertEqual(search_result, expected_order)
+
+ def test_13_m2o_order_loop_multi(self):
+ Users = self.env['res.users']
+
+ # will sort by login desc of the creator, then by name
+ self.patch_order('res.partner', 'create_uid, name')
+ self.patch_order('res.users', 'partner_id, login desc')
+
+ kw = dict(groups_id=[(6, 0, [self.ref('base.group_system'),
+ self.ref('base.group_partner_manager')])])
+
+ u1 = Users.create(dict(name='Q', login='m', **kw)).id
+ u2 = Users.sudo(user=u1).create(dict(name='B', login='f', **kw)).id
+ u3 = Users.create(dict(name='C', login='c', **kw)).id
+ u4 = Users.sudo(user=u2).create(dict(name='D', login='z', **kw)).id
+
+ expected_order = [u2, u4, u3, u1]
+
+ domain = [('id', 'in', [u1, u2, u3, u4])]
+ search_result = list(Users.search(domain)._ids)
+ self.assertEqual(search_result, expected_order)
if __name__ == '__main__':
unittest2.main()
diff --git a/openerp/models.py b/openerp/models.py
index 93cc537d12f..6aaecba62be 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -4541,7 +4541,7 @@ class BaseModel(object):
return '"%s"."%s"' % (table_alias, field)
@api.model
- def _generate_m2o_order_by(self, alias, order_field, query, reverse_direction):
+ def _generate_m2o_order_by(self, alias, order_field, query, reverse_direction, seen):
"""
Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
either native m2o fields or function/related fields that are stored, including
@@ -4551,18 +4551,18 @@ class BaseModel(object):
"""
if order_field not in self._columns and order_field in self._inherit_fields:
# also add missing joins for reaching the table containing the m2o field
- qualified_field = self._inherits_join_calc(alias, order_field, query)
order_field_column = self._inherit_fields[order_field][2]
+ qualified_field = self._inherits_join_calc(alias, order_field, query)
+ alias, order_field = qualified_field.replace('"', '').split('.', 1)
else:
- qualified_field = '"%s"."%s"' % (alias, order_field)
order_field_column = self._columns[order_field]
assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
if not order_field_column._classic_write and not getattr(order_field_column, 'store', False):
- _logger.debug("Many2one function/related fields must be stored " \
- "to be used as ordering fields! Ignoring sorting for %s.%s",
- self._name, order_field)
- return
+ _logger.debug("Many2one function/related fields must be stored "
+ "to be used as ordering fields! Ignoring sorting for %s.%s",
+ self._name, order_field)
+ return []
# figure out the applicable order_by for the m2o
dest_model = self.env[order_field_column._obj]
@@ -4573,13 +4573,15 @@ class BaseModel(object):
# Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
# as we don't want to exclude results that have NULL values for the m2o
- src_table, src_field = qualified_field.replace('"', '').split('.', 1)
- dst_alias, dst_alias_statement = query.add_join((src_table, dest_model._table, src_field, 'id', src_field), implicit=False, outer=True)
+ join = (alias, dest_model._table, order_field, 'id', order_field)
+ dst_alias, dst_alias_statement = query.add_join(join, implicit=False, outer=True)
return dest_model._generate_order_by_inner(dst_alias, m2o_order, query,
- reverse_direction=reverse_direction)
+ reverse_direction=reverse_direction, seen=seen)
@api.model
- def _generate_order_by_inner(self, alias, order_spec, query, reverse_direction=False):
+ def _generate_order_by_inner(self, alias, order_spec, query, reverse_direction=False, seen=None):
+ if seen is None:
+ seen = set()
order_by_elements = []
self._check_qorder(order_spec)
for order_part in order_spec.split(','):
@@ -4603,7 +4605,10 @@ class BaseModel(object):
inner_clauses = ['"%s"."%s"' % (alias, order_field)]
add_dir = True
elif order_column._type == 'many2one':
- inner_clauses = self._generate_m2o_order_by(alias, order_field, query, do_reverse)
+ key = (self._name, order_column._obj, order_field)
+ if key not in seen:
+ seen.add(key)
+ inner_clauses = self._generate_m2o_order_by(alias, order_field, query, do_reverse, seen)
else:
continue # ignore non-readable or "non-joinable" fields
elif order_field in self._inherit_fields:
@@ -4613,7 +4618,10 @@ class BaseModel(object):
inner_clauses = [self._inherits_join_calc(alias, order_field, query)]
add_dir = True
elif order_column._type == 'many2one':
- inner_clauses = self._generate_m2o_order_by(alias, order_field, query, do_reverse)
+ key = (parent_obj._name, order_column._obj, order_field)
+ if key not in seen:
+ seen.add(key)
+ inner_clauses = self._generate_m2o_order_by(alias, order_field, query, do_reverse, seen)
else:
continue # ignore non-readable or "non-joinable" fields
else: