diff --git a/addons/account/report/account_balance.py b/addons/account/report/account_balance.py index 5e7c441a671..61ece9107d4 100644 --- a/addons/account/report/account_balance.py +++ b/addons/account/report/account_balance.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import time -from odoo import api, models +from odoo import api, models, _ from odoo.exceptions import UserError diff --git a/addons/account/report/account_general_ledger.py b/addons/account/report/account_general_ledger.py index e154b629fcd..7f58736d889 100644 --- a/addons/account/report/account_general_ledger.py +++ b/addons/account/report/account_general_ledger.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import time -from odoo import api, models +from odoo import api, models, _ from odoo.exceptions import UserError diff --git a/addons/account/report/account_journal.py b/addons/account/report/account_journal.py index 9a26cdeb397..83b95bccb9f 100644 --- a/addons/account/report/account_journal.py +++ b/addons/account/report/account_journal.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import time -from odoo import api, models +from odoo import api, models, _ from odoo.exceptions import UserError diff --git a/addons/account/report/account_partner_ledger.py b/addons/account/report/account_partner_ledger.py index 4787469f476..9ca5875ffa4 100644 --- a/addons/account/report/account_partner_ledger.py +++ b/addons/account/report/account_partner_ledger.py @@ -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 diff --git a/addons/account/report/account_report_financial.py b/addons/account/report/account_report_financial.py index 918919014bf..56cafdeb3cb 100644 --- a/addons/account/report/account_report_financial.py +++ b/addons/account/report/account_report_financial.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import time -from odoo import api, models +from odoo import api, models, _ from odoo.exceptions import UserError diff --git a/addons/hr_attendance/models/hr_attendance.py b/addons/hr_attendance/models/hr_attendance.py index c8728ec0e31..c373bbe23f4 100644 --- a/addons/hr_attendance/models/hr_attendance.py +++ b/addons/hr_attendance/models/hr_attendance.py @@ -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: diff --git a/addons/hr_holidays/report/holidays_summary_report.py b/addons/hr_holidays/report/holidays_summary_report.py index 42c447655b1..0118429725f 100644 --- a/addons/hr_holidays/report/holidays_summary_report.py +++ b/addons/hr_holidays/report/holidays_summary_report.py @@ -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 diff --git a/addons/hr_payroll/report/report_contribution_register.py b/addons/hr_payroll/report/report_contribution_register.py index 3ae49b196ec..d1cc532cf62 100644 --- a/addons/hr_payroll/report/report_contribution_register.py +++ b/addons/hr_payroll/report/report_contribution_register.py @@ -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 diff --git a/addons/l10n_be/data/account_tax_template_data.xml b/addons/l10n_be/data/account_tax_template_data.xml index 05a5a0cb31a..2876dd1f6ba 100644 --- a/addons/l10n_be/data/account_tax_template_data.xml +++ b/addons/l10n_be/data/account_tax_template_data.xml @@ -1007,7 +1007,7 @@ 12% EU M. 12 - percent + group purchase diff --git a/addons/l10n_in_hr_payroll/report/report_hr_salary_employee_bymonth.py b/addons/l10n_in_hr_payroll/report/report_hr_salary_employee_bymonth.py index f42b76eaea6..5ece594f908 100644 --- a/addons/l10n_in_hr_payroll/report/report_hr_salary_employee_bymonth.py +++ b/addons/l10n_in_hr_payroll/report/report_hr_salary_employee_bymonth.py @@ -3,7 +3,7 @@ from datetime import date -from odoo import api, models +from odoo import api, models, _ from odoo.exceptions import UserError diff --git a/addons/l10n_in_hr_payroll/report/report_hr_yearly_salary_detail.py b/addons/l10n_in_hr_payroll/report/report_hr_yearly_salary_detail.py index dd768cf9f5f..82f262c8d1f 100644 --- a/addons/l10n_in_hr_payroll/report/report_hr_yearly_salary_detail.py +++ b/addons/l10n_in_hr_payroll/report/report_hr_yearly_salary_detail.py @@ -3,7 +3,7 @@ from datetime import date -from odoo import api, models +from odoo import api, models, _ from odoo.exceptions import UserError diff --git a/addons/mail/tests/test_mail_followers.py b/addons/mail/tests/test_mail_followers.py index 85e122bdb48..fbd078938d3 100644 --- a/addons/mail/tests/test_mail_followers.py +++ b/addons/mail/tests/test_mail_followers.py @@ -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, diff --git a/addons/pad/models/pad.py b/addons/pad/models/pad.py index dfa1dbf859b..eb6267ee389 100644 --- a/addons/pad/models/pad.py +++ b/addons/pad/models/pad.py @@ -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'): diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 735dbc2feec..606853a55b3 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -1474,10 +1474,12 @@ - Discount: - - - + + Discount: + + + + Total: diff --git a/addons/product/models/product.py b/addons/product/models/product.py index e2585067ed1..2c1d3ee7263 100644 --- a/addons/product/models/product.py +++ b/addons/product/models/product.py @@ -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', diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 8ec74b146e6..51fd12ad241 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -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: diff --git a/addons/report_intrastat/report/report_intrastat_invoice_template.xml b/addons/report_intrastat/report/report_intrastat_invoice_template.xml index a7d8b36206c..6827b216890 100644 --- a/addons/report_intrastat/report/report_intrastat_invoice_template.xml +++ b/addons/report_intrastat/report/report_intrastat_invoice_template.xml @@ -129,6 +129,10 @@

Fiscal Position: + +

+

+ Fiscal Position Remark:

diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index cb84495073d..b49182a23e0 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -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') diff --git a/addons/sale/tests/test_product_id_change.py b/addons/sale/tests/test_product_id_change.py index 4a795baa4c8..bf61c8d644f 100644 --- a/addons/sale/tests/test_product_id_change.py +++ b/addons/sale/tests/test_product_id_change.py @@ -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") diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index ae0fa43c55b..ccb431ddffa 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -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): diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py index a06e46a6127..84d6492bb42 100644 --- a/addons/stock/tests/test_stock_flow.py +++ b/addons/stock/tests/test_stock_flow.py @@ -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, diff --git a/addons/web/static/src/js/chrome/search_menus.js b/addons/web/static/src/js/chrome/search_menus.js index 1da7800b049..eee7303ffbd 100644 --- a/addons/web/static/src/js/chrome/search_menus.js +++ b/addons/web/static/src/js/chrome/search_menus.js @@ -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) { diff --git a/addons/website_links/static/src/js/website_links_charts.js b/addons/website_links/static/src/js/website_links_charts.js index 193c29d3a76..64119172eb2 100644 --- a/addons/website_links/static/src/js/website_links_charts.js +++ b/addons/website_links/static/src/js/website_links_charts.js @@ -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')); } }); diff --git a/addons/website_portal_sale/models/payment.py b/addons/website_portal_sale/models/payment.py index 8bfd8167229..a5095502fb6 100644 --- a/addons/website_portal_sale/models/payment.py +++ b/addons/website_portal_sale/models/payment.py @@ -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() diff --git a/addons/website_sale_stock/models/website.py b/addons/website_sale_stock/models/website.py index b5c866c27bb..e00d29cec89 100644 --- a/addons/website_sale_stock/models/website.py +++ b/addons/website_sale_stock/models/website.py @@ -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 diff --git a/odoo/netsvc.py b/odoo/netsvc.py index 3832ae10cd6..507e8e9c5c6 100644 --- a/odoo/netsvc.py +++ b/odoo/netsvc.py @@ -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'], diff --git a/odoo/sql_db.py b/odoo/sql_db.py index acd422b5d33..bf97fe63d42 100644 --- a/odoo/sql_db.py +++ b/odoo/sql_db.py @@ -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