diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 6fb13ffeb73..15669281ead 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -415,7 +415,7 @@ class AccountBankStatementLine(models.Model): st_line_currency = self.currency_id or self.journal_id.currency_id currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places - params = {'company_id': self.company_id.id, + params = {'company_id': self.env.user.company_id.id, 'account_payable_receivable': (self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id), 'amount': float_round(amount, precision_digits=precision), 'partner_id': self.partner_id.id, diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 9f9111a7300..c7c7326b788 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -956,6 +956,7 @@ class AccountMoveLine(models.Model): temp['amount_currency'] = bank.company_id.currency_id.with_context(ctx).compute(tax_vals['amount'], bank.currency_id, round=True) if vals.get('tax_exigible'): temp['tax_exigible'] = True + temp['account_id'] = tax.cash_basis_account.id or account_id tax_lines_vals.append(temp) #Toggle the 'tax_exigible' field to False in case it is not yet given and the tax in 'tax_line_id' or one of diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 62da2abe007..24dbebe5e88 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -779,7 +779,7 @@ var StatementModel = BasicModel.extend({ }).then(function (result) { if (result.length > 0) { var line = self.getLine(handle); - self.lines[handle].st_line.open_balance_account_id = line.amount < 0 ? result[0]['property_account_payable_id'][0] : result[0]['property_account_receivable_id'][0]; + self.lines[handle].st_line.open_balance_account_id = line.balance.amount < 0 ? result[0]['property_account_payable_id'][0] : result[0]['property_account_receivable_id'][0]; } }); }, diff --git a/addons/base_geolocalize/models/res_partner.py b/addons/base_geolocalize/models/res_partner.py index 855b4d8bbd1..3594e1cb61b 100644 --- a/addons/base_geolocalize/models/res_partner.py +++ b/addons/base_geolocalize/models/res_partner.py @@ -1,23 +1,35 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import json +import logging import requests from odoo import api, fields, models, tools, _ from odoo.exceptions import UserError +_logger = logging.getLogger(__name__) -def geo_find(addr): + +def geo_find(addr, apikey=False): if not addr: return None - url = 'https://maps.googleapis.com/maps/api/geocode/json' + + if not apikey: + raise UserError(_('''API key for GeoCoding (Places) required.\n + Save this key in System Parameters with key: google.api_key_geocode, value: + Visit https://developers.google.com/maps/documentation/geocoding/get-api-key for more information. + ''')) + + url = "https://maps.googleapis.com/maps/api/geocode/json" try: - result = requests.get(url, params={'sensor': 'false', 'address': addr}).json() + result = requests.get(url, params={'sensor': 'false', 'address': addr, 'key': apikey}).json() except Exception as e: raise UserError(_('Cannot contact geolocation servers. Please make sure that your Internet connection is up and running (%s).') % e) if result['status'] != 'OK': + if result.get('error_message'): + _logger.error(result['error_message']) return None try: @@ -45,22 +57,26 @@ class ResPartner(models.Model): partner_longitude = fields.Float(string='Geo Longitude', digits=(16, 5)) date_localization = fields.Date(string='Geolocation Date') + @classmethod + def _geo_localize(cls, apikey, street='', zip='', city='', state='', country=''): + search = geo_query_address(street=street, zip=zip, city=city, state=state, country=country) + result = geo_find(search, apikey) + if result is None: + search = geo_query_address(city=city, state=state, country=country) + result = geo_find(search, apikey) + return result + @api.multi def geo_localize(self): # We need country names in English below + apikey = self.env['ir.config_parameter'].sudo().get_param('google.api_key_geocode') for partner in self.with_context(lang='en_US'): - result = geo_find(geo_query_address(street=partner.street, - zip=partner.zip, - city=partner.city, - state=partner.state_id.name, - country=partner.country_id.name)) - if result is None: - result = geo_find(geo_query_address( - city=partner.city, - state=partner.state_id.name, - country=partner.country_id.name - )) - + result = partner._geo_localize(apikey, + partner.street, + partner.zip, + partner.city, + partner.state_id.name, + partner.country_id.name) if result: partner.write({ 'partner_latitude': result[0], diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py index eac470a8be5..9ee65f0df1d 100644 --- a/addons/mrp/wizard/change_production_qty.py +++ b/addons/mrp/wizard/change_production_qty.py @@ -77,6 +77,8 @@ class ChangeProductionQty(models.TransientModel): wo.qty_producing = quantity if wo.qty_produced < wo.qty_production and wo.state == 'done': wo.state = 'progress' + if wo.qty_produced == wo.qty_production and wo.state == 'progress': + wo.state = 'done' # assign moves; last operation receive all unassigned moves # TODO: following could be put in a function as it is similar as code in _workorders_create # TODO: only needed when creating new moves diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index 21f75b5cf0c..6ce7ead5762 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -292,6 +292,13 @@ class SaleOrderLine(models.Model): }) return values + def _get_qty_procurement(self): + self.ensure_one() + qty = 0.0 + for move in self.move_ids.filtered(lambda r: r.state != 'cancel'): + qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') + return qty + @api.multi def _action_launch_procurement_rule(self): """ @@ -304,9 +311,7 @@ class SaleOrderLine(models.Model): for line in self: if line.state != 'sale' or not line.product_id.type in ('consu','product'): continue - qty = 0.0 - for move in line.move_ids.filtered(lambda r: r.state != 'cancel'): - qty += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom, rounding_method='HALF-UP') + qty = line._get_qty_procurement() if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0: continue diff --git a/addons/stock_dropshipping/models/__init__.py b/addons/stock_dropshipping/models/__init__.py index c617a28246d..7d6f1be055f 100644 --- a/addons/stock_dropshipping/models/__init__.py +++ b/addons/stock_dropshipping/models/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import purchase \ No newline at end of file +from . import purchase +from . import sale diff --git a/addons/stock_dropshipping/models/sale.py b/addons/stock_dropshipping/models/sale.py new file mode 100644 index 00000000000..81f975cbce6 --- /dev/null +++ b/addons/stock_dropshipping/models/sale.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, models, fields + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + purchase_line_ids = fields.One2many('purchase.order.line', 'sale_line_id') + + @api.multi + def _get_qty_procurement(self): + if not self.move_ids.filtered(lambda r: r.state != 'cancel') and self.purchase_line_ids.filtered(lambda r: r.state != 'cancel'): + qty = 0.0 + for po_line in self.purchase_line_ids.filtered(lambda r: r.state != 'cancel'): + qty += po_line.product_uom._compute_quantity(po_line.product_qty, self.product_uom, rounding_method='HALF-UP') + return qty + else: + return super(SaleOrderLine, self)._get_qty_procurement() + diff --git a/addons/stock_dropshipping/tests/__init__.py b/addons/stock_dropshipping/tests/__init__.py index c060ad7247b..990bd0591e3 100644 --- a/addons/stock_dropshipping/tests/__init__.py +++ b/addons/stock_dropshipping/tests/__init__.py @@ -6,4 +6,3 @@ from . import test_dropship from . import test_lifo_price from . import test_procurement_exception from . import test_stockvaluation - diff --git a/addons/stock_dropshipping/tests/test_dropship.py b/addons/stock_dropshipping/tests/test_dropship.py index 7f06c437fa3..9837d93f0af 100644 --- a/addons/stock_dropshipping/tests/test_dropship.py +++ b/addons/stock_dropshipping/tests/test_dropship.py @@ -5,6 +5,50 @@ from odoo.tests import common, Form from odoo.tools import mute_logger +class TestDropship(common.TransactionCase): + def test_change_qty(self): + # enable the dropship and MTO route on the product + prod = self.env.ref('product.product_product_8') + dropshipping_route = self.env.ref('stock_dropshipping.route_drop_shipping') + mto_route = self.env.ref('stock.route_warehouse0_mto') + prod.write({'route_ids': [(6, 0, [dropshipping_route.id, mto_route.id])]}) + + # add a vendor + vendor1 = self.env['res.partner'].create({'name': 'vendor1'}) + seller1 = self.env['product.supplierinfo'].create({ + 'name': vendor1.id, + 'price': 8, + }) + prod.write({'seller_ids': [(6, 0, [seller1.id])]}) + + # sell one unit of this product + cust = self.env['res.partner'].create({'name': 'customer1'}) + so = self.env['sale.order'].create({ + 'partner_id': cust.id, + 'partner_invoice_id': cust.id, + 'partner_shipping_id': cust.id, + 'order_line': [(0, 0, { + 'name': prod.name, + 'product_id': prod.id, + 'product_uom_qty': 1.00, + 'product_uom': prod.uom_id.id, + 'price_unit': 12, + })], + 'pricelist_id': self.env.ref('product.list0').id, + 'picking_policy': 'direct', + }) + so.action_confirm() + po = self.env['purchase.order'].search([('group_id', '=', so.procurement_group_id.id)]) + po_line = po.order_line + + # Check the qty on the P0 + self.assertAlmostEqual(po_line.product_qty, 1.00) + + # Update qty on SO and check PO + so.order_line.product_uom_qty = 2.00 + self.assertAlmostEqual(po_line.product_qty, 2.00) + + class TestDropship(common.TransactionCase): def test_00_dropship(self): diff --git a/addons/website_crm_partner_assign/models/crm_lead.py b/addons/website_crm_partner_assign/models/crm_lead.py index b5f0c2d49c4..c9e76447d90 100644 --- a/addons/website_crm_partner_assign/models/crm_lead.py +++ b/addons/website_crm_partner_assign/models/crm_lead.py @@ -86,19 +86,10 @@ class CrmLead(models.Model): if lead.partner_latitude and lead.partner_longitude: continue if lead.country_id: - result = geo_find(geo_query_address(street=lead.street, - zip=lead.zip, - city=lead.city, - state=lead.state_id.name, - country=lead.country_id.name)) - - if result is None: - result = geo_find(geo_query_address( - city=lead.city, - state=lead.state_id.name, - country=lead.country_id.name - )) - + apikey = self.env['ir.config_parameter'].sudo().get_param('google.api_key_geocode') + result = self.env['res.partner']._geo_localize(apikey, + lead.street, lead.zip, lead.city, + lead.state_id.name, lead.country_id.name) if result: lead.write({ 'partner_latitude': result[0], diff --git a/addons/website_crm_partner_assign/tests/test_partner_assign.py b/addons/website_crm_partner_assign/tests/test_partner_assign.py index 3b4344e255f..83989c0a4d8 100644 --- a/addons/website_crm_partner_assign/tests/test_partner_assign.py +++ b/addons/website_crm_partner_assign/tests/test_partner_assign.py @@ -16,7 +16,7 @@ class TestPartnerAssign(TransactionCase): def setUp(self): super(TestPartnerAssign, self).setUp() - def geo_find(addr): + def geo_find(addr, apikey): return { 'Wavre, Belgium': (50.7158956, 4.6128075), 'Cannon Hill Park, B46 3AG Birmingham, United Kingdom': (52.45216, -1.898578), diff --git a/doc/cla/corporate/initos.md b/doc/cla/corporate/initos.md index 3c95fe2d07f..dd7fb8ea0e5 100644 --- a/doc/cla/corporate/initos.md +++ b/doc/cla/corporate/initos.md @@ -21,3 +21,4 @@ Peter Hahn peter.hahn@initos.com https://github.com/codingforfun Claudia Haida claudia.haida@initos.com Andreas Zöllner andreas.zoellner@initos.com https://github.com/azoellner Rami Alwafaie rami.alwafaie@initos.com https://github.com/rami-wafaie +Florian Kantelberg florian.kantelberg@initos.com https://github.com/fkantelberg diff --git a/doc/reference/views.rst b/doc/reference/views.rst index 0a541f162ea..cc165285915 100644 --- a/doc/reference/views.rst +++ b/doc/reference/views.rst @@ -808,6 +808,18 @@ element is ```` which can take the following attributes: The elements allowed within a pivot view are the same as for the graph view. +In Pivot view a ``field`` can have a ``widget`` attribute to dictate its format. +The widget should be a field formatter, of which the most interesting are +``date``, ``datetime``, ``float_time``, and ``monetary``. + +For instance a timesheet pivot view could be defined as:: + + + + + + + .. _reference/views/kanban: Kanban