diff --git a/.tx/config b/.tx/config index ba9318f5a58..da3e448a067 100644 --- a/.tx/config +++ b/.tx/config @@ -362,6 +362,11 @@ file_filter = addons/im_livechat/i18n/.po source_file = addons/im_livechat/i18n/im_livechat.pot source_lang = en +[odoo-master.im_support] +file_filter = addons/im_support/i18n/.po +source_file = addons/im_support/i18n/im_support.pot +source_lang = en + [odoo-master.link_tracker] file_filter = addons/link_tracker/i18n/.po source_file = addons/link_tracker/i18n/link_tracker.pot @@ -407,6 +412,11 @@ file_filter = addons/mrp/i18n/.po source_file = addons/mrp/i18n/mrp.pot source_lang = en +[odoo-master.mrp_bom_cost] +file_filter = addons/mrp_bom_cost/i18n/.po +source_file = addons/mrp_bom_cost/i18n/mrp_bom_cost.pot +source_lang = en + [odoo-master.mrp_byproduct] file_filter = addons/mrp_byproduct/i18n/.po source_file = addons/mrp_byproduct/i18n/mrp_byproduct.pot @@ -552,11 +562,6 @@ file_filter = addons/product_expiry/i18n/.po source_file = addons/product_expiry/i18n/product_expiry.pot source_lang = en -[odoo-master.product_extended] -file_filter = addons/product_extended/i18n/.po -source_file = addons/product_extended/i18n/product_extended.pot -source_lang = en - [odoo-master.product_margin] file_filter = addons/product_margin/i18n/.po source_file = addons/product_margin/i18n/product_margin.pot @@ -722,6 +727,11 @@ file_filter = addons/transifex/i18n/.po source_file = addons/transifex/i18n/transifex.pot source_lang = en +[odoo-master.uom] +file_filter = addons/uom/i18n/.po +source_file = addons/uom/i18n/uom.pot +source_lang = en + [odoo-master.utm] file_filter = addons/utm/i18n/.po source_file = addons/utm/i18n/utm.pot diff --git a/addons/account/models/product.py b/addons/account/models/product.py index c52355f6f4f..b72bc31d7bd 100644 --- a/addons/account/models/product.py +++ b/addons/account/models/product.py @@ -35,22 +35,6 @@ class ProductTemplate(models.Model): domain=[('deprecated', '=', False)], help="The expense is accounted for when a vendor bill is validated, except in anglo-saxon accounting with perpetual inventory valuation in which case the expense (Cost of Goods Sold account) is recognized at the customer invoice validation. If the field is empty, it uses the one defined in the product category.") - @api.multi - def write(self, vals): - #TODO: really? i don't see the reason we'd need that constraint.. - check = self.ids and 'uom_po_id' in vals - if check: - self._cr.execute("SELECT id, uom_po_id FROM product_template WHERE id IN %s", [tuple(self.ids)]) - uoms = dict(self._cr.fetchall()) - res = super(ProductTemplate, self).write(vals) - if check: - self._cr.execute("SELECT id, uom_po_id FROM product_template WHERE id IN %s", [tuple(self.ids)]) - if dict(self._cr.fetchall()) != uoms: - products = self.env['product.product'].search([('product_tmpl_id', 'in', self.ids)]) - if self.env['account.move.line'].search_count([('product_id', 'in', products.ids)]): - raise UserError(_('You can not change the unit of measure of a product that has been already used in an account journal item. If you need to change the unit of measure, you should archive this product and create a new one.')) - return res - @api.multi def _get_product_accounts(self): return { diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py index 75891ff7a54..95a01bfd0c2 100644 --- a/addons/account/report/account_aged_partner_balance.py +++ b/addons/account/report/account_aged_partner_balance.py @@ -203,7 +203,7 @@ class ReportAgedPartnerBalance(models.AbstractModel): values['name'] = _('Unknown Partner') values['trust'] = False - if at_least_one_amount or self._context.get('include_nullified_amount'): + if at_least_one_amount or (self._context.get('include_nullified_amount') and lines[partner['partner_id']]): res.append(values) return res, total, lines diff --git a/addons/account/report/account_general_ledger.py b/addons/account/report/account_general_ledger.py index 922ec24ff0a..be46e7b361b 100644 --- a/addons/account/report/account_general_ledger.py +++ b/addons/account/report/account_general_ledger.py @@ -38,7 +38,7 @@ class ReportGeneralLedger(models.AbstractModel): init_wheres.append(init_where_clause.strip()) init_filters = " AND ".join(init_wheres) filters = init_filters.replace('account_move_line__move_id', 'm').replace('account_move_line', 'l') - sql = ("""SELECT 0 AS lid, l.account_id AS account_id, '' AS ldate, '' AS lcode, NULL AS amount_currency, '' AS lref, 'Initial Balance' AS lname, COALESCE(SUM(l.debit),0.0) AS debit, COALESCE(SUM(l.credit),0.0) AS credit, COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance, '' AS lpartner_id,\ + sql = ("""SELECT 0 AS lid, l.account_id AS account_id, '' AS ldate, '' AS lcode, 0.0 AS amount_currency, '' AS lref, 'Initial Balance' AS lname, COALESCE(SUM(l.debit),0.0) AS debit, COALESCE(SUM(l.credit),0.0) AS credit, COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance, '' AS lpartner_id,\ '' AS move_name, '' AS mmove_id, '' AS currency_code,\ NULL AS currency_id,\ '' AS invoice_id, '' AS invoice_type, '' AS invoice_number,\ diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index 814fda8c7d7..3cacc14d212 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -760,6 +760,63 @@ class TestReconciliation(AccountingTestCase): self.assertEqual(reversed_bank_line.full_reconcile_id.id, bank_line.full_reconcile_id.id) self.assertEqual(reversed_customer_line.full_reconcile_id.id, customer_line.full_reconcile_id.id) + def create_invoice_partner(self, type='out_invoice', invoice_amount=50, currency_id=None, partner_id=False): + #we create an invoice in given currency + invoice = self.account_invoice_model.create({'partner_id': partner_id, + 'reference_type': 'none', + 'currency_id': currency_id, + 'name': type == 'out_invoice' and 'invoice to client' or 'invoice to vendor', + 'account_id': self.account_rcv.id, + 'type': type, + 'date_invoice': time.strftime('%Y') + '-07-01', + }) + self.account_invoice_line_model.create({'product_id': self.product.id, + 'quantity': 1, + 'price_unit': invoice_amount, + 'invoice_id': invoice.id, + 'name': 'product that cost ' + str(invoice_amount), + 'account_id': self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1).id, + }) + + #validate invoice + invoice.action_invoice_open() + return invoice + + def test_aged_report(self): + AgedReport = self.env['report.account.report_agedpartnerbalance'].with_context(include_nullified_amount=True) + account_type = ['receivable'] + report_date_to = time.strftime('%Y') + '-07-15' + partner = self.env['res.partner'].create({'name': 'AgedPartner'}) + currency = self.env.user.company_id.currency_id + + invoice = self.create_invoice_partner(currency_id=currency.id, partner_id=partner.id) + journal = self.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'THE', 'currency_id': currency.id}) + + statement = self.make_payment(invoice, journal, 50) + + # Case 1: The invoice and payment are reconciled: Nothing should appear + report_lines, total, amls = AgedReport._get_partner_move_lines(account_type, report_date_to, 'posted', 30) + + partner_lines = [line for line in report_lines if line['partner_id'] == partner.id] + self.assertEqual(partner_lines, [], 'The aged receivable shouldn\'t have lines at this point') + self.assertFalse(partner.id in amls, 'The aged receivable should not have amls either') + + # Case 2: The invoice and payment are not reconciled: we should have one line on the report + # and 2 amls + invoice.move_id.line_ids.with_context(invoice_id=invoice.id).remove_move_reconcile() + report_lines, total, amls = AgedReport._get_partner_move_lines(account_type, report_date_to, 'posted', 30) + + partner_lines = [line for line in report_lines if line['partner_id'] == partner.id] + self.assertEqual(partner_lines, [{'trust': 'normal', '1': 0.0, '0': 0.0, 'direction': 0.0, 'partner_id': partner.id, '3': 0.0, 'total': 0.0, 'name': 'AgedPartner', '4': 0.0, '2': 0.0}], + 'We should have a line in the report for the partner') + self.assertEqual(len(amls[partner.id]), 2, 'We should have 2 account move lines for the partner') + + positive_line = [line for line in amls[partner.id] if line['line'].balance > 0] + negative_line = [line for line in amls[partner.id] if line['line'].balance < 0] + + self.assertEqual(positive_line[0]['amount'], 50.0, 'The amount of the amls should be 50') + self.assertEqual(negative_line[0]['amount'], -50.0, 'The amount of the amls should be -50') + def test_partial_reconcile_currencies_02(self): #### # Day 1: Invoice Cust/001 to customer (expressed in USD) diff --git a/addons/account_check_printing/models/account_payment.py b/addons/account_check_printing/models/account_payment.py index 15421a6e7c2..51870ebeeb3 100644 --- a/addons/account_check_printing/models/account_payment.py +++ b/addons/account_check_printing/models/account_payment.py @@ -162,12 +162,12 @@ class AccountPayment(models.Model): # Prepare the stub lines if not credits: - stub_lines = [self.make_stub_line(inv) for inv in invoices] + stub_lines = [self._check_make_stub_line(inv) for inv in invoices] else: stub_lines = [{'header': True, 'name': "Bills"}] - stub_lines += [self.make_stub_line(inv) for inv in debits] + stub_lines += [self._check_make_stub_line(inv) for inv in debits] stub_lines += [{'header': True, 'name': "Refunds"}] - stub_lines += [self.make_stub_line(inv) for inv in credits] + stub_lines += [self._check_make_stub_line(inv) for inv in credits] # Crop the stub lines or split them on multiple pages if not multi_stub: diff --git a/addons/account_payment/controllers/payment.py b/addons/account_payment/controllers/payment.py index 7f08570de8e..5c3f6f0ea31 100644 --- a/addons/account_payment/controllers/payment.py +++ b/addons/account_payment/controllers/payment.py @@ -17,7 +17,6 @@ class PaymentPortal(http.Controller): :return html: form containing all values related to the acquirer to redirect customers to the acquirer website """ success_url = kwargs.get('success_url', '/my') - callback_method = kwargs.get('callback_method', '') invoice_sudo = request.env['account.invoice'].sudo().browse(invoice_id) if not invoice_sudo: @@ -28,11 +27,10 @@ class PaymentPortal(http.Controller): except: return False + if request.env.user == request.env.ref('base.public_user'): + save_token = False # we avoid to create a token for the public user vals = { 'acquirer_id': acquirer_id, - 'callback_model_id': request.env['ir.model'].sudo().search([('model', '=', invoice_sudo._name)], limit=1).id, - 'callback_res_id': invoice_sudo.id, - 'callback_method': callback_method, } if save_token: @@ -55,7 +53,6 @@ class PaymentPortal(http.Controller): """ Use a token to perform a s2s transaction """ error_url = kwargs.get('error_url', '/my') success_url = kwargs.get('success_url', '/my') - callback_method = kwargs.get('callback_method', '') access_token = kwargs.get('access_token') params = {} if access_token: @@ -67,18 +64,17 @@ class PaymentPortal(http.Controller): return request.redirect(_build_url_w_params(error_url, params)) try: - pm_id = int(pm_id) + token = request.env['payment.token'].sudo().browse(int(pm_id)) except (ValueError, TypeError): + token = False + token_owner = invoice_sudo.partner_id if request.env.user == request.env.ref('base.public_user') else request.env.user.partner_id + if not token or token.partner_id != token_owner: params['error'] = 'pay_invoice_invalid_token' return request.redirect(_build_url_w_params(error_url, params)) vals = { - 'payment_token_id': pm_id, + 'payment_token_id': token.id, 'type': 'server2server', - 'callback_model_id': request.env['ir.model'].sudo().search([('model', '=', invoice_sudo._name)], - limit=1).id, - 'callback_res_id': invoice_sudo.id, - 'callback_method': callback_method, } invoice_sudo._create_payment_transaction(vals) diff --git a/addons/account_payment/controllers/portal.py b/addons/account_payment/controllers/portal.py index a2247288fdb..28bca2a12a8 100644 --- a/addons/account_payment/controllers/portal.py +++ b/addons/account_payment/controllers/portal.py @@ -9,5 +9,18 @@ class PortalAccount(PortalAccount): def _invoice_get_page_view_values(self, invoice, access_token, **kwargs): values = super(PortalAccount, self)._invoice_get_page_view_values(invoice, access_token, **kwargs) - values.update(request.env['payment.acquirer']._get_available_payment_input(invoice.partner_id, invoice.company_id)) + payment_inputs = request.env['payment.acquirer']._get_available_payment_input(company=invoice.company_id) + # if not connected (using public user), the method _get_available_payment_input will return public user tokens + is_public_user = request.env.ref('base.public_user') == request.env.user + if is_public_user: + # we should not display payment tokens owned by the public user + payment_inputs.pop('pms', None) + token_count = request.env['payment.token'].sudo().search_count([('acquirer_id.company_id', '=', invoice.company_id.id), + ('partner_id', '=', invoice.partner_id.id), + ]) + values['existing_token'] = token_count > 0 + values.update(payment_inputs) + # if the current user is connected we set partner_id to his partner otherwise we set it as the invoice partner + # we do this to force the creation of payment tokens to the correct partner and avoid token linked to the public user + values['partner_id'] = invoice.partner_id if is_public_user else request.env.user.partner_id, return values diff --git a/addons/account_payment/views/account_portal_templates.xml b/addons/account_payment/views/account_portal_templates.xml index 36bab9558ea..0a8de0cc973 100644 --- a/addons/account_payment/views/account_portal_templates.xml +++ b/addons/account_payment/views/account_portal_templates.xml @@ -53,6 +53,11 @@ +
+
+ You have credits card registered, you can log-in to be able to use them. +
+
diff --git a/addons/auth_signup/models/res_users.py b/addons/auth_signup/models/res_users.py index 2dcd9cb373a..598d390dbd5 100644 --- a/addons/auth_signup/models/res_users.py +++ b/addons/auth_signup/models/res_users.py @@ -196,5 +196,5 @@ class ResUsers(models.Model): sup = super(ResUsers, self) if not default or not default.get('email'): # avoid sending email to the user we are duplicating - sup = super(ResUsers, self.with_context(reset_password=False)) + sup = super(ResUsers, self.with_context(no_reset_password=True)) return sup.copy(default=default) diff --git a/addons/barcodes/static/src/js/barcode_parser.js b/addons/barcodes/static/src/js/barcode_parser.js index d834f3cbd27..cf357a438b2 100644 --- a/addons/barcodes/static/src/js/barcode_parser.js +++ b/addons/barcodes/static/src/js/barcode_parser.js @@ -18,6 +18,9 @@ var BarcodeParser = Class.extend({ // only when those data have been loaded load: function(){ var self = this; + if (!this.nomenclature_id) { + return; + } var id = this.nomenclature_id[0]; rpc.query({ model: 'barcode.nomenclature', diff --git a/addons/base_address_extended/__manifest__.py b/addons/base_address_extended/__manifest__.py index c9c78846b26..adb27e8bb90 100644 --- a/addons/base_address_extended/__manifest__.py +++ b/addons/base_address_extended/__manifest__.py @@ -16,7 +16,8 @@ For example, in legal reports, some countries need to split the street into seve with the street name, the house number, and room number. """, 'data': [ - 'views/base_address_extended.xml' + 'views/base_address_extended.xml', + 'data/base_address_extended_data.xml', ], 'depends': ['base'], } diff --git a/addons/base_address_extended/data/base_address_extended_data.xml b/addons/base_address_extended/data/base_address_extended_data.xml new file mode 100644 index 00000000000..1e76cb4c5df --- /dev/null +++ b/addons/base_address_extended/data/base_address_extended_data.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index 6ae096d7eb1..06272c99925 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -4,7 +4,7 @@ import base64 import babel.dates import collections -from datetime import datetime, timedelta +from datetime import datetime, timedelta, MAXYEAR from dateutil import parser from dateutil import rrule from dateutil.relativedelta import relativedelta @@ -633,7 +633,7 @@ class Meeting(models.Model): recurring_date = todate(meeting.recurrent_id_date) rset1.exdate(recurring_date) invalidate = True - return [d.astimezone(pytz.UTC) if d.tzinfo else d for d in rset1] + return [d.astimezone(pytz.UTC) if d.tzinfo else d for d in rset1 if d.year < MAXYEAR] @api.multi def _get_recurrency_end_date(self): @@ -903,8 +903,8 @@ class Meeting(models.Model): startdate = startdate.astimezone(pytz.utc) # Convert to UTC meeting.start = fields.Datetime.to_string(startdate) else: - meeting.start = meeting.start_datetime - meeting.stop = meeting.stop_datetime + meeting.write({'start': meeting.start_datetime, + 'stop': meeting.stop_datetime}) @api.depends('byday', 'recurrency', 'final_date', 'rrule_type', 'month_by', 'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'day', 'week_list') def _compute_rrule(self): diff --git a/addons/calendar/static/src/js/base_calendar.js b/addons/calendar/static/src/js/base_calendar.js index 3752b7aa94a..e09222b6025 100644 --- a/addons/calendar/static/src/js/base_calendar.js +++ b/addons/calendar/static/src/js/base_calendar.js @@ -40,8 +40,10 @@ var CalendarNotification = Notification.extend({ }, 'click .link2showed': function() { - this.destroy(true); - this._rpc({route: '/calendar/notify_ack'}); + var self = this; + this._rpc({route: '/calendar/notify_ack'}).always(function() { + self.destroy(); + }); }, }); }, diff --git a/addons/crm/__init__.py b/addons/crm/__init__.py index 4e9b019bd7a..3398a4b470d 100644 --- a/addons/crm/__init__.py +++ b/addons/crm/__init__.py @@ -5,3 +5,11 @@ from . import controllers from . import models from . import report from . import wizard + +from odoo import api, SUPERUSER_ID + + +def uninstall_hook(cr, registry): + env = api.Environment(cr, SUPERUSER_ID, {}) + teams = env['crm.team'].search([('dashboard_graph_model', '=', 'crm.opportunity.report')]) + teams.update({'dashboard_graph_model': None}) diff --git a/addons/crm/__manifest__.py b/addons/crm/__manifest__.py index e224a6d8101..b09e6c9c70d 100644 --- a/addons/crm/__manifest__.py +++ b/addons/crm/__manifest__.py @@ -54,4 +54,5 @@ 'installable': True, 'application': True, 'auto_install': False, + 'uninstall_hook': 'uninstall_hook', } diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 3ec21c7eb1e..5945470f51c 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -450,7 +450,8 @@ class Lead(models.Model): 'res_id': self.id, 'views': [(form_view.id, 'form'),], 'type': 'ir.actions.act_window', - 'target': 'inline' + 'target': 'inline', + 'context': {'default_type': 'opportunity'} } # ---------------------------------------- diff --git a/addons/crm/wizard/crm_lead_to_opportunity_views.xml b/addons/crm/wizard/crm_lead_to_opportunity_views.xml index 201f2957636..5b29d25b934 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity_views.xml +++ b/addons/crm/wizard/crm_lead_to_opportunity_views.xml @@ -56,8 +56,7 @@ - - +