[MERGE] forward port branch 10.0 up to 3d085f6
This commit is contained in:
+5
-1
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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});
|
||||
|
||||
|
||||
@@ -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', '>', 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"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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', '<=', 0.99)]}" invisible="not context.get('show_reserved') or context.get('state_done')"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</field>
|
||||
<field name="standard_price" position="replace">
|
||||
<field name="standard_price"
|
||||
attrs="{'readonly':['&', ('valuation','=','real_time'), ('cost_method', 'in', ['standard', 'average'])], 'invisible': [('product_variant_count', '>', 1)]}" class="oe_inline"
|
||||
attrs="{'readonly':['&', ('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,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)
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}));
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user