[MERGE] forward port branch saas-12 up to 6386037

This commit is contained in:
Christophe Simonis
2016-11-08 15:01:20 +01:00
42 changed files with 230 additions and 91 deletions
@@ -342,7 +342,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='Label', required=True) name = fields.Char(string='Label', required=True)
@@ -951,8 +951,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 and payment.id or False aml_dict['payment_id'] = payment and payment.id or False
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, apply_taxes=True).create(aml_dict) aml_obj.with_context(check_move_validity=False, apply_taxes=True).create(aml_dict)
# Create counterpart move lines and reconcile them # Create counterpart move lines and reconcile them
+3 -1
View File
@@ -906,6 +906,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()
@@ -1208,7 +1210,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
@@ -243,7 +243,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,13 +1,14 @@
# -*- 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 calendar
from datetime import date, datetime from datetime import date, datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _ from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
from odoo.tools import float_compare from odoo.tools import float_compare, float_is_zero
class AccountAssetCategory(models.Model): class AccountAssetCategory(models.Model):
@@ -156,14 +157,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):
@@ -216,6 +229,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,
@@ -413,7 +428,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)],
} }
@@ -55,7 +55,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")],
+3
View File
@@ -348,6 +348,9 @@ core.form_tag_registry
FavoriteMenu.include({ FavoriteMenu.include({
start: function () { start: function () {
var self = this; var self = this;
if(this.action_id === undefined) {
return this._super();
}
var am = this.findAncestor(function (a) { var am = this.findAncestor(function (a) {
return a instanceof ActionManager; return a instanceof ActionManager;
}); });
+14 -4
View File
@@ -19,6 +19,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;
@@ -33,15 +34,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
@@ -78,7 +78,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)
@@ -90,6 +90,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:
+7
View File
@@ -418,6 +418,13 @@ class HrExpenseSheet(models.Model):
self._add_followers() self._add_followers()
return res return res
@api.multi
def unlink(self):
for expense in self:
if expense.state == "post":
raise UserError(_("You cannot delete a posted expense."))
super(HrExpenseSheet, self).unlink()
@api.multi @api.multi
def set_to_paid(self): def set_to_paid(self):
self.write({'state': 'done'}) self.write({'state': 'done'})
@@ -662,6 +662,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);
}, },
+4 -3
View File
@@ -215,7 +215,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)
@@ -609,7 +609,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
@@ -619,7 +619,8 @@ class PosOrder(models.Model):
picking.action_confirm() picking.action_confirm()
picking.force_assign() picking.force_assign()
self.set_pack_operation_lot(picking) self.set_pack_operation_lot(picking)
picking.action_done() if not any([(x.product_id.tracking != 'none') for x in picking.pack_operation_ids]):
picking.action_done()
def set_pack_operation_lot(self, picking=None): def set_pack_operation_lot(self, picking=None):
"""Set Serial/Lot number in pack operations to mark the pack operation done.""" """Set Serial/Lot number in pack operations to mark the pack operation done."""
+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
@@ -1847,11 +1847,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
@@ -1861,7 +1859,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();
@@ -1871,12 +1869,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.
@@ -1890,7 +1888,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;
} }
} }
@@ -1911,9 +1909,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();
@@ -1963,6 +1968,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});
+1 -1
View File
@@ -47,7 +47,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>
@@ -154,7 +154,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
@@ -823,13 +823,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
@@ -47,7 +47,7 @@ class ProcurementOrder(models.Model):
def _get_project(self): def _get_project(self):
Project = self.env['project.project'] Project = self.env['project.project']
project = self.product_id.project_id project = self.product_id.with_context(force_company=self.company_id.id).project_id
if not project and self.sale_line_id: if not project and self.sale_line_id:
# find the project corresponding to the analytic account of the sales order # find the project corresponding to the analytic account of the sales order
account = self.sale_line_id.order_id.project_id account = self.sale_line_id.order_id.project_id
+6 -10
View File
@@ -615,21 +615,17 @@ class QuantPackage(models.Model):
@api.one @api.one
@api.depends('parent_id', 'children_ids', 'quant_ids.package_id') @api.depends('parent_id', 'children_ids', 'quant_ids.package_id')
def _compute_children_quant_ids(self): def _compute_children_quant_ids(self):
res = dict.fromkeys(self.ids, self.env['stock.quant'])
children_quants = self.env['stock.quant'].search(['package_id', 'child_of', self.ids])
for quant in children_quants:
res[quant.package_id.id] |= quant
for package in self: for package in self:
package.children_quant_ids = res[package.id].ids package.children_quant_ids = self.env['stock.quant'].search([('package_id', 'child_of', package.id)]).ids
@api.depends('quant_ids.package_id', 'quant_ids.location_id', 'quant_ids.company_id', 'quant_ids.owner_id', 'ancestor_ids') @api.depends('quant_ids.package_id', 'quant_ids.location_id', 'quant_ids.company_id', 'quant_ids.owner_id', 'ancestor_ids')
def _compute_package_info(self): def _compute_package_info(self):
res = {}
quants = self.env['stock.quant'].search([('package_id', 'in', self.ids)]) # TDE FIXME: was child_od
for quant in quants:
res[quant.package_id.id] = {'location_id': quant.location_id.id, 'owner_id': quant.owner_id.id, 'company_id': quant.company_id.id}
for package in self: for package in self:
values = res.get(package.id, {'location_id': False, 'company_id': self.env.user.company_id.id, 'owner_id': False}) quants = package.children_quant_ids
if quants:
values = quants[0]
else:
values = {'location_id': False, 'company_id': self.env.user.company_id.id, 'owner_id': False}
package.location_id = values['location_id'] package.location_id = values['location_id']
package.company_id = values['company_id'] package.company_id = values['company_id']
package.owner_id = values['owner_id'] package.owner_id = values['owner_id']
@@ -25,8 +25,8 @@
<field name="location_dest_id" domain="[('id', 'child_of', picking_destination_location_id)]"/> <field name="location_dest_id" domain="[('id', 'child_of', picking_destination_location_id)]"/>
</group> </group>
<group string="Packages" groups="stock.group_tracking_lot"> <group string="Packages" groups="stock.group_tracking_lot">
<field name="package_id" attrs="{'required': [('product_id', '=', False)]}" domain="[('location_id', '=', location_id)]" context="{'location_id': location_id}"/> <field name="package_id" attrs="{'required': [('product_id', '=', False)]}" domain="[('location_id', '=', location_id)]" context="{'default_location_id': location_id}"/>
<field name="result_package_id" domain="[('location_id', '=', location_dest_id)]" context="{'location_id': location_dest_id}"/> <field name="result_package_id" domain="[('location_id', '=', location_dest_id)]" context="{'default_location_id': location_dest_id}"/>
</group> </group>
</group> </group>
</form> </form>
@@ -85,7 +85,7 @@
<field name="lot_name" invisible="not context.get('only_create', False)"/> <field name="lot_name" invisible="not context.get('only_create', False)"/>
<field name="lot_id" invisible="context.get('only_create', False)" <field name="lot_id" invisible="context.get('only_create', False)"
domain="[('product_id','=', parent.product_id)]" domain="[('product_id','=', parent.product_id)]"
context="{'product_id': parent.product_id}"/> context="{'default_product_id': parent.product_id}"/>
<field name="qty_todo" invisible="not context.get('show_reserved') or context.get('serial') or context.get('state_done')" readonly="1"/> <field name="qty_todo" invisible="not context.get('show_reserved') or context.get('serial') or context.get('state_done')" readonly="1"/>
<field name="qty" invisible="context.get('serial')"/> <field name="qty" invisible="context.get('serial')"/>
<button name="do_minus" type="object" icon="fa-minus-square" attrs="{'invisible': [('qty', '&lt;=', 0.99)]}" invisible="not context.get('show_reserved') or context.get('state_done')"/> <button name="do_minus" type="object" icon="fa-minus-square" attrs="{'invisible': [('qty', '&lt;=', 0.99)]}" invisible="not context.get('show_reserved') or context.get('state_done')"/>
+3 -3
View File
@@ -260,7 +260,7 @@
<field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/> <field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/>
<field name="from_loc" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/> <field name="from_loc" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
<field name="to_loc" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/> <field name="to_loc" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
<field name="result_package_id" groups="stock.group_tracking_lot" context="{'location_id': location_dest_id}" invisible="1"/> <field name="result_package_id" groups="stock.group_tracking_lot" context="{'default_location_id': location_dest_id}" invisible="1"/>
<field name="state" invisible="1"/> <field name="state" invisible="1"/>
<field name="product_qty" readonly="1" attrs="{'required': [('product_id', '!=', False)]}"/> <field name="product_qty" readonly="1" attrs="{'required': [('product_id', '!=', False)]}"/>
<field name="qty_done" attrs="{'readonly': [('lots_visible', '=', True)]}"/> <field name="qty_done" attrs="{'readonly': [('lots_visible', '=', True)]}"/>
@@ -280,7 +280,7 @@
<field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/> <field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/>
<field name="from_loc" groups="stock.group_locations,stock.group_tracking_lot"/> <field name="from_loc" groups="stock.group_locations,stock.group_tracking_lot"/>
<field name="to_loc" groups="stock.group_locations,stock.group_tracking_lot"/> <field name="to_loc" groups="stock.group_locations,stock.group_tracking_lot"/>
<field name="result_package_id" groups="stock.group_tracking_lot" context="{'location_id': location_dest_id}" invisible="1"/> <field name="result_package_id" groups="stock.group_tracking_lot" context="{'default_location_id': location_dest_id}" invisible="1"/>
<field name="state" invisible="1"/> <field name="state" invisible="1"/>
<field name="product_qty" readonly="1" attrs="{'required': [('product_id', '!=', False)]}"/> <field name="product_qty" readonly="1" attrs="{'required': [('product_id', '!=', False)]}"/>
<field name="qty_done" attrs="{'readonly': [('lots_visible', '=', True)]}"/> <field name="qty_done" attrs="{'readonly': [('lots_visible', '=', True)]}"/>
@@ -317,7 +317,7 @@
<field name="from_loc" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/> <field name="from_loc" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
<field name="to_loc" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/> <field name="to_loc" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
<field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/> <field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/>
<field name="result_package_id" groups="stock.group_tracking_lot" context="{'location_id': location_dest_id}" invisible="1"/> <field name="result_package_id" groups="stock.group_tracking_lot" context="{'default_location_id': location_dest_id}" invisible="1"/>
<field name="state" invisible="1"/> <field name="state" invisible="1"/>
<field name="qty_done" invisible="1"/> <field name="qty_done" invisible="1"/>
<field name="lots_visible" invisible="1"/> <field name="lots_visible" invisible="1"/>
+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 odoo 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
@@ -189,7 +189,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
@@ -464,6 +464,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;
@@ -100,6 +100,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');
@@ -88,6 +88,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;
}, },
@@ -216,7 +221,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();
@@ -377,7 +382,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"];
@@ -1032,12 +1032,12 @@ data.Editor = Class.extend({
if($parent.closest(":data(\"snippet-editor\")").length) { if($parent.closest(":data(\"snippet-editor\")").length) {
while (!$parent.data("snippet-editor")) { while (!$parent.data("snippet-editor")) {
var $nextParent = $parent.parent(); var $nextParent = $parent.parent();
if ($parent.children().length === 0 && $parent.text().trim() === "") { if ($parent.children().length === 0 && $parent.text().trim() === "" && !$parent.hasClass("oe_structure")) {
$parent.remove(); $parent.remove();
} }
$parent = $nextParent; $parent = $nextParent;
} }
if ($parent.children().length === 0 && $parent.text().trim() === "") { if ($parent.children().length === 0 && $parent.text().trim() === "" && !$parent.hasClass("oe_structure")) {
_.defer(function () { _.defer(function () {
$parent.data("snippet-editor").on_remove(); $parent.data("snippet-editor").on_remove();
}); });
@@ -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
@@ -360,7 +360,16 @@ options.registry.gallery_img = options.Class.extend({
this.buildingBlock.make_active($parent); this.buildingBlock.make_active($parent);
$parent.data('snippet-editor').styles.gallery.reapply(); $parent.data('snippet-editor').styles.gallery.reapply();
}).bind(this)); }).bind(this));
} },
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");
},
}); });
+7
View File
@@ -1243,6 +1243,13 @@ class Monetary(Field):
return float_precision(value, currency.decimal_places) return float_precision(value, currency.decimal_places)
return float(value or 0.0) return float(value or 0.0)
def convert_to_read(self, value, record, use_name_get=True):
# float_precision values are not supported in pure XMLRPC
return float(value)
def convert_to_write(self, value, record):
return value
class _String(Field): class _String(Field):
""" Abstract class for string fields. """ """ Abstract class for string fields. """
+1 -1
View File
@@ -784,7 +784,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)