[MERGE] forward port branch saas-15 up to 681f03e309
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
from odoo import api, models
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
from odoo import api, models
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
from odoo import api, models
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from datetime import datetime
|
||||
import time
|
||||
from odoo import api, models
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
from odoo import api, models
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ class HrAttendance(models.Model):
|
||||
# is the same as the one before our check_in time computed before, otherwise it overlaps
|
||||
last_attendance_before_check_out = self.env['hr.attendance'].search([
|
||||
('employee_id', '=', attendance.employee_id.id),
|
||||
('check_in', '<=', attendance.check_out),
|
||||
('check_in', '<', attendance.check_out),
|
||||
('id', '!=', attendance.id),
|
||||
], order='check_in desc', limit=1)
|
||||
if last_attendance_before_check_out and last_attendance_before_check_in != last_attendance_before_check_out:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from datetime import timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo import api, fields, models
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
|
||||
@@ -1007,7 +1007,7 @@
|
||||
<field name="name">12% EU M.</field>
|
||||
<field name="price_include" eval="0"/>
|
||||
<field name="amount">12</field>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="amount_type">group</field>
|
||||
<field name="type_tax_use">purchase</field>
|
||||
<field name="children_tax_ids" eval="[(6, 0, [ref('attn_VAT-IN-V81-12-EU-C1'), ref('attn_VAT-IN-V81-12-EU-C2')])]"/>
|
||||
<field name="chart_template_id" ref="l10nbe_chart_template"/>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from datetime import date
|
||||
|
||||
from odoo import api, models
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from datetime import date
|
||||
|
||||
from odoo import api, models
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
from odoo.addons.mail.tests.common import TestMail
|
||||
from odoo.tools.misc import mute_logger
|
||||
|
||||
|
||||
class TestMailFollowers(TestMail):
|
||||
@@ -135,7 +136,7 @@ class TestMailFollowers(TestMail):
|
||||
'name': 'I used to be schizo, but now we are alright.'
|
||||
})
|
||||
test_channel = self.env['mail.channel'].create({'name': 'Follower Channel'})
|
||||
with self.assertRaises(IntegrityError):
|
||||
with self.assertRaises(IntegrityError), mute_logger('odoo.sql_db'):
|
||||
self.env['mail.followers'].create({
|
||||
'res_model_id': self.env['ir.model']._get('mail.test').id,
|
||||
'res_id': test_record.id,
|
||||
|
||||
@@ -115,6 +115,14 @@ class PadCommon(models.AbstractModel):
|
||||
|
||||
# Set the pad content in vals
|
||||
def _set_pad_value(self, vals):
|
||||
# Update the pad if the `pad_content_field` is modified
|
||||
for k, field in self._fields.iteritems():
|
||||
if hasattr(field, 'pad_content_field') and vals.get(field.pad_content_field):
|
||||
company = self.env.user.sudo().company_id
|
||||
myPad = EtherpadLiteClient(company.pad_key, company.pad_server + '/api')
|
||||
path = self[k].split('/p/')[1]
|
||||
myPad.setText(path, (html2plaintext(vals[field.pad_content_field]).encode('utf-8')))
|
||||
# Update the `pad_content_field` if the pad is modified
|
||||
for k, v in vals.items():
|
||||
field = self._fields[k]
|
||||
if hasattr(field, 'pad_content_field'):
|
||||
|
||||
@@ -1474,10 +1474,12 @@
|
||||
</tr>
|
||||
</t>
|
||||
<tr>
|
||||
<td>Discount:</td>
|
||||
<td class="pos-right-align">
|
||||
<t t-esc="widget.format_currency(order.get_total_discount())"/>
|
||||
</td>
|
||||
<t t-if="order.get_total_discount() > 0">
|
||||
<td>Discount:</td>
|
||||
<td class="pos-right-align">
|
||||
<t t-esc="widget.format_currency(order.get_total_discount())"/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
<tr class="emph">
|
||||
<td>Total:</td>
|
||||
|
||||
@@ -83,7 +83,7 @@ class ProductProduct(models.Model):
|
||||
_description = "Product"
|
||||
_inherits = {'product.template': 'product_tmpl_id'}
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'default_code, id'
|
||||
_order = 'default_code, name, id'
|
||||
|
||||
price = fields.Float(
|
||||
'Price', compute='_compute_product_price',
|
||||
|
||||
@@ -232,6 +232,14 @@ class PurchaseOrder(models.Model):
|
||||
self.currency_id = self.partner_id.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id
|
||||
return {}
|
||||
|
||||
@api.onchange('fiscal_position_id')
|
||||
def _compute_tax_id(self):
|
||||
"""
|
||||
Trigger the recompute of the taxes if the fiscal position is changed on the PO.
|
||||
"""
|
||||
for order in self:
|
||||
order.order_line._compute_tax_id()
|
||||
|
||||
@api.onchange('partner_id')
|
||||
def onchange_partner_id_warning(self):
|
||||
if not self.partner_id:
|
||||
@@ -407,6 +415,10 @@ class PurchaseOrder(models.Model):
|
||||
picking = pickings[0]
|
||||
moves = order.order_line._create_stock_moves(picking)
|
||||
moves = moves.filtered(lambda x: x.state not in ('done', 'cancel')).action_confirm()
|
||||
seq = 0
|
||||
for move in moves:
|
||||
seq += 5
|
||||
move.sequence = seq
|
||||
moves.force_assign()
|
||||
picking.message_post_with_view('mail.message_origin_link',
|
||||
values={'self': picking, 'origin': order},
|
||||
@@ -518,6 +530,14 @@ class PurchaseOrderLine(models.Model):
|
||||
'price_subtotal': taxes['total_excluded'],
|
||||
})
|
||||
|
||||
@api.multi
|
||||
def _compute_tax_id(self):
|
||||
for line in self:
|
||||
fpos = line.order_id.fiscal_position_id or line.order_id.partner_id.property_account_position_id
|
||||
# If company_id is set, always filter taxes by the company
|
||||
taxes = line.product_id.supplier_taxes_id.filtered(lambda r: not line.company_id or r.company_id == line.company_id)
|
||||
line.taxes_id = fpos.map_tax(taxes, line.product_id, line.order_id.partner_id) if fpos else taxes
|
||||
|
||||
@api.depends('invoice_lines.invoice_id.state')
|
||||
def _compute_qty_invoiced(self):
|
||||
for line in self:
|
||||
|
||||
@@ -129,6 +129,10 @@
|
||||
</p>
|
||||
<p t-if="o.fiscal_position_id">
|
||||
<strong>Fiscal Position:</strong>
|
||||
<span t-field="o.fiscal_position_id"/>
|
||||
</p>
|
||||
<p t-if="o.fiscal_position_id.note">
|
||||
<strong>Fiscal Position Remark:</strong>
|
||||
<span t-field="o.fiscal_position_id.note"/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -514,12 +514,14 @@ class SaleOrder(models.Model):
|
||||
for tax in line.tax_id:
|
||||
group = tax.tax_group_id
|
||||
res.setdefault(group, 0.0)
|
||||
taxes = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty,
|
||||
# FORWARD-PORT UP TO SAAS-17
|
||||
price_reduce = line.price_unit * (1.0 - line.discount / 100.0)
|
||||
taxes = tax.compute_all(price_reduce + base_tax, quantity=line.product_uom_qty,
|
||||
product=line.product_id, partner=self.partner_shipping_id)['taxes']
|
||||
for t in taxes:
|
||||
res[group] += t['amount']
|
||||
if tax.include_base_amount:
|
||||
base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1, product=line.product_id,
|
||||
base_tax += tax.compute_all(price_reduce + base_tax, quantity=1, product=line.product_id,
|
||||
partner=self.partner_shipping_id)['taxes'][0]['amount']
|
||||
res = sorted(res.items(), key=lambda l: l[0].sequence)
|
||||
res = map(lambda l: (l[0].name, l[1]), res)
|
||||
@@ -927,7 +929,7 @@ class SaleOrderLine(models.Model):
|
||||
lang=self.order_id.partner_id.lang,
|
||||
partner=self.order_id.partner_id.id,
|
||||
quantity=self.product_uom_qty,
|
||||
date_order=self.order_id.date_order,
|
||||
date=self.order_id.date_order,
|
||||
pricelist=self.order_id.pricelist_id.id,
|
||||
uom=self.product_uom.id,
|
||||
fiscal_position=self.env.context.get('fiscal_position')
|
||||
|
||||
@@ -68,3 +68,51 @@ class TestOnchangeProductId(TransactionCase):
|
||||
so_line = so.order_line[0]
|
||||
so_line.product_id_change()
|
||||
self.assertEquals(100, so_line.price_unit, "The included tax must be subtracted to the price")
|
||||
|
||||
def test_pricelist_application(self):
|
||||
""" Test different prices are correctly applied based on dates """
|
||||
support_product = self.env.ref('product.product_product_2')
|
||||
support_product.list_price = 100
|
||||
partner = self.res_partner_model.create(dict(name="George"))
|
||||
|
||||
christmas_pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'Christmas pricelist',
|
||||
'item_ids': [(0, 0, {
|
||||
'date_start': "2017-12-01",
|
||||
'date_end': "2017-12-24",
|
||||
'compute_price': 'percentage',
|
||||
'base': 'list_price',
|
||||
'percent_price': 20,
|
||||
'applied_on': '3_global',
|
||||
'name': 'Pre-Christmas discount'
|
||||
}), (0, 0, {
|
||||
'date_start': "2017-12-25",
|
||||
'date_end': "2017-12-31",
|
||||
'compute_price': 'percentage',
|
||||
'base': 'list_price',
|
||||
'percent_price': 50,
|
||||
'applied_on': '3_global',
|
||||
'name': 'Post-Christmas super-discount'
|
||||
})]
|
||||
})
|
||||
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': partner.id,
|
||||
'date_order': '2017-12-20',
|
||||
'pricelist_id': christmas_pricelist.id,
|
||||
})
|
||||
|
||||
order_line = self.env['sale.order.line'].new({
|
||||
'order_id': so.id,
|
||||
'product_id': support_product.id,
|
||||
})
|
||||
|
||||
# force compute uom and prices
|
||||
order_line.product_id_change()
|
||||
order_line.product_uom_change()
|
||||
|
||||
self.assertEqual(order_line.price_unit, 80, "First date pricelist rule not applied")
|
||||
|
||||
so.date_order = '2017-12-30'
|
||||
order_line.product_id_change()
|
||||
self.assertEqual(order_line.price_unit, 50, "Second date pricelist rule not applied")
|
||||
|
||||
@@ -273,6 +273,10 @@ class Product(models.Model):
|
||||
if value == 0.0 and operator in ('=', '>=', '<='):
|
||||
return self._search_product_quantity(operator, value, 'qty_available')
|
||||
product_ids = self._search_qty_available_new(operator, value, self._context.get('lot_id'), self._context.get('owner_id'), self._context.get('package_id'))
|
||||
if (value > 0 and operator in ('<=', '<')) or (value < 0 and operator in ('>=', '>')):
|
||||
# include also unavailable products
|
||||
domain = self._search_product_quantity(operator, value, 'qty_available')
|
||||
product_ids += domain[0][2]
|
||||
return [('id', 'in', product_ids)]
|
||||
|
||||
def _search_qty_available_new(self, operator, value, lot_id=False, owner_id=False, package_id=False):
|
||||
|
||||
@@ -1728,8 +1728,8 @@ class TestStockFlow(TestStockCommon):
|
||||
self.assertEquals(picking_out.state, "assigned")
|
||||
|
||||
def test_74_move_state_waiting_mto(self):
|
||||
""" This test will check that when a move is unreserved, it state change to 'waiting' if
|
||||
it has ancestors or is has a 'procure_method' equal to 'make_to_order' else the state
|
||||
""" This test will check that when a move is unreserved, its state changes to 'waiting' if
|
||||
it has ancestors or if it has a 'procure_method' equal to 'make_to_order' else the state
|
||||
changes to 'confirmed'.
|
||||
"""
|
||||
picking_out = self.PickingObj.create({
|
||||
@@ -1754,7 +1754,7 @@ class TestStockFlow(TestStockCommon):
|
||||
'picking_id': picking_out.id,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location})
|
||||
the_ancestor = self.MoveObj.create({
|
||||
self.MoveObj.create({
|
||||
'name': self.productA.name,
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_qty': 2,
|
||||
|
||||
@@ -122,6 +122,7 @@ return Widget.extend({
|
||||
model_id: this.target_model,
|
||||
context: results.context,
|
||||
domain: results.domain,
|
||||
sort: JSON.stringify(this.searchview.dataset._sort),
|
||||
is_default: default_filter,
|
||||
action_id: this.action_id,
|
||||
};
|
||||
@@ -243,6 +244,13 @@ return Widget.extend({
|
||||
}
|
||||
this.query.reset([this.facet_for(filter)], {
|
||||
preventSearch: preventSearch || false});
|
||||
|
||||
// Load sort settings on view
|
||||
if (!_.isUndefined(filter.sort)){
|
||||
var sort_items = JSON.parse(filter.sort);
|
||||
this.searchview.dataset.set_sort(sort_items);
|
||||
}
|
||||
|
||||
this.$filters[this.key_for(filter)].addClass('selected');
|
||||
},
|
||||
remove_filter: function (filter, $filter, key) {
|
||||
|
||||
@@ -4,8 +4,10 @@ odoo.define('website_links.charts', function (require) {
|
||||
var rpc = require('web.rpc');
|
||||
var Widget = require('web.Widget');
|
||||
var base = require('web_editor.base');
|
||||
var core = require('web.core');
|
||||
var website = require('website.website');
|
||||
|
||||
var _t = core._t;
|
||||
var exports = {};
|
||||
|
||||
if(!$('.o_website_links_chart').length) {
|
||||
@@ -48,11 +50,11 @@ if(!$('.o_website_links_chart').length) {
|
||||
|
||||
// Set title
|
||||
var nb_clicks = _.reduce(clicks_array, function(total, val) { return total + val[1] ; }, 0);
|
||||
$(this.$element + ' .title').html(nb_clicks + ' clicks');
|
||||
$(this.$element + ' .title').html(nb_clicks + _t(' clicks'));
|
||||
|
||||
// Fit data into the NVD3 scheme
|
||||
var chart_data = [{}];
|
||||
chart_data[0]['key'] = '# of clicks';
|
||||
chart_data[0]['key'] = _t('# of clicks');
|
||||
chart_data[0]['values'] = clicks_array;
|
||||
|
||||
nv.addGraph(function() {
|
||||
@@ -94,12 +96,12 @@ if(!$('.o_website_links_chart').length) {
|
||||
// Process country data to fit into the NVD3 scheme
|
||||
var processed_data = [];
|
||||
for(var i = 0 ; i < this.data.length ; i++) {
|
||||
var country_name = this.data[i]['country_id'] ? this.data[i]['country_id'][1] : 'Undefined';
|
||||
var country_name = this.data[i]['country_id'] ? this.data[i]['country_id'][1] : _t('Undefined');
|
||||
processed_data.push({'label':country_name + ' (' + this.data[i]['country_id_count'] + ')', 'value':this.data[i]['country_id_count']});
|
||||
}
|
||||
|
||||
// Set title
|
||||
$(this.$element + ' .title').html(this.data.length + ' countries');
|
||||
$(this.$element + ' .title').html(this.data.length + _t(' countries'));
|
||||
|
||||
nv.addGraph(function() {
|
||||
var chart = nv.models.pieChart()
|
||||
@@ -224,9 +226,9 @@ if(!$('.o_website_links_chart').length) {
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('#all_time_charts').prepend('There is no data to show');
|
||||
$('#last_month_charts').prepend('There is no data to show');
|
||||
$('#last_week_charts').prepend('There is no data to show');
|
||||
$('#all_time_charts').prepend(_t('There is no data to show'));
|
||||
$('#last_month_charts').prepend(_t('There is no data to show'));
|
||||
$('#last_week_charts').prepend(_t('There is no data to show'));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class PaymentTransaction(models.Model):
|
||||
amount_matches = float_compare(tx.amount, tx.sale_order_id.amount_total, 2) == 0
|
||||
if amount_matches:
|
||||
if not acquirer_name:
|
||||
acquirer_name = tx.sale_order_id.payment_acquirer_id.provider or 'unknown'
|
||||
acquirer_name = tx.acquirer_id.provider or 'unknown'
|
||||
if tx.state == 'authorized' and tx.acquirer_id.auto_confirm == 'authorize':
|
||||
_logger.info('<%s> transaction authorized, auto-confirming order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
|
||||
tx.sale_order_id.with_context(send_email=True).action_confirm()
|
||||
|
||||
@@ -9,7 +9,11 @@ class Website(models.Model):
|
||||
self.ensure_one()
|
||||
values = super(Website, self)._prepare_sale_order_values(partner, pricelist)
|
||||
if values['company_id']:
|
||||
warehouses = self.env['stock.warehouse'].sudo().search([('company_id', '=', values['company_id'])], limit=1)
|
||||
if warehouses:
|
||||
values['warehouse_id'] = warehouses.id
|
||||
warehouse_id = (
|
||||
self.env['ir.values'].get_default('sale.order', 'warehouse_id', company_id=values.get('company_id')) or
|
||||
self.env['ir.values'].get_default('sale.order', 'warehouse_id') or
|
||||
self.env['stock.warehouse'].sudo().search([('company_id', '=', values['company_id'])], limit=1).id
|
||||
)
|
||||
if warehouse_id:
|
||||
values['warehouse_id'] = warehouse_id
|
||||
return values
|
||||
|
||||
+4
-5
@@ -188,17 +188,16 @@ def init_logger():
|
||||
for logconfig_item in logging_configurations:
|
||||
_logger.debug('logger level set: "%s"', logconfig_item)
|
||||
|
||||
|
||||
DEFAULT_LOG_CONFIGURATION = [
|
||||
'odoo.http.rpc.request:INFO',
|
||||
'odoo.http.rpc.response:INFO',
|
||||
'odoo.addons.web.http:INFO',
|
||||
'odoo.sql_db:INFO',
|
||||
':INFO',
|
||||
]
|
||||
PSEUDOCONFIG_MAPPER = {
|
||||
'debug_rpc_answer': ['odoo:DEBUG','odoo.http.rpc.request:DEBUG', 'odoo.http.rpc.response:DEBUG'],
|
||||
'debug_rpc': ['odoo:DEBUG','odoo.http.rpc.request:DEBUG'],
|
||||
'debug': ['odoo:DEBUG'],
|
||||
'debug_rpc_answer': ['odoo:DEBUG', 'odoo.sql_db:INFO', 'odoo.http.rpc:DEBUG'],
|
||||
'debug_rpc': ['odoo:DEBUG', 'odoo.sql_db:INFO', 'odoo.http.rpc.request:DEBUG'],
|
||||
'debug': ['odoo:DEBUG', 'odoo.sql_db:INFO'],
|
||||
'debug_sql': ['odoo.sql_db:DEBUG'],
|
||||
'info': [],
|
||||
'warn': ['odoo:WARNING', 'werkzeug:WARNING'],
|
||||
|
||||
+1
-1
@@ -237,7 +237,7 @@ class Cursor(object):
|
||||
res = self._obj.execute(query, params)
|
||||
except Exception as e:
|
||||
if self._default_log_exceptions if log_exceptions is None else log_exceptions:
|
||||
_logger.info("bad query: %s \nERROR: %s", self._obj.query or query, e)
|
||||
_logger.error("bad query: %s\nERROR: %s", self._obj.query or query, e)
|
||||
raise
|
||||
|
||||
# simple query count is always computed
|
||||
|
||||
Reference in New Issue
Block a user