[MERGE] forward port branch 9.0 up to b795755

This commit is contained in:
Christophe Simonis
2016-11-08 14:20:07 +01:00
36 changed files with 273 additions and 152 deletions
@@ -352,7 +352,7 @@ class AccountBankStatement(models.Model):
class AccountBankStatementLine(models.Model): class AccountBankStatementLine(models.Model):
_name = "account.bank.statement.line" _name = "account.bank.statement.line"
_description = "Bank Statement Line" _description = "Bank Statement Line"
_order = "statement_id desc, sequence" _order = "statement_id desc, sequence, id desc"
_inherit = ['ir.needaction_mixin'] _inherit = ['ir.needaction_mixin']
name = fields.Char(string='Memo', required=True) name = fields.Char(string='Memo', required=True)
@@ -956,8 +956,23 @@ class AccountBankStatementLine(models.Model):
aml_dict['currency_id'] = statement_currency.id aml_dict['currency_id'] = statement_currency.id
# Create write-offs # Create write-offs
# When we register a payment on an invoice, the write-off line contains the amount
# currency if all related invoices have the same currency. We apply the same logic in
# the manual reconciliation.
counterpart_aml = self.env['account.move.line']
for aml_dict in counterpart_aml_dicts:
counterpart_aml |= aml_dict.get('move_line', self.env['account.move.line'])
new_aml_currency = False
if counterpart_aml\
and len(counterpart_aml.mapped('currency_id')) == 1\
and counterpart_aml[0].currency_id\
and counterpart_aml[0].currency_id != company_currency:
new_aml_currency = counterpart_aml[0].currency_id
for aml_dict in new_aml_dicts: for aml_dict in new_aml_dicts:
aml_dict['payment_id'] = payment_id aml_dict['payment_id'] = payment_id
if new_aml_currency and not aml_dict.get('currency_id'):
aml_dict['currency_id'] = new_aml_currency.id
aml_dict['amount_currency'] = company_currency.with_context(ctx).compute(aml_dict['debit'] - aml_dict['credit'], new_aml_currency)
aml_obj.with_context(check_move_validity=False).create(aml_dict) aml_obj.with_context(check_move_validity=False).create(aml_dict)
# Create counterpart move lines and reconcile them # Create counterpart move lines and reconcile them
+3 -1
View File
@@ -918,6 +918,8 @@ class AccountMoveLine(models.Model):
first_line_dict['account_id'] = self[0].account_id.id first_line_dict['account_id'] = self[0].account_id.id
if 'analytic_account_id' in first_line_dict: if 'analytic_account_id' in first_line_dict:
del first_line_dict['analytic_account_id'] del first_line_dict['analytic_account_id']
if 'tax_ids' in first_line_dict:
del first_line_dict['tax_ids']
# Writeoff line in specified writeoff account # Writeoff line in specified writeoff account
second_line_dict = vals.copy() second_line_dict = vals.copy()
@@ -1244,7 +1246,7 @@ class AccountMoveLine(models.Model):
""" Prepare the values used to create() an account.analytic.line upon validation of an account.move.line having """ Prepare the values used to create() an account.analytic.line upon validation of an account.move.line having
an analytic account. This method is intended to be extended in other modules. an analytic account. This method is intended to be extended in other modules.
""" """
amount = (self.credit or 0.0) - (self.debit or 0.0) amount = (self.debit or 0.0) - (self.credit or 0.0)
return { return {
'name': self.name, 'name': self.name,
'date': self.date, 'date': self.date,
+1 -1
View File
@@ -242,7 +242,7 @@ class TestReconciliation(AccountingTestCase):
self.check_results(bank_stmt.move_line_ids, { self.check_results(bank_stmt.move_line_ids, {
self.account_euro.id: {'debit': 40.0, 'credit': 0.0, 'amount_currency': 0, 'currency_id': False}, self.account_euro.id: {'debit': 40.0, 'credit': 0.0, 'amount_currency': 0, 'currency_id': False},
self.account_rcv.id: {'debit': 0.0, 'credit': 32.7, 'amount_currency': -41.97, 'currency_id': self.currency_usd_id, 'currency_diff': 0, 'amount_currency_diff': -8.03}, self.account_rcv.id: {'debit': 0.0, 'credit': 32.7, 'amount_currency': -41.97, 'currency_id': self.currency_usd_id, 'currency_diff': 0, 'amount_currency_diff': -8.03},
self.diff_income_account.id: {'debit': 0.0, 'credit': 7.3, 'amount_currency': 0, 'currency_id': False}, self.diff_income_account.id: {'debit': 0.0, 'credit': 7.3, 'amount_currency': -9.37, 'currency_id': self.currency_usd_id},
}) })
# The invoice should be paid, as the payments totally cover its total # The invoice should be paid, as the payments totally cover its total
+21 -6
View File
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import calendar
from datetime import date, datetime from datetime import date, datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from openerp import api, fields, models, _ from openerp import api, fields, models, _
from openerp.exceptions import UserError, ValidationError from openerp.exceptions import UserError, ValidationError
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
from openerp.tools import float_compare from openerp.tools import float_compare, float_is_zero
class AccountAssetCategory(models.Model): class AccountAssetCategory(models.Model):
@@ -150,14 +151,26 @@ class AccountAssetAsset(models.Model):
if self.prorata: if self.prorata:
amount = amount_to_depr / self.method_number amount = amount_to_depr / self.method_number
if sequence == 1: if sequence == 1:
days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1 if self.method_period % 12 != 0:
amount = (amount_to_depr / self.method_number) / total_days * days date = datetime.strptime(self.date, '%Y-%m-%d')
month_days = calendar.monthrange(date.year, date.month)[1]
days = month_days - date.day + 1
amount = (amount_to_depr / self.method_number) / month_days * days
else:
days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1
amount = (amount_to_depr / self.method_number) / total_days * days
elif self.method == 'degressive': elif self.method == 'degressive':
amount = residual_amount * self.method_progress_factor amount = residual_amount * self.method_progress_factor
if self.prorata: if self.prorata:
if sequence == 1: if sequence == 1:
days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1 if self.method_period % 12 != 0:
amount = (residual_amount * self.method_progress_factor) / total_days * days date = datetime.strptime(self.date, '%Y-%m-%d')
month_days = calendar.monthrange(date.year, date.month)[1]
days = month_days - date.day + 1
amount = (residual_amount * self.method_progress_factor) / month_days * days
else:
days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1
amount = (residual_amount * self.method_progress_factor) / total_days * days
return amount return amount
def _compute_board_undone_dotation_nb(self, depreciation_date, total_days): def _compute_board_undone_dotation_nb(self, depreciation_date, total_days):
@@ -210,6 +223,8 @@ class AccountAssetAsset(models.Model):
sequence = x + 1 sequence = x + 1
amount = self._compute_board_amount(sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date) amount = self._compute_board_amount(sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date)
amount = self.currency_id.round(amount) amount = self.currency_id.round(amount)
if float_is_zero(amount, precision_rounding=self.currency_id.rounding):
continue
residual_amount -= amount residual_amount -= amount
vals = { vals = {
'amount': amount, 'amount': amount,
@@ -407,7 +422,7 @@ class AccountAssetAsset(models.Model):
'res_model': 'account.move', 'res_model': 'account.move',
'view_id': False, 'view_id': False,
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'domain': [('id', 'in', move_ids)] 'domain': [('id', 'in', move_ids)],
} }
@@ -54,7 +54,7 @@ class TestAccountAsset(common.TransactionCase):
asset_modify_number_0.with_context({'active_id': account_asset_asset_office0.id}).modify() asset_modify_number_0.with_context({'active_id': account_asset_asset_office0.id}).modify()
# I check the proper depreciation lines created. # I check the proper depreciation lines created.
self.assertEqual(account_asset_asset_office0.method_number, len(account_asset_asset_office0.depreciation_line_ids) - 1) self.assertEqual(account_asset_asset_office0.method_number, len(account_asset_asset_office0.depreciation_line_ids))
# I compute a asset on period. # I compute a asset on period.
context = { context = {
"active_ids": [self.ref("account_asset.menu_asset_depreciation_confirmation_wizard")], "active_ids": [self.ref("account_asset.menu_asset_depreciation_confirmation_wizard")],
+14 -4
View File
@@ -18,6 +18,7 @@ bus.Bus = Widget.extend({
this._super(); this._super();
this.options = {}; this.options = {};
this.activated = false; this.activated = false;
this.bus_id = _.uniqueId('bus');
this.channels = []; this.channels = [];
this.last = 0; this.last = 0;
this.stop = false; this.stop = false;
@@ -32,15 +33,24 @@ bus.Bus = Widget.extend({
this.trigger('window_focus', this.is_master); this.trigger('window_focus', this.is_master);
} }
}); });
$(window).on("focus", _.bind(this.focus_change, this, true)); $(window).on("focus." + this.bus_id, _.bind(this.focus_change, this, true));
$(window).on("blur", _.bind(this.focus_change, this, false)); $(window).on("blur." + this.bus_id, _.bind(this.focus_change, this, false));
$(window).on("unload", _.bind(this.focus_change, this, false)); $(window).on("unload." + this.bus_id, _.bind(this.focus_change, this, false));
_.each('click,keydown,keyup'.split(','), function(evtype) { _.each('click,keydown,keyup'.split(','), function(evtype) {
$(window).on(evtype, function() { $(window).on(evtype + "." + self.bus_id, function() {
self.last_presence = new Date().getTime(); self.last_presence = new Date().getTime();
}); });
}); });
}, },
destroy: function () {
var self = this;
$(window).off("focus." + this.bus_id);
$(window).off("blur." + this.bus_id);
$(window).off("unload." + this.bus_id);
_.each('click,keydown,keyup'.split(','), function(evtype) {
$(window).off(evtype + "." + self.bus_id);
});
},
start_polling: function(){ start_polling: function(){
if(!this.activated){ if(!this.activated){
this.poll(); this.poll();
+6 -1
View File
@@ -79,7 +79,7 @@ class StockPicking(models.Model):
def _compute_shipping_weight(self): def _compute_shipping_weight(self):
self.shipping_weight = self.weight_bulk + sum([pack.shipping_weight for pack in self.package_ids]) self.shipping_weight = self.weight_bulk + sum([pack.shipping_weight for pack in self.package_ids])
carrier_price = fields.Float(string="Shipping Cost", readonly=True) carrier_price = fields.Float(string="Shipping Cost")
delivery_type = fields.Selection(related='carrier_id.delivery_type', readonly=True) delivery_type = fields.Selection(related='carrier_id.delivery_type', readonly=True)
carrier_id = fields.Many2one("delivery.carrier", string="Carrier") carrier_id = fields.Many2one("delivery.carrier", string="Carrier")
volume = fields.Float(copy=False) volume = fields.Float(copy=False)
@@ -91,6 +91,11 @@ class StockPicking(models.Model):
weight_bulk = fields.Float('Bulk Weight', compute='_compute_bulk_weight') weight_bulk = fields.Float('Bulk Weight', compute='_compute_bulk_weight')
shipping_weight = fields.Float("Weight for Shipping", compute='_compute_shipping_weight') shipping_weight = fields.Float("Weight for Shipping", compute='_compute_shipping_weight')
@api.onchange('carrier_id')
def onchange_carrier(self):
if self.carrier_id.delivery_type in ['fixed', 'base_on_rule']:
self.carrier_price = self.carrier_id.price
@api.depends('product_id', 'move_lines') @api.depends('product_id', 'move_lines')
def _cal_weight(self): def _cal_weight(self):
for picking in self: for picking in self:
@@ -27,6 +27,10 @@ var OdooSupport = Widget.extend({
// bind event // bind event
$(window).on("odoo_support_ready_to_bind", this, _.bind(this.bind_actions, this)); $(window).on("odoo_support_ready_to_bind", this, _.bind(this.bind_actions, this));
}, },
destroy: function () {
$(window).off("odoo_support_ready_to_bind");
return this._super.apply(this, arguments);
},
bind_actions: function(event, button){ bind_actions: function(event, button){
if (button === 'usermenu'){ if (button === 'usermenu'){
$('.oe_user_menu_placeholder .odoo_support_contact').on('click', this, _.bind(this.click_action, this)); $('.oe_user_menu_placeholder .odoo_support_contact').on('click', this, _.bind(this.click_action, this));
@@ -661,6 +661,10 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
target: 'current' target: 'current'
}); });
}, },
destroy: function() {
this.$buttons.off().destroy();
this._super.apply(this, arguments);
},
}); });
+5
View File
@@ -461,6 +461,11 @@ var BasicComposer = Widget.extend({
return this._super(); return this._super();
}, },
destroy: function () {
$(window).off(this.fileupload_id);
return this._super.apply(this, arguments);
},
toggle: function(state) { toggle: function(state) {
this.$el.toggle(state); this.$el.toggle(state);
}, },
+5 -4
View File
@@ -214,7 +214,7 @@ class PosOrder(models.Model):
if move is None: if move is None:
# Create an entry for the sale # Create an entry for the sale
journal_id = self.env['ir.config_parameter'].sudo().get_param( journal_id = self.env['ir.config_parameter'].sudo().get_param(
'pos.closing.journal_id', default=order.sale_journal.id) 'pos.closing.journal_id_%s' % current_company.id, default=order.sale_journal.id)
move = self._create_account_move( move = self._create_account_move(
order.session_id.start_at, order.name, int(journal_id), order.company_id.id) order.session_id.start_at, order.name, int(journal_id), order.company_id.id)
@@ -598,7 +598,7 @@ class PosOrder(models.Model):
if moves and not return_picking and not order_picking: if moves and not return_picking and not order_picking:
moves.action_confirm() moves.action_confirm()
moves.force_assign() moves.force_assign()
moves.action_done() moves.filtered(lambda m: m.product_id.tracking == 'none').action_done()
return True return True
@@ -608,9 +608,10 @@ class PosOrder(models.Model):
picking.action_confirm() picking.action_confirm()
picking.force_assign() picking.force_assign()
# Mark pack operations as done # Mark pack operations as done
for pack in picking.pack_operation_ids: for pack in picking.pack_operation_ids.filtered(lambda x: x.product_id.tracking == 'none'):
pack.write({'qty_done': pack.product_qty}) pack.write({'qty_done': pack.product_qty})
picking.action_done() if not any([(x.product_id.tracking != 'none') for x in picking.pack_operation_ids]):
picking.action_done()
def add_payment(self, data): def add_payment(self, data):
"""Create a new payment for the order""" """Create a new payment for the order"""
+1 -1
View File
@@ -19,7 +19,7 @@ class PosSession(models.Model):
company_id = session.config_id.journal_id.company_id.id company_id = session.config_id.journal_id.company_id.id
orders = session.order_ids.filtered(lambda order: order.state == 'paid') orders = session.order_ids.filtered(lambda order: order.state == 'paid')
journal_id = self.env['ir.config_parameter'].sudo().get_param( journal_id = self.env['ir.config_parameter'].sudo().get_param(
'pos.closing.journal_id', default=session.config_id.journal_id.id) 'pos.closing.journal_id_%s' % company_id, default=session.config_id.journal_id.id)
move = self.env['pos.order'].with_context(force_company=company_id)._create_account_move(session.start_at, session.name, int(journal_id), company_id) move = self.env['pos.order'].with_context(force_company=company_id)._create_account_move(session.start_at, session.name, int(journal_id), company_id)
orders.with_context(force_company=company_id)._create_account_move_line(session, move) orders.with_context(force_company=company_id)._create_account_move_line(session, move)
for order in session.order_ids.filtered(lambda o: o.state != 'done'): for order in session.order_ids.filtered(lambda o: o.state != 'done'):
+22 -9
View File
@@ -1821,11 +1821,9 @@ var PaymentScreenWidget = ScreenWidget.extend({
self.$('.next').removeClass('highlight'); self.$('.next').removeClass('highlight');
} }
}, },
// Check if the order is paid, then sends it to the backend,
// and complete the sale process
validate_order: function(force_validation) {
var self = this;
order_is_valid: function(force_validation) {
var self = this;
var order = this.pos.get_order(); var order = this.pos.get_order();
// FIXME: this check is there because the backend is unable to // FIXME: this check is there because the backend is unable to
@@ -1835,7 +1833,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
'title': _t('Empty Order'), 'title': _t('Empty Order'),
'body': _t('There must be at least one product in your order before it can be validated'), 'body': _t('There must be at least one product in your order before it can be validated'),
}); });
return; return false;
} }
var plines = order.get_paymentlines(); var plines = order.get_paymentlines();
@@ -1845,12 +1843,12 @@ var PaymentScreenWidget = ScreenWidget.extend({
'message': _t('Negative Bank Payment'), 'message': _t('Negative Bank Payment'),
'comment': _t('You cannot have a negative amount in a Bank payment. Use a cash payment method to return money to the customer.'), 'comment': _t('You cannot have a negative amount in a Bank payment. Use a cash payment method to return money to the customer.'),
}); });
return; return false;
} }
} }
if (!order.is_paid() || this.invoicing) { if (!order.is_paid() || this.invoicing) {
return; return false;
} }
// The exact amount must be paid if there is no cash payment method defined. // The exact amount must be paid if there is no cash payment method defined.
@@ -1864,7 +1862,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
title: _t('Cannot return change without a cash payment method'), title: _t('Cannot return change without a cash payment method'),
body: _t('There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration'), body: _t('There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration'),
}); });
return; return false;
} }
} }
@@ -1885,9 +1883,16 @@ var PaymentScreenWidget = ScreenWidget.extend({
self.validate_order('confirm'); self.validate_order('confirm');
}, },
}); });
return; return false;
} }
return true;
},
finalize_validation: function() {
var self = this;
var order = this.pos.get_order();
if (order.is_paid_with_cash() && this.pos.config.iface_cashdrawer) { if (order.is_paid_with_cash() && this.pos.config.iface_cashdrawer) {
this.pos.proxy.open_cashbox(); this.pos.proxy.open_cashbox();
@@ -1937,6 +1942,14 @@ var PaymentScreenWidget = ScreenWidget.extend({
} }
}, },
// Check if the order is paid, then sends it to the backend,
// and complete the sale process
validate_order: function(force_validation) {
if (this.order_is_valid(force_validation)) {
this.finalize_validation();
}
},
}); });
gui.define_screen({name:'payment', widget: PaymentScreenWidget}); gui.define_screen({name:'payment', widget: PaymentScreenWidget});
+5 -5
View File
@@ -108,7 +108,7 @@
</group> </group>
<group name="group_standard_price"> <group name="group_standard_price">
<field name="list_price" widget='monetary' options="{'currency_field': 'currency_id'}"/> <field name="list_price" widget='monetary' options="{'currency_field': 'currency_id'}"/>
<label for="standard_price" groups="base.group_user" attrs="{'invisible': [('product_variant_count', '&gt;', 1)]}"/> <label for="standard_price" groups="base.group_user"/>
<div name="standard_price_uom" groups="base.group_user"> <div name="standard_price_uom" groups="base.group_user">
<field name="standard_price" widget='monetary' options="{'currency_field': 'currency_id'}" class="oe_inline"/> <field name="standard_price" widget='monetary' options="{'currency_field': 'currency_id'}" class="oe_inline"/>
</div> </div>
@@ -126,12 +126,12 @@
<field name="categ_id" string="Internal Category"/> <field name="categ_id" string="Internal Category"/>
</group> </group>
<group name="group_lots_and_weight"> <group name="group_lots_and_weight">
<label for="weight" attrs="{'invisible':['|', ('type', 'not in', ['product', 'consu']), ('product_variant_count', '&gt;', 1)]}"/> <label for="weight" attrs="{'invisible':[('type', 'not in', ['product', 'consu']), ('product_variant_count', '&gt;', 1)]}"/>
<div class="o_row" name="weight" attrs="{'invisible':['|', ('type', 'not in', ['product', 'consu']), ('product_variant_count', '&gt;', 1)]}"> <div class="o_row" name="weight" attrs="{'invisible':[('type', 'not in', ['product', 'consu']), ('product_variant_count', '&gt;', 1)]}">
<field name="weight"/> <field name="weight"/>
</div> </div>
<label for="volume" attrs="{'invisible':['|', ('type', 'not in', ['product', 'consu']), ('product_variant_count', '&gt;', 1)]}"/> <label for="volume" attrs="{'invisible':[('type', 'not in', ['product', 'consu']), ('product_variant_count', '&gt;', 1)]}"/>
<div class="o_row" name="volume" attrs="{'invisible':['|', ('type', 'not in', ['product', 'consu']), ('product_variant_count', '&gt;', 1)]}"> <div class="o_row" name="volume" attrs="{'invisible':[('type', 'not in', ['product', 'consu']), ('product_variant_count', '&gt;', 1)]}">
<field name="volume" string="Volume"/> <field name="volume" string="Volume"/>
</div> </div>
</group> </group>
@@ -155,7 +155,6 @@
<t t-name="kanban-tooltip"> <t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip"> <ul class="oe_kanban_tooltip">
<li><b>Project:</b> <field name="project_id"/></li> <li><b>Project:</b> <field name="project_id"/></li>
<li><b>Category:</b> <field name="tag_ids"/></li>
</ul> </ul>
</t> </t>
<t t-name="kanban-box"> <t t-name="kanban-box">
@@ -74,7 +74,7 @@
<field name="name">report.intrastat.tree</field> <field name="name">report.intrastat.tree</field>
<field name="model">report.intrastat</field> <field name="model">report.intrastat</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Intrastat Data" create="false"> <tree string="Intrastat Data" create="false" delete="false">
<field name="name" /> <field name="name" />
<field name="month"/> <field name="month"/>
<field name="code"/> <field name="code"/>
+3 -2
View File
@@ -812,13 +812,14 @@ class SaleOrderLine(models.Model):
vals = {} vals = {}
domain = {'product_uom': [('category_id', '=', self.product_id.uom_id.category_id.id)]} domain = {'product_uom': [('category_id', '=', self.product_id.uom_id.category_id.id)]}
if not self.product_uom or (self.product_id.uom_id.category_id.id != self.product_uom.category_id.id): if not self.product_uom or (self.product_id.uom_id.id != self.product_uom.id):
vals['product_uom'] = self.product_id.uom_id vals['product_uom'] = self.product_id.uom_id
vals['product_uom_qty'] = 1.0
product = self.product_id.with_context( product = self.product_id.with_context(
lang=self.order_id.partner_id.lang, lang=self.order_id.partner_id.lang,
partner=self.order_id.partner_id.id, partner=self.order_id.partner_id.id,
quantity=self.product_uom_qty, quantity=vals.get('product_uom_qty') or self.product_uom_qty,
date=self.order_id.date_order, date=self.order_id.date_order,
pricelist=self.order_id.pricelist_id.id, pricelist=self.order_id.pricelist_id.id,
uom=self.product_uom.id uom=self.product_uom.id
+1 -1
View File
@@ -39,7 +39,7 @@
</field> </field>
<field name="standard_price" position="replace"> <field name="standard_price" position="replace">
<field name="standard_price" <field name="standard_price"
attrs="{'readonly':['&amp;', ('valuation','=','real_time'), ('cost_method', 'in', ['standard', 'average'])], 'invisible': [('product_variant_count', '&gt;', 1)]}" class="oe_inline" attrs="{'readonly':['&amp;', ('valuation','=','real_time'), ('cost_method', 'in', ['standard', 'average'])]}" class="oe_inline"
widget='monetary' options="{'currency_field': 'currency_id'}"/> widget='monetary' options="{'currency_field': 'currency_id'}"/>
</field> </field>
<div name="standard_price_uom" position="after"> <div name="standard_price_uom" position="after">
-1
View File
@@ -1,3 +1,2 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
import models # noqa
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import stock_dropshipping # noqa
@@ -1,19 +0,0 @@
# coding: utf-8
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, api
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
@api.model
def _anglo_saxon_sale_move_lines(self, i_line):
for sale_line in i_line.sale_line_ids:
for proc in sale_line.procurement_ids:
if proc.purchase_line_id:
# if the invoice line is related to sale order lines having one of its
# procurement_ids with a purchase_line_id set, it means that it is a
# confirmed dropship and in that case we mustn't create the cost of
# sale line (because the product won't enter the stock)
return []
return super(AccountInvoice, self)._anglo_saxon_sale_move_lines(i_line)
+1 -1
View File
@@ -192,7 +192,7 @@
if (odoo.debug && !_.isEmpty(debug_jobs)) log.push('\nDebug: ', debug_jobs); if (odoo.debug && !_.isEmpty(debug_jobs)) log.push('\nDebug: ', debug_jobs);
if (odoo.debug || !_.isEmpty(failed) || !_.isEmpty(unloaded)) { if (odoo.debug || !_.isEmpty(failed) || !_.isEmpty(unloaded)) {
console[_.isEmpty(unloaded) ? 'info' : 'error'].apply(console, log); console[_.isEmpty(failed) || _.isEmpty(unloaded) ? 'info' : 'error'].apply(console, log);
} }
} }
}, },
+18 -1
View File
@@ -76,6 +76,10 @@ var ControlPanel = Widget.extend({
return this._super(); return this._super();
}, },
destroy: function() {
this._clear_breadcrumbs_handlers();
return this._super.apply(this, arguments);
},
/** /**
* @return {Object} the Bus the ControlPanel is listening on * @return {Object} the Bus the ControlPanel is listening on
*/ */
@@ -108,7 +112,9 @@ var ControlPanel = Widget.extend({
// Render the breadcrumbs // Render the breadcrumbs
if (status.breadcrumbs) { if (status.breadcrumbs) {
new_cp_content.$breadcrumbs = this._render_breadcrumbs(status.breadcrumbs); this._clear_breadcrumbs_handlers();
this.$breadcrumbs = this._render_breadcrumbs(status.breadcrumbs);
new_cp_content.$breadcrumbs = this.$breadcrumbs;
} }
// Detach control_panel old content and attach new elements // Detach control_panel old content and attach new elements
@@ -207,6 +213,17 @@ var ControlPanel = Widget.extend({
} }
return $bc; return $bc;
}, },
/**
* Private function that removes event handlers attached on the currently
* displayed breadcrumbs.
*/
_clear_breadcrumbs_handlers: function () {
if (this.$breadcrumbs) {
_.each(this.$breadcrumbs, function ($bc) {
$bc.off();
});
}
},
/** /**
* Private function that updates the SearchView's visibility and extend the * Private function that updates the SearchView's visibility and extend the
* breadcrumbs area if the SearchView is not visible * breadcrumbs area if the SearchView is not visible
@@ -196,6 +196,12 @@ var View = Widget.extend({
get_context: function () { get_context: function () {
return {}; return {};
}, },
destroy: function () {
if (this.$buttons) {
this.$buttons.off();
}
return this._super.apply(this, arguments);
},
set_scrollTop: function(scrollTop) { set_scrollTop: function(scrollTop) {
this.scrollTop = scrollTop; this.scrollTop = scrollTop;
}, },
+8
View File
@@ -463,6 +463,14 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
} }
this.active_view.controller.do_load_state(state, warm); this.active_view.controller.do_load_state(state, warm);
}, },
destroy: function () {
if (this.control_elements) {
if (this.control_elements.$switch_buttons) {
this.control_elements.$switch_buttons.off();
}
}
return this._super.apply(this, arguments);
},
}); });
return ViewManager; return ViewManager;
@@ -99,6 +99,9 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
delete this.$dropdown; delete this.$dropdown;
} }
if (this.$input) { if (this.$input) {
if (this.$input.data('ui-autocomplete')) {
this.$input.autocomplete("destroy");
}
this.$input.closest(".modal .modal-content").off('scroll'); this.$input.closest(".modal .modal-content").off('scroll');
this.$input.off('keyup blur autocompleteclose autocompleteopen ' + this.$input.off('keyup blur autocompleteclose autocompleteopen ' +
'focus focusout change keydown'); 'focus focusout change keydown');
@@ -94,6 +94,9 @@ var FormView = View.extend(common.FieldManagerMixin, {
}); });
}, },
start: function() { start: function() {
if (this.$pager) {
this.$pager.off();
}
var self = this; var self = this;
this.rendering_engine.set_fields_registry(this.fields_registry); this.rendering_engine.set_fields_registry(this.fields_registry);
@@ -129,6 +129,12 @@ var GraphView = View.extend({
this.widget.set_measure(this.active_measure); this.widget.set_measure(this.active_measure);
} }
}, },
destroy: function () {
if (this.$buttons) {
this.$buttons.find('button').off(); // remove jquery's tooltip() handlers
}
return this._super.apply(this, arguments);
},
}); });
core.view_registry.add('graph', GraphView); core.view_registry.add('graph', GraphView);
+1 -1
View File
@@ -267,7 +267,7 @@ var ListView = View.extend({
render_buttons: function($node) { render_buttons: function($node) {
if (!this.$buttons) { if (!this.$buttons) {
this.$buttons = $(QWeb.render("ListView.buttons", {'widget': this})); this.$buttons = $(QWeb.render("ListView.buttons", {'widget': this}));
this.$buttons.find('.o_list_button_add').click(this.proxy('do_add_record')); this.$buttons.on('click', '.o_list_button_add', this.proxy('do_add_record'));
this.$buttons.appendTo($node); this.$buttons.appendTo($node);
} }
}, },
@@ -827,6 +827,12 @@ var PivotView = View.extend({
error: crash_manager.rpc_error.bind(crash_manager) error: crash_manager.rpc_error.bind(crash_manager)
}); });
}, },
destroy: function () {
if (this.$buttons) {
this.$buttons.find('button').off(); // remove jquery's tooltip() handlers
}
return this._super.apply(this, arguments);
},
}); });
function traverse_tree(root, f, arg1, arg2, arg3) { function traverse_tree(root, f, arg1, arg2, arg3) {
@@ -52,6 +52,10 @@ var Sidebar = Widget.extend({
event.preventDefault(); event.preventDefault();
}); });
}, },
destroy: function() {
$(window).off(this.fileupload_id);
return this._super.apply(this, arguments);
},
redraw: function() { redraw: function() {
this.$el.html(QWeb.render('Sidebar', {widget: this})); this.$el.html(QWeb.render('Sidebar', {widget: this}));
+12 -7
View File
@@ -113,11 +113,16 @@ var FieldTextHtmlSimple = widget.extend({
}, },
text_to_html: function (text) { text_to_html: function (text) {
var value = text || ""; var value = text || "";
if (value.match(/^\s*$/)) { try {
value = '<p><br/></p>'; $(text)[0].innerHTML;
} else { return text;
value = "<p>"+value.split(/<br\/?>/).join("<br/></p><p>")+"</p>"; } catch (e) {
value = value.replace(/<p><\/p>/g, '').replace('<p><p>', '<p>').replace('<p><p ', '<p ').replace('</p></p>', '</p>'); if (value.match(/^\s*$/)) {
value = '<p><br/></p>';
} else {
value = "<p>"+value.split(/<br\/?>/).join("<br/></p><p>")+"</p>";
value = value.replace(/<p><\/p>/g, '').replace('<p><p>', '<p>').replace('<p><p ', '<p ').replace('</p></p>', '</p>');
}
} }
return value; return value;
}, },
@@ -213,7 +218,7 @@ var FieldTextHtml = widget.extend({
self.$iframe.css("height", (self.$body.find("#oe_snippets").length ? 500 : 300) + "px"); self.$iframe.css("height", (self.$body.find("#oe_snippets").length ? 500 : 300) + "px");
} }
}; };
$(window).on('resize', self.resize); $(window).on('resize', this.resize);
var def = this._super.apply(this, arguments); var def = this._super.apply(this, arguments);
this.$translate.remove(); this.$translate.remove();
@@ -374,7 +379,7 @@ var FieldTextHtml = widget.extend({
} }
}, },
destroy: function () { destroy: function () {
$(window).off('resize', self.resize); $(window).off('resize', this.resize);
delete window.odoo[this.callback+"_editor"]; delete window.odoo[this.callback+"_editor"];
delete window.odoo[this.callback+"_content"]; delete window.odoo[this.callback+"_content"];
delete window.odoo[this.callback+"_updown"]; delete window.odoo[this.callback+"_updown"];
@@ -64,6 +64,7 @@ var KanbanRecord = Widget.extend({
qweb_context[p] = _.bind(this[p], this); qweb_context[p] = _.bind(this[p], this);
} }
} }
this.qweb_context = qweb_context;
this.content = this.qweb.render('kanban-box', qweb_context); this.content = this.qweb.render('kanban-box', qweb_context);
}, },
@@ -81,7 +82,18 @@ var KanbanRecord = Widget.extend({
}, },
start: function() { start: function() {
var self = this;
this.add_widgets(); this.add_widgets();
this.$('[tooltip]').each(function () {
var $el = $(this);
var tooltip = $el.attr('tooltip');
if (tooltip) {
$el.tooltip({
'html': true,
'title': self.qweb.render(tooltip, self.qweb_context)
});
}
});
}, },
renderElement: function () { renderElement: function () {
@@ -172,14 +184,15 @@ var KanbanRecord = Widget.extend({
if (elem == ev.currentTarget) { if (elem == ev.currentTarget) {
ischild = false; ischild = false;
} }
var test_event = events && events.click && (events.click.length > 1 || events.click[0].namespace !== "tooltip");
if (ischild) { if (ischild) {
children.push(elem); children.push(elem);
if (events && events.click) { if (test_event) {
// do not trigger global click if one child has a click event registered // do not trigger global click if one child has a click event registered
trigger = false; trigger = false;
} }
} }
if (trigger && events && events.click) { if (trigger && test_event) {
_.each(events.click, function(click_event) { _.each(events.click, function(click_event) {
if (click_event.selector) { if (click_event.selector) {
// For each parent of original target, check if a // For each parent of original target, check if a
+66 -75
View File
@@ -15,6 +15,7 @@ var Tips = Class.extend({
var self = this; var self = this;
this.tips = []; this.tips = [];
this.tipObjects = {};
this.view = null; this.view = null;
var Tips = new Model('web.tip'); var Tips = new Model('web.tip');
@@ -27,69 +28,47 @@ var Tips = Class.extend({
bus.on('view_shown', this, function(view) { bus.on('view_shown', this, function(view) {
if (_.keys(view.fields_view).length === 0) { if (_.keys(view.fields_view).length === 0) {
view.on('view_loaded', this, function(fields_view) { view.on('view_loaded', this, function(fields_view) {
self.view = view; if (this.view && this._eval_tips) {
self.on_view(); this.view.off('chatter_messages_displayed', this, this._eval_tips);
this.view.off('to_edit_mode', this, this._remove_tips);
this.view.off('to_view_mode', this, this._eval_tips);
this.view.off('view_list_rendered', this, this._eval_tips);
}
this.view = view;
}); });
} else { } else {
self.view = view; this.view = view;
self.on_view();
} }
}); });
},
on_view: function() { bus.on('DOM_updated', this, function() {
var self = this; if (!this.view) return;
if (this._DOM_updated) { var self = this;
bus.off('DOM_updated', this, this._DOM_updated); var action_id = this.view.ViewManager.action ? this.view.ViewManager.action.id : null;
} var model = this.view.fields_view.model;
this._DOM_updated = function() { var mode = this.view.fields_view.type;
var action_id = self.view.ViewManager.action ? self.view.ViewManager.action.id : null; var type = this.view.datarecord ? this.view.datarecord.type : null;
var model = self.view.fields_view.model;
var mode = self.view.fields_view.type; this._eval_tips = function() { self.eval_tips(action_id, model, mode, type); };
this._remove_tips = function() { self.remove_tips(); };
if (mode == 'form') { if (mode == 'form') {
self.on_form_view(action_id, model); if ($('.oe_chatter').length > 0) {
} this.view.on('chatter_messages_displayed', this, this._eval_tips);
else if (self.view.hasOwnProperty('editor')) { } else {
if (self._view_list_rendered) { this._eval_tips();
self.view.off('view_list_rendered', self, self._view_list_rendered);
} }
self._view_list_rendered = function() { self.eval_tips(action_id, model, mode); }; this.view.on('to_edit_mode', this, this._remove_tips);
self.view.on('view_list_rendered', self, self._view_list_rendered); this.view.on('to_view_mode', this, this._eval_tips);
}
else if (this.view.hasOwnProperty('editor')) {
this.view.on('view_list_rendered', this, this._eval_tips);
} }
else { else {
self.eval_tips(action_id, model, mode); this._eval_tips();
} }
}; });
bus.on('DOM_updated', this, this._DOM_updated);
},
on_form_view: function(action_id, model) {
var self = this;
var type = this.view.datarecord ? this.view.datarecord.type : null
if ($('.oe_chatter').length > 0) {
if (this._chatter_messages_displayed) {
this.view.off('chatter_messages_displayed', this, this._chatter_messages_displayed);
}
this._chatter_messages_displayed = function() { self.eval_tips(action_id, model, 'form', type); };
this.view.on('chatter_messages_displayed', this, this._chatter_messages_displayed);
} else {
this.eval_tips(action_id, model, 'form', type);
}
if (this._to_edit_mode) {
this.view.off('to_edit_mode', this, this._to_edit_mode);
}
this._to_edit_mode = function() { self.remove_tips(); };
this.view.on('to_edit_mode', this, this._to_edit_mode);
if (this._to_view_mode) {
this.view.off('to_view_mode', this, this._to_view_mode);
}
this._to_view_mode = function() { self.eval_tips(action_id, model, 'form', type); };
this.view.on('to_view_mode', this, this._to_view_mode);
}, },
eval_tips: function(action_id, model, mode, type) { eval_tips: function(action_id, model, mode, type) {
@@ -109,7 +88,7 @@ var Tips = Class.extend({
filter.is_consumed = false; filter.is_consumed = false;
tips = _.where(self.tips, filter); tips = _.where(self.tips, filter);
// To take into account a tip without fixed model : e.g. a generic tip on the breadcrumb // To take into account a tip without fixed model : e.g. a generic tip on the breadcrumb
tips = tips.concat(_.where(self.tips, {mode: mode, is_consumed: false, model:false})) tips = tips.concat(_.where(self.tips, {mode: mode, is_consumed: false, model:false}));
if (type) { if (type) {
tips = _.filter(tips, function(tip) { tips = _.filter(tips, function(tip) {
@@ -123,14 +102,21 @@ var Tips = Class.extend({
valid_tips = _.uniq(valid_tips.concat(tips)); valid_tips = _.uniq(valid_tips.concat(tips));
_.each(valid_tips, function(tip) { _.each(valid_tips, function(tip) {
if (!tip.is_consumed) { if (!tip.is_consumed) {
var t = new Tip(tip); if (self.tipObjects[tip.id]) {
t.do_tip(); self.tipObjects[tip.id].destroy();
} else {
self.tipObjects[tip.id] = new Tip(tip);
}
self.tipObjects[tip.id].do_tip();
} }
}); });
}, },
remove_tips: function() { remove_tips: function() {
$('.oe_breathing').remove(); for (var k in this.tipObjects) {
this.tipObjects[k].destroy();
}
this.tipObjects = {};
}, },
}); });
@@ -275,27 +261,9 @@ var Tip = Class.extend({
}, },
end_tip: function() { end_tip: function() {
var Tips = new Model('web.tip'); new Model('web.tip').call('consume', [this.tip.id], {});
this.$element.popover('destroy');
this.$element.removeClass('oe_tip_show_element');
this.$breathing.remove();
this.$helper.remove();
this.$overlay.remove();
this.$cross.remove();
$('.oe_tip_fix_parent').each(function() {
$(this).removeClass('oe_tip_fix_parent');
});
$(document).off('keyup.web_tip');
bus.off('resize', this, this._resize_position);
bus.off('resize', this, this._resize);
bus.off('image_loaded', this, this._resize);
bus.off('please_reposition_tip', this, this._resize);
Tips.call('consume', [this.tip.id], {});
this.tip.is_consumed = true; this.tip.is_consumed = true;
this.destroy();
}, },
_set_breathing_position: function() { _set_breathing_position: function() {
@@ -327,6 +295,29 @@ var Tip = Class.extend({
} }
} }
}, },
destroy: function () {
if (this.$element) {
this.$element.popover('destroy');
this.$element.removeClass('oe_tip_show_element');
this.$breathing.off().remove();
}
if (this.$helper) {
this.$helper.off().remove();
this.$overlay.off().remove();
this.$cross.off().remove();
}
$('.oe_tip_fix_parent').each(function() {
$(this).removeClass('oe_tip_fix_parent');
});
$(document).off('keyup.web_tip');
bus.off('resize', this, this._resize_position);
bus.off('resize', this, this._resize);
bus.off('image_loaded', this, this._resize);
bus.off('please_reposition_tip', this, this._resize);
},
}); });
WebClient.include({ WebClient.include({
@@ -367,7 +367,16 @@ options.registry.gallery_img = options.Class.extend({
var gallery = $parent.data('snippet-editor').styles.gallery; var gallery = $parent.data('snippet-editor').styles.gallery;
gallery.reapply(); gallery.reapply();
}, 0); }, 0);
} },
on_focus: function () {
this._super.apply(this, arguments);
if (this._current_src && this._current_src !== this.$target.attr("src")) {
_.defer((function () {
snippet_editor.globalSelector.closest(this.$target.parent()).data('snippet-editor').styles.gallery.reapply();
}).bind(this));
}
this._current_src = this.$target.attr("src");
},
}); });
+1 -1
View File
@@ -806,7 +806,7 @@ class HttpRequest(WebRequest):
if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'): if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'):
headers = { headers = {
'Access-Control-Max-Age': 60 * 60 * 24, 'Access-Control-Max-Age': 60 * 60 * 24,
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept' 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, X-Debug-Mode'
} }
return Response(status=200, headers=headers) return Response(status=200, headers=headers)