[MERGE] forward port branch 10.0 up to 3d085f6

This commit is contained in:
Christophe Simonis
2016-11-08 15:13:05 +01:00
66 changed files with 368 additions and 142 deletions
+5 -1
View File
@@ -908,8 +908,12 @@ file_filter = addons/website_slides/i18n/<lang>.po
source_file = addons/website_slides/i18n/website_slides.pot
source_lang = en
[odoo-master.website_theme_install]
file_filter = addons/website_theme_install/i18n/<lang>.po
source_file = addons/website_theme_install/i18n/website_theme_install.pot
source_lang = en
[odoo-master.website_twitter]
file_filter = addons/website_twitter/i18n/<lang>.po
source_file = addons/website_twitter/i18n/website_twitter.pot
source_lang = en
@@ -342,7 +342,7 @@ class AccountBankStatement(models.Model):
class AccountBankStatementLine(models.Model):
_name = "account.bank.statement.line"
_description = "Bank Statement Line"
_order = "statement_id desc, sequence"
_order = "statement_id desc, sequence, id desc"
_inherit = ['ir.needaction_mixin']
name = fields.Char(string='Label', required=True)
@@ -951,8 +951,23 @@ class AccountBankStatementLine(models.Model):
aml_dict['currency_id'] = statement_currency.id
# 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:
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)
# Create counterpart move lines and reconcile them
+3 -1
View File
@@ -914,6 +914,8 @@ class AccountMoveLine(models.Model):
first_line_dict['account_id'] = self[0].account_id.id
if 'analytic_account_id' in first_line_dict:
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
second_line_dict = vals.copy()
@@ -1216,7 +1218,7 @@ class AccountMoveLine(models.Model):
""" 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.
"""
amount = (self.credit or 0.0) - (self.debit or 0.0)
amount = (self.debit or 0.0) - (self.credit or 0.0)
return {
'name': self.name,
'date': self.date,
+1 -1
View File
@@ -243,7 +243,7 @@ class TestReconciliation(AccountingTestCase):
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_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
+21 -6
View File
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import calendar
from datetime import date, datetime
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
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):
@@ -156,14 +157,26 @@ class AccountAssetAsset(models.Model):
if self.prorata:
amount = amount_to_depr / self.method_number
if sequence == 1:
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
if self.method_period % 12 != 0:
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':
amount = residual_amount * self.method_progress_factor
if self.prorata:
if sequence == 1:
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
if self.method_period % 12 != 0:
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
def _compute_board_undone_dotation_nb(self, depreciation_date, total_days):
@@ -216,6 +229,8 @@ class AccountAssetAsset(models.Model):
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.currency_id.round(amount)
if float_is_zero(amount, precision_rounding=self.currency_id.rounding):
continue
residual_amount -= amount
vals = {
'amount': amount,
@@ -413,7 +428,7 @@ class AccountAssetAsset(models.Model):
'res_model': 'account.move',
'view_id': False,
'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()
# 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.
context = {
"active_ids": [self.ref("account_asset.menu_asset_depreciation_confirmation_wizard")],
@@ -345,6 +345,20 @@ class AccountVoucherLine(models.Model):
taxes = self.tax_ids.compute_all(self.price_unit, self.voucher_id.currency_id, self.quantity, product=self.product_id, partner=self.voucher_id.partner_id)
self.price_subtotal = taxes['total_excluded']
@api.onchange('product_id', 'voucher_id', 'price_unit', 'company_id')
def _onchange_line_details(self):
if not self.voucher_id or not self.product_id or not self.voucher_id.partner_id:
return
onchange_res = self.product_id_change(
self.product_id.id,
self.voucher_id.partner_id.id,
self.price_unit,
self.company_id.id,
self.voucher_id.currency_id.id,
self.voucher_id.voucher_type)
for fname, fvalue in onchange_res['values'].iteritems():
setattr(self, fname, fvalue)
def _get_account(self, product, fpos, type):
accounts = product.product_tmpl_id.get_product_accounts(fpos)
if type == 'sale':
@@ -353,6 +367,7 @@ class AccountVoucherLine(models.Model):
@api.multi
def product_id_change(self, product_id, partner_id=False, price_unit=False, company_id=None, currency_id=None, type=None):
# TDE note: mix of old and new onchange badly written in 9, multi but does not use record set
context = self._context
company_id = company_id if company_id is not None else context.get('company_id', False)
company = self.env['res.company'].browse(company_id)
@@ -100,8 +100,7 @@
<field name="model">account.voucher.line</field>
<field name="arch" type="xml">
<tree string="Voucher Lines" editable="bottom">
<field name="product_id"
on_change="product_id_change(product_id, parent.partner_id, price_unit, company_id, parent.currency_id, parent.voucher_type)" />
<field name="product_id"/>
<field name="name"/>
<field name="account_id"/>
<field name="account_analytic_id" domain="[('company_id', '=', company_id)]" groups="analytic.group_analytic_accounting"/>
@@ -248,8 +247,7 @@
<page string="Sales Information">
<field name="line_ids">
<tree string="Voucher Lines" editable="bottom">
<field name="product_id"
on_change="product_id_change(product_id, parent.partner_id, price_unit, company_id, parent.currency_id, parent.voucher_type)" />
<field name="product_id"/>
<field name="name"/>
<field name="account_id"/>
<field name="account_analytic_id" domain="[('company_id', '=', parent.company_id)]" groups="analytic.group_analytic_accounting"/>
@@ -365,8 +363,7 @@
<page string="Bill Information">
<field name="line_ids">
<tree string="Voucher Lines" editable="bottom">
<field name="product_id"
on_change="product_id_change(product_id, parent.partner_id, price_unit, company_id, parent.currency_id, parent.voucher_type)" />
<field name="product_id"/>
<field name="name"/>
<field name="account_id"/>
<field name="account_analytic_id" domain="[('company_id', '=', parent.company_id)]" groups="analytic.group_analytic_accounting"/>
+2 -2
View File
@@ -8,7 +8,7 @@ will automatically create Odoo users for them on the fly.
Configuration:
--------------
After installing this module, you need to configure the LDAP parameters in the
Configuration tab of the Company details. Different companies may have different
General Settings menu. Different companies may have different
LDAP servers, as long as they have unique usernames (usernames need to be unique
in Odoo, even across multiple companies).
@@ -52,7 +52,7 @@ encrypted.
User Template:
--------------
In the LDAP configuration on the Company form, it is possible to select a *User
In the LDAP configuration on the General Settings, it is possible to select a *User
Template*. If set, this user will be used as template to create the local users
whenever someone authenticates for the first time via LDAP authentication. This
allows pre-setting the default groups and menus of the first-time users.
+3
View File
@@ -348,6 +348,9 @@ core.form_tag_registry
FavoriteMenu.include({
start: function () {
var self = this;
if(this.action_id === undefined) {
return this._super();
}
var am = this.findAncestor(function (a) {
return a instanceof ActionManager;
});
+14 -4
View File
@@ -19,6 +19,7 @@ bus.Bus = Widget.extend({
this._super();
this.options = {};
this.activated = false;
this.bus_id = _.uniqueId('bus');
this.channels = [];
this.last = 0;
this.stop = false;
@@ -33,15 +34,24 @@ bus.Bus = Widget.extend({
this.trigger('window_focus', this.is_master);
}
});
$(window).on("focus", _.bind(this.focus_change, this, true));
$(window).on("blur", _.bind(this.focus_change, this, false));
$(window).on("unload", _.bind(this.focus_change, this, false));
$(window).on("focus." + this.bus_id, _.bind(this.focus_change, this, true));
$(window).on("blur." + this.bus_id, _.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) {
$(window).on(evtype, function() {
$(window).on(evtype + "." + self.bus_id, function() {
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(){
if(!this.activated){
this.poll();
+6 -1
View File
@@ -78,7 +78,7 @@ class StockPicking(models.Model):
def _compute_shipping_weight(self):
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)
carrier_id = fields.Many2one("delivery.carrier", string="Carrier")
volume = fields.Float(copy=False)
@@ -90,6 +90,11 @@ class StockPicking(models.Model):
weight_bulk = fields.Float('Bulk Weight', compute='_compute_bulk_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')
def _cal_weight(self):
for picking in self:
+7
View File
@@ -418,6 +418,13 @@ class HrExpenseSheet(models.Model):
self._add_followers()
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
def set_to_paid(self):
self.write({'state': 'done'})
@@ -29,7 +29,7 @@
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<separator/>
<filter string="My Department Leaves" name="department" domain="[('department_id.manager_id.user_id', '=', uid)]" help="My Department Leaves"/>
<filter string="My Team Leaves" domain="[('employee_id.parent_id.user_id', '=', uid)]" groups="hr_holidays.group_hr_holidays_manager" help="Leaves of Your Team Member"/>
<filter name="my_team_leaves" string="My Team Leaves" domain="[('employee_id.parent_id.user_id', '=', uid)]" groups="hr_holidays.group_hr_holidays_manager" help="Leaves of Your Team Member"/>
<separator/>
<filter string="To Report in Payslip" name="gray" domain="[('payslip_status', '=', False)]" groups="hr_holidays.group_hr_holidays_manager"/>
<separator/>
@@ -66,7 +66,7 @@
<sheet>
<div class="oe_title">
<label for="employee_id" class="oe_edit_only"/>
<h1><field name="employee_id" on_change="onchange_employee_id(employee_id)" class="oe_inline" placeholder="Employee's Name"/></h1>
<h1><field name="employee_id" class="oe_inline" placeholder="Employee's Name"/></h1>
<field name="user_id" invisible="1"/>
</div>
<group>
@@ -48,7 +48,7 @@ class AccountInvoice(models.Model):
if mod == int(bbacomm[-2:]):
return True
@api.onchange('partner_id')
@api.onchange('partner_id', 'type')
def _onchange_partner_id(self):
result = super(AccountInvoice, self)._onchange_partner_id()
reference = False
@@ -7,8 +7,7 @@
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<field name="date_due" position="after">
<field name="reference_type" nolabel="1" attrs="{'readonly':[('state','!=','draft')]}"
on_change="generate_bbacomm(type,reference_type, partner_id,reference)"/>
<field name="reference_type" nolabel="1" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="reference" nolabel="1" attrs="{'readonly':[('state','!=','draft')]}"/>
</field>
</field>
@@ -662,6 +662,10 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
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();
},
destroy: function () {
$(window).off(this.fileupload_id);
return this._super.apply(this, arguments);
},
toggle: function(state) {
this.$el.toggle(state);
},
@@ -141,6 +141,9 @@
display: inline-block;
}
}
td {
vertical-align: middle;
}
}
.o_mail_block_header_text_social {
@@ -187,6 +190,18 @@
height: 1px;
background-color: darken(@o_mm_def_color_alpha, 15%);
}
table table {
td, th {
vertical-align: middle;
}
td {
padding: 10px 20px;
* {
margin: 0;
line-height: 1.3;
}
}
}
}
.o_mail_block_discount2 {
@@ -202,6 +217,7 @@
.o_mail_table_styles {
border-top: 2px solid @o_mm_def_color_alpha;
padding-top: 20px;
padding-bottom: 20px;
}
.o_mail_footer_links {
@@ -241,6 +257,9 @@
}
}
}
td {
vertical-align: middle;
}
}
.o_mail_display_coupon {
@@ -245,22 +245,22 @@
</tr>
<tr>
<td class="o_mail_col_mv">
<p style="margin: 0 15px 15px;">Basic features</p>
<p>Basic features</p>
</td>
</tr>
<tr>
<td class="o_mail_col_mv">
<p style="margin: 0 15px 15px;">Basic management</p>
<p>Basic management</p>
</td>
</tr>
<tr>
<td class="o_mail_col_mv">
<p style="margin: 0 15px 15px;">No customization</p>
<p>No customization</p>
</td>
</tr>
<tr>
<td class="o_mail_col_mv">
<p style="margin: 0 15px 15px;">No support</p>
<p>No support</p>
</td>
</tr>
</tbody>
@@ -301,22 +301,22 @@
</tr>
<tr>
<td class="o_mail_col_mv">
<p style="margin: 0 15px 15px;"><strong>Advanced</strong> features</p>
<p><strong>Advanced</strong> features</p>
</td>
</tr>
<tr>
<td class="o_mail_col_mv">
<p style="margin: 0 15px 15px;"><strong>Total</strong> management</p>
<p><strong>Total</strong> management</p>
</td>
</tr>
<tr>
<td class="o_mail_col_mv">
<p style="margin: 0 15px 15px;"><strong>Fully customizable</strong></p>
<p><strong>Fully customizable</strong></p>
</td>
</tr>
<tr>
<td class="o_mail_col_mv">
<p style="margin: 0 15px 15px;"><strong>24/7 Support</strong></p>
<p><strong>24/7 Support</strong></p>
</td>
</tr>
</tbody>
+1 -1
View File
@@ -225,7 +225,7 @@
<field name="currency_id" options="{'no_open': True, 'no_create': True}" class="oe_inline" />
</div>
<field name="fees"/>
<field name="partner_id" on_change="on_change_partner_id(partner_id)"/>
<field name="partner_id"/>
<label for="partner_details" string="Customer Details"/>
<div name="partner_details">
<field name="partner_name" placeholder="Name" attrs="{'readonly': [('partner_id', '!=', False)]}"/>
@@ -47,8 +47,14 @@ odoo.define('payment_stripe.stripe', function(require) {
return false;
}
var so_token = $("input[name='token']").val();
var so_id = $("input[name='return_url']").val().match(/quote\/([0-9]+)/);
if (so_id) {
so_id = parseInt(so_id[1]);
}
e.preventDefault();
ajax.jsonRpc('/shop/payment/transaction/' + acquirer_id, 'call', {}).then(function (data) {
ajax.jsonRpc('/shop/payment/transaction/' + acquirer_id, 'call', {'so_id': so_id, 'so_token': so_token}).then(function (data) {
$form.html(data);
handler.open({
name: $("input[name='merchant']").val(),
+4 -3
View File
@@ -215,7 +215,7 @@ class PosOrder(models.Model):
if move is None:
# Create an entry for the sale
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(
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:
moves.action_confirm()
moves.force_assign()
moves.action_done()
moves.filtered(lambda m: m.product_id.tracking == 'none').action_done()
return True
@@ -619,7 +619,8 @@ class PosOrder(models.Model):
picking.action_confirm()
picking.force_assign()
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):
"""Set Serial/Lot number in pack operations to mark the pack operation done."""
+5 -1
View File
@@ -19,7 +19,7 @@ class PosSession(models.Model):
company_id = session.config_id.journal_id.company_id.id
orders = session.order_ids.filtered(lambda order: order.state == 'paid')
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)
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'):
@@ -223,6 +223,10 @@ class PosSession(models.Model):
@api.multi
def action_pos_session_closing_control(self):
for session in self:
#DO NOT FORWARD-PORT
if session.state == 'closing_control':
session.action_pos_session_close()
continue
for statement in session.statement_ids:
if (statement != session.cash_register_id) and (statement.balance_end != statement.balance_end_real):
statement.write({'balance_end_real': statement.balance_end})
+22 -9
View File
@@ -1847,11 +1847,9 @@ var PaymentScreenWidget = ScreenWidget.extend({
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();
// FIXME: this check is there because the backend is unable to
@@ -1861,7 +1859,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
'title': _t('Empty Order'),
'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();
@@ -1871,12 +1869,12 @@ var PaymentScreenWidget = ScreenWidget.extend({
'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.'),
});
return;
return false;
}
}
if (!order.is_paid() || this.invoicing) {
return;
return false;
}
// 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'),
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');
},
});
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) {
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});
+1 -1
View File
@@ -47,7 +47,7 @@
</group>
<group name="group_standard_price">
<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">
<field name="standard_price" widget='monetary' options="{'currency_field': 'currency_id'}" class="oe_inline"/>
</div>
@@ -90,10 +90,9 @@
.o_project_kanban_box {
position: relative;
text-align: center;
padding: 15px 0 15px 0;
padding: 0 0 0 0;
.o-flex(1, 1, auto);
.o-flex-display();
.o-align-items(center);
.o-flex-flow(column, nowrap);
.o-justify-content(center);
}
@@ -154,7 +154,6 @@
<t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip">
<li><b>Project:</b> <field name="project_id"/></li>
<li><b>Category:</b> <field name="tag_ids"/></li>
</ul>
</t>
<t t-name="kanban-box">
@@ -74,7 +74,7 @@
<field name="name">report.intrastat.tree</field>
<field name="model">report.intrastat</field>
<field name="arch" type="xml">
<tree string="Intrastat Data" create="false">
<tree string="Intrastat Data" create="false" delete="false">
<field name="name" />
<field name="month"/>
<field name="code"/>
+4 -3
View File
@@ -518,7 +518,7 @@ class SaleOrder(models.Model):
for tax in line.tax_id:
group = tax.tax_group_id
res.setdefault(group, 0.0)
res[group] += tax.compute_all(line.price_unit, quantity=line.product_uom_qty)['taxes'][0]['amount']
res[group] += tax.compute_all(line.price_reduce, quantity=line.product_uom_qty)['taxes'][0]['amount']
res = sorted(res.items(), key=lambda l: l[0].sequence)
res = map(lambda l: (l[0].name, formatLang(self.env, l[1], currency_obj=currency)), res)
return res
@@ -836,13 +836,14 @@ class SaleOrderLine(models.Model):
vals = {}
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_qty'] = 1.0
product = self.product_id.with_context(
lang=self.order_id.partner_id.lang,
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,
pricelist=self.order_id.pricelist_id.id,
uom=self.product_uom.id
+1 -1
View File
@@ -47,7 +47,7 @@ class ProcurementOrder(models.Model):
def _get_project(self):
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:
# find the project corresponding to the analytic account of the sales order
account = self.sale_line_id.order_id.project_id
+6 -10
View File
@@ -615,21 +615,17 @@ class QuantPackage(models.Model):
@api.one
@api.depends('parent_id', 'children_ids', 'quant_ids.package_id')
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:
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')
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:
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.company_id = values['company_id']
package.owner_id = values['owner_id']
@@ -25,8 +25,8 @@
<field name="location_dest_id" domain="[('id', 'child_of', picking_destination_location_id)]"/>
</group>
<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="result_package_id" domain="[('location_id', '=', location_dest_id)]" context="{'location_id': location_dest_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="{'default_location_id': location_dest_id}"/>
</group>
</group>
</form>
@@ -85,7 +85,7 @@
<field name="lot_name" invisible="not context.get('only_create', False)"/>
<field name="lot_id" invisible="context.get('only_create', False)"
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" 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')"/>
+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="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="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="product_qty" readonly="1" attrs="{'required': [('product_id', '!=', False)]}"/>
<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="from_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="product_qty" readonly="1" attrs="{'required': [('product_id', '!=', False)]}"/>
<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="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="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="qty_done" invisible="1"/>
<field name="lots_visible" invisible="1"/>
+1 -1
View File
@@ -39,7 +39,7 @@
</field>
<field name="standard_price" position="replace">
<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'}"/>
</field>
<div name="standard_price_uom" position="after">
@@ -23,7 +23,7 @@ class StockChangeStandardPrice(models.TransientModel):
res = super(StockChangeStandardPrice, self).default_get(fields)
product_or_template = self.env[self._context['active_model']].browse(self._context['active_id'])
if 'new_price' in fields and 'default_new_price' not in res:
if 'new_price' in fields and 'new_price' not in res:
res['new_price'] = product_or_template.standard_price
if 'counterpart_account_id' in fields and 'counterpart_account_id' not in res:
res['counterpart_account_id'] = product_or_template.property_account_expense_id.id or product_or_template.categ_id.property_account_expense_categ_id.id
-1
View File
@@ -1,3 +1,2 @@
# -*- coding: utf-8 -*-
# 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)
+2 -1
View File
@@ -701,7 +701,8 @@ var ActionManager = Widget.extend({
this.dialog = new Dialog(this, {
title: executor.action.name,
dialogClass: executor.klass,
buttons: []
buttons: [],
size: executor.action.context.dialog_size,
});
// chain on_close triggers with previous dialog, if any
+1 -1
View File
@@ -189,7 +189,7 @@
if (odoo.debug && !_.isEmpty(debug_jobs)) log.push('\nDebug: ', debug_jobs);
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();
},
destroy: function() {
this._clear_breadcrumbs_handlers();
return this._super.apply(this, arguments);
},
/**
* @return {Object} the Bus the ControlPanel is listening on
*/
@@ -108,7 +112,9 @@ var ControlPanel = Widget.extend({
// Render the 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
@@ -207,6 +213,17 @@ var ControlPanel = Widget.extend({
}
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
* breadcrumbs area if the SearchView is not visible
@@ -141,9 +141,12 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
}
var def = this._groups_def[group];
if (!def) {
var Model = window.openerp.web.Model;
var Users = new Model('res.users');
def = this._groups_def[group] = Users.call('has_group', [group]);
def = this._groups_def[group] = this.rpc('/web/dataset/call_kw/res.users/has_group', {
"model": "res.users",
"method": "has_group",
"args": [group],
"kwargs": {}
});
}
return def;
},
@@ -196,6 +196,12 @@ var View = Widget.extend({
get_context: function () {
return {};
},
destroy: function () {
if (this.$buttons) {
this.$buttons.off();
}
return this._super.apply(this, arguments);
},
set_scrollTop: function(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);
},
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;
@@ -7,6 +7,7 @@ var data = require('web.data');
var Dialog = require('web.Dialog');
var common = require('web.form_common');
var ListView = require('web.ListView');
require('web.ListEditor'); // one must be sure that the include of ListView are done (for eg: add start_edition methods)
var Model = require('web.DataModel');
var session = require('web.session');
var utils = require('web.utils');
@@ -99,6 +100,9 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
delete this.$dropdown;
}
if (this.$input) {
if (this.$input.data('ui-autocomplete')) {
this.$input.autocomplete("destroy");
}
this.$input.closest(".modal .modal-content").off('scroll');
this.$input.off('keyup blur autocompleteclose autocompleteopen ' +
'focus focusout change keydown');
@@ -88,6 +88,9 @@ var FormView = View.extend(common.FieldManagerMixin, {
});
},
start: function() {
if (this.$pager) {
this.$pager.off();
}
var self = this;
this.rendering_engine.set_fields_registry(this.fields_registry);
@@ -129,6 +129,12 @@ var GraphView = View.extend({
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);
+1 -1
View File
@@ -267,7 +267,7 @@ var ListView = View.extend({
render_buttons: function($node) {
if (!this.$buttons) {
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);
}
},
@@ -827,6 +827,12 @@ var PivotView = View.extend({
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) {
@@ -52,6 +52,10 @@ var Sidebar = Widget.extend({
event.preventDefault();
});
},
destroy: function() {
$(window).off(this.fileupload_id);
return this._super.apply(this, arguments);
},
redraw: function() {
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) {
var value = text || "";
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>');
try {
$(text)[0].innerHTML;
return text;
} catch (e) {
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;
},
@@ -216,7 +221,7 @@ var FieldTextHtml = widget.extend({
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);
this.$translate.remove();
@@ -377,7 +382,7 @@ var FieldTextHtml = widget.extend({
}
},
destroy: function () {
$(window).off('resize', self.resize);
$(window).off('resize', this.resize);
delete window.odoo[this.callback+"_editor"];
delete window.odoo[this.callback+"_content"];
delete window.odoo[this.callback+"_updown"];
@@ -1032,12 +1032,12 @@ data.Editor = Class.extend({
if($parent.closest(":data(\"snippet-editor\")").length) {
while (!$parent.data("snippet-editor")) {
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 = $nextParent;
}
if ($parent.children().length === 0 && $parent.text().trim() === "") {
if ($parent.children().length === 0 && $parent.text().trim() === "" && !$parent.hasClass("oe_structure")) {
_.defer(function () {
$parent.data("snippet-editor").on_remove();
});
@@ -64,6 +64,7 @@ var KanbanRecord = Widget.extend({
qweb_context[p] = _.bind(this[p], this);
}
}
this.qweb_context = qweb_context;
this.content = this.qweb.render('kanban-box', qweb_context);
},
@@ -81,7 +82,18 @@ var KanbanRecord = Widget.extend({
},
start: function() {
var self = this;
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 () {
@@ -172,14 +184,15 @@ var KanbanRecord = Widget.extend({
if (elem == ev.currentTarget) {
ischild = false;
}
var test_event = events && events.click && (events.click.length > 1 || events.click[0].namespace !== "tooltip");
if (ischild) {
children.push(elem);
if (events && events.click) {
if (test_event) {
// do not trigger global click if one child has a click event registered
trigger = false;
}
}
if (trigger && events && events.click) {
if (trigger && test_event) {
_.each(events.click, function(click_event) {
if (click_event.selector) {
// 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);
$parent.data('snippet-editor').styles.gallery.reapply();
}).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");
},
});
@@ -4,6 +4,10 @@
<field name="name">No more partner available</field>
<field name="color">3</field>
</record>
<record model="crm.lead.tag" id="tag_portal_lead_is_spam">
<field name="name">Spam</field>
<field name="color">3</field>
</record>
<record id="email_template_lead_forward_mail" model="mail.template">
<field name="name">Lead Mass Mail</field>
<field name="model_id" model="ir.model" search="[('name', '=', 'crm.lead.forward.to.partner')]"></field>
@@ -12,7 +16,6 @@
<field name="email_from">${(user.email or '')|safe}</field>
<field name="subject">Fwd: Lead: ${ctx['partner_id'].name}</field>
<field name="body_html"><![CDATA[
<p>Hello,</p>
@@ -197,12 +197,12 @@ class CrmLead(models.Model):
lead.sudo().convert_opportunity(lead.partner_id.id)
@api.multi
def partner_desinterested(self, comment=False, contacted=False):
def partner_desinterested(self, comment=False, contacted=False, spam=False):
self.check_access_rights('write')
if contacted:
message = _('<p>I am not interested by this lead. I contacted the lead.</p>')
message = '<p>%s</p>' % _('I am not interested by this lead. I contacted the lead.')
else:
message = _('<p>I am not interested by this lead. I have not contacted the lead.</p>')
message = '<p>%s</p>' % _('I am not interested by this lead. I have not contacted the lead.')
partner_ids = self.env['res.partner'].search(
[('id', 'child_of', self.env.user.partner_id.commercial_partner_id.id)])
self.sudo().message_unsubscribe(partner_ids=partner_ids.ids)
@@ -210,8 +210,13 @@ class CrmLead(models.Model):
message += '<p>%s</p>' % comment
self.message_post(body=message, subtype="mail.mt_note")
values = {
'partner_assigned_id': False
'partner_assigned_id': False,
}
if spam:
tag_spam = self.env.ref('website_crm_partner_assign.tag_portal_lead_is_spam', False)
if tag_spam and tag_spam not in self.tag_ids:
values['tag_ids'] = [(4, tag_spam.id, False)]
if partner_ids:
values['partner_declined_ids'] = map(lambda p: (4, p, 0), partner_ids.ids)
self.sudo().write(values)
@@ -37,7 +37,9 @@ $('.desinterested_partner_assign_confirm').on('click',function(e){
.call("partner_desinterested",
[[parseInt($('.desinterested_partner_assign_form .assign_lead_id').val())],
$('.desinterested_partner_assign_form .comment_desinterested').val(),
$('.desinterested_partner_assign_form .contacted_desinterested').prop('checked')])
$('.desinterested_partner_assign_form .contacted_desinterested').prop('checked'),
$('.desinterested_partner_assign_form .customer_mark_spam').prop('checked'),
])
.then(function(){
window.location.href = '/my/leads';
});
@@ -266,7 +266,7 @@
</thead>
<tbody>
<tr t-foreach="leads" t-as="lead">
<td><span t-field="lead.create_date" t-field-options='{"widget": "date"}' /></td>
<td><span t-field="lead.create_date" t-options='{"widget": "date"}' /></td>
<td>
<a t-attf-href="/my/lead/#{lead.id}"><span t-field="lead.name"/></a>
</td>
@@ -317,6 +317,7 @@
<table t-if="opportunities" class="table table-hover">
<thead>
<tr class="active">
<th class="col-xs-2">Date</th>
<th class="col-md-5">Name</th>
<th class="col-md-3">Expected Revenue</th>
<th>Contact Name</th>
@@ -325,6 +326,7 @@
</thead>
<tbody>
<tr t-foreach="opportunities" t-as="opp">
<td><span t-field="opp.create_date" t-options='{"widget": "date"}' /></td>
<td>
<a t-attf-href="/my/opportunity/#{opp.id}"><span t-field="opp.name"/></a>
</td>
@@ -493,8 +495,12 @@
<input type="text" name="comment" id="comment" class="form-control comment_desinterested"/>
</div>
<div class="form-group">
<label class="control-label" for="customer_contacted">Do you have contacted the customer?</label>
<input type="checkbox" name="customer_contacted" id="customer_contacted" class="contacted_desinterested"/>
<label class="control-label" for="contacted_desinterested">Do you have contacted the customer?</label>
<input type="checkbox" name="contacted_desinterested" id="contacted_desinterested" class="contacted_desinterested"/>
</div>
<div class="form-group">
<label class="control-label" for="customer_mark_spam">This lead is a spam</label>
<input type="checkbox" name="customer_mark_spam" id="customer_mark_spam" class="customer_mark_spam"/>
</div>
<div>
<span class="text-danger error_partner_assign_desinterested" style="display:none;">You need to fill up the next action and contact the customer before accepting the lead</span>
@@ -727,16 +733,16 @@
<label>Priority: </label>
<div class="input-group">
<label class="radio-inline">
<input type="radio" name="PriorityRadioOptions" value="0" t-att-checked="opportunity.priority not in ['1','2','3']"/><i class="fa fa-star"></i>
<input type="radio" name="PriorityRadioOptions" value="0" t-att-checked="opportunity.priority not in ['1','2','3']"/><i class="fa fa-star-o"></i>
</label>
<label class="radio-inline">
<input type="radio" name="PriorityRadioOptions" value="1" t-att-checked="opportunity.priority == '1'"/><i class="fa fa-star"></i><i class="fa fa-star"></i>
<input type="radio" name="PriorityRadioOptions" value="1" t-att-checked="opportunity.priority == '1'"/><i class="fa fa-star"></i>
</label>
<label class="radio-inline">
<input type="radio" name="PriorityRadioOptions" value="2" t-att-checked="opportunity.priority == '2'"/><i class="fa fa-star"></i><i class="fa fa-star"></i><i class="fa fa-star"></i>
<input type="radio" name="PriorityRadioOptions" value="2" t-att-checked="opportunity.priority == '2'"/><i class="fa fa-star"></i><i class="fa fa-star"></i>
</label>
<label class="radio-inline">
<input type="radio" name="PriorityRadioOptions" value="3" t-att-checked="opportunity.priority == '3'"/><i class="fa fa-star"></i><i class="fa fa-star"></i><i class="fa fa-star"></i><i class="fa fa-star"></i>
<input type="radio" name="PriorityRadioOptions" value="3" t-att-checked="opportunity.priority == '3'"/><i class="fa fa-star"></i><i class="fa fa-star"></i><i class="fa fa-star"></i>
</label>
</div>
</div>
-1
View File
@@ -30,7 +30,6 @@ class sale_quote(http.Controller):
if not Order:
return request.render('website.404')
request.session['sale_order_id'] = Order.id
days = 0
if Order.validity_date:
+11 -2
View File
@@ -771,7 +771,7 @@ class WebsiteSale(http.Controller):
return request.redirect("/shop/payment?error=no_token_or_missmatch_tx")
@http.route(['/shop/payment/transaction/<int:acquirer_id>'], type='json', auth="public", website=True)
def payment_transaction(self, acquirer_id, tx_type='form', token=None):
def payment_transaction(self, acquirer_id, tx_type='form', token=None, **kwargs):
""" Json method that creates a payment.transaction, used to create a
transaction when the user clicks on 'pay now' button. After having
created the transaction, the event continues and the user is redirected
@@ -781,7 +781,16 @@ class WebsiteSale(http.Controller):
user is redirected to the checkout page
"""
Transaction = request.env['payment.transaction'].sudo()
order = request.website.sale_get_order()
# In case the route is called directly from the JS (as done in Stripe payment method)
so_id = kwargs.get('so_id')
so_token = kwargs.get('so_token')
if so_id and so_token:
order = request.env['sale.order'].sudo().search([('id', '=', so_id), ('access_token', '=', so_token)])
elif so_id:
order = request.env['sale.order'].search([('id', '=', so_id)])
else:
order = request.website.sale_get_order()
if not order or not order.order_line or acquirer_id is None:
return request.redirect("/shop/checkout")
+15
View File
@@ -0,0 +1,15 @@
United States, 11/03/2016
APR, LLC 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,
Joseph McDonald jmcdonald@goapr.com https://github.com/joe1981alAPR
List of contributors:
Joseph McDonald jmcdonald@goapr.com https://github.com/joe1981alAPR
+3 -3
View File
@@ -152,11 +152,11 @@ class IrModel(models.Model):
def write(self, vals):
if '__last_update' in self._context:
self = self.with_context({k: v for k, v in self._context.iteritems() if k != '__last_update'})
if 'model' in vals:
if 'model' in vals and any(rec.model != vals['model'] for rec in self):
raise UserError(_('Field "Model" cannot be modified on models.'))
if 'state' in vals:
if 'state' in vals and any(rec.state != vals['state'] for rec in self):
raise UserError(_('Field "Type" cannot be modified on models.'))
if 'transient' in vals:
if 'transient' in vals and any(rec.transient != vals['transient'] for rec in self):
raise UserError(_('Field "Transient Model" cannot be modified on models.'))
# Filter out operations 4 from field id, because the web client always
# writes (4,id,False) even for non dirty items.
+7
View File
@@ -1243,6 +1243,13 @@ class Monetary(Field):
return float_precision(value, currency.decimal_places)
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):
""" 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'):
headers = {
'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)