[MERGE] forward port of branch saas-6 up to e16d495
This commit is contained in:
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# KEYBOARD CONFIGURATION FILE
|
||||
|
||||
# Consult the keyboard(5) manual page.
|
||||
|
||||
XKBMODEL="pc105"
|
||||
XKBLAYOUT="us"
|
||||
XKBVARIANT=""
|
||||
XKBOPTIONS=""
|
||||
|
||||
BACKSPACE="guess"
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
SUBSYSTEM=="usb", GROUP="usbusers", MODE="0660"
|
||||
SUBSYSTEMS=="usb", GROUP="usbusers", MODE="0660"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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")
|
||||
|
||||
@@ -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','<>',False)]"/>
|
||||
<field name="analytics_id" groups="purchase.group_analytic_accounting" context="{'journal_id':parent.journal_id, 'model': 'purchase_order'}" domain="[('plan_id','<>',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','<>',False)]"/>
|
||||
<field name="analytics_id" groups="purchase.group_analytic_accounting" context="{'journal_id':parent.journal_id}" domain="[('plan_id','<>',False)]"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'):
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
}
|
||||
|
||||
/* ---- Default Styles ---- */
|
||||
|
||||
.o_alternative_product {
|
||||
margin: auto;
|
||||
}
|
||||
.oe_product {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -259,6 +259,11 @@
|
||||
<rng:optional><rng:attribute name="decoration-primary"/></rng:optional>
|
||||
<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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
+21
-13
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user