diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index d22962ebbd0..e03dea98b26 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -618,9 +618,12 @@ class AccountBankStatementLine(models.Model): # Create The payment payment = self.env['account.payment'] + partner_id = self.partner_id or (aml_dict.get('move_line') and aml_dict['move_line'].partner_id) or self.env['res.partner'] if abs(total)>0.00001: payment_vals = self._prepare_payment_vals(total) - if self.partner_id and len(account_types) == 1: + if not payment_vals['partner_id']: + payment_vals['partner_id'] = partner_id.id + if payment_vals['partner_id'] and len(account_types) == 1: payment_vals['partner_type'] = 'customer' if account_types == receivable_account_type else 'supplier' payment = payment.create(payment_vals) diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 6ded10a62cc..141658dc19f 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1583,12 +1583,14 @@ class AccountInvoice(models.Model): fmt = partial(formatLang, invoice.with_context(lang=invoice.partner_id.lang).env, currency_obj=currency) res = {} for line in invoice.tax_line_ids: - res.setdefault(line.tax_id.tax_group_id, {'base': 0.0, 'amount': 0.0}) - res[line.tax_id.tax_group_id]['amount'] += line.amount_total - res[line.tax_id.tax_group_id]['base'] += line.base - res = sorted(res.items(), key=lambda l: l[0].sequence) + tax = line.tax_id + group_key = (tax.tax_group_id, tax.amount_type, tax.amount) + res.setdefault(group_key, {'base': 0.0, 'amount': 0.0}) + res[group_key]['amount'] += line.amount_total + res[group_key]['base'] += line.base + res = sorted(res.items(), key=lambda l: l[0][0].sequence) invoice.amount_by_group = [( - r[0].name, r[1]['amount'], r[1]['base'], + r[0][0].name, r[1]['amount'], r[1]['base'], fmt(r[1]['amount']), fmt(r[1]['base']), len(res), ) for r in res] diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index eeaca15fae8..7d19678ebcf 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -169,7 +169,7 @@ class account_journal(models.Model): LEFT JOIN account_move move ON aml.move_id = move.id WHERE aml.account_id in %%s AND move.date <= %%s AND move.state = 'posted';""" % (amount_field,) - self.env.cr.execute(query, (account_ids, fields.Date.today(),)) + self.env.cr.execute(query, (account_ids, fields.Date.context_today(self),)) query_results = self.env.cr.dictfetchall() if query_results and query_results[0].get('sum') != None: account_sum = query_results[0].get('sum') diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index 7dffbaa2233..f4cb84a3531 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -236,10 +236,14 @@ class account_abstract_payment(models.AbstractModel): currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id or invoices and invoices[0].currency_id # Avoid currency rounding issues by summing the amounts according to the company_currency_id before + invoice_datas = invoices.read_group( + [('id', 'in', invoices.ids)], + ['currency_id', 'type', 'residual_signed'], + ['currency_id', 'type'], lazy=False) total = 0.0 - groups = groupby(invoices, lambda i: i.currency_id) - for payment_currency, payment_invoices in groups: - amount_total = sum([MAP_INVOICE_TYPE_PAYMENT_SIGN[i.type] * i.residual_signed for i in payment_invoices]) + for invoice_data in invoice_datas: + amount_total = MAP_INVOICE_TYPE_PAYMENT_SIGN[invoice_data['type']] * invoice_data['residual_signed'] + payment_currency = self.env['res.currency'].browse(invoice_data['currency_id'][0]) if payment_currency == currency: total += amount_total else: diff --git a/addons/account/models/reconciliation_widget.py b/addons/account/models/reconciliation_widget.py index df92a5b45f1..0db2460b478 100644 --- a/addons/account/models/reconciliation_widget.py +++ b/addons/account/models/reconciliation_widget.py @@ -134,11 +134,12 @@ class AccountReconciliation(models.AbstractModel): else: aml_ids = matching_amls[line.id]['aml_ids'] bank_statements_left += line.statement_id + target_currency = line.currency_id or line.journal_id.currency_id or line.journal_id.company_id.currency_id amls = aml_ids and self.env['account.move.line'].browse(aml_ids) line_vals = { 'st_line': self._get_statement_line(line), - 'reconciliation_proposition': aml_ids and self._prepare_move_lines(amls) or [], + 'reconciliation_proposition': aml_ids and self._prepare_move_lines(amls, target_currency=target_currency, target_date=line.date) or [], 'model_id': matching_amls[line.id].get('model') and matching_amls[line.id]['model'].id, 'write_off': matching_amls[line.id].get('status') == 'write_off', } @@ -453,21 +454,12 @@ class AccountReconciliation(models.AbstractModel): ('payment_id', '<>', False) ] - # Black lines = unreconciled & (not linked to a payment or open balance created by statement - domain_matching = [('reconciled', '=', False)] - if partner_id: - domain_matching = expression.AND([ - domain_matching, - [('account_id.internal_type', 'in', ['payable', 'receivable'])] - ]) - else: - # TODO : find out what use case this permits (match a check payment, registered on a journal whose account type is other instead of liquidity) - domain_matching = expression.AND([ - domain_matching, - [('account_id.reconcile', '=', True)] - ]) + # default domain matching + domain_matching = expression.AND([ + [('reconciled', '=', False)], + [('account_id.reconcile', '=', True)] + ]) - # Let's add what applies to both domain = expression.OR([domain_reconciliation, domain_matching]) if partner_id: domain = expression.AND([domain, [('partner_id', '=', partner_id)]]) diff --git a/addons/account/tests/test_reconciliation_widget.py b/addons/account/tests/test_reconciliation_widget.py index 5d2dc637ed7..6edb1fa108b 100644 --- a/addons/account/tests/test_reconciliation_widget.py +++ b/addons/account/tests/test_reconciliation_widget.py @@ -1,6 +1,7 @@ import logging import odoo.tests import time +from odoo.addons.account.tests.test_reconciliation import TestReconciliation _logger = logging.getLogger(__name__) @@ -22,3 +23,29 @@ class TestUi(odoo.tests.HttpCase): self.phantom_js("/web#statement_ids=" + str(bank_stmt.id) + "&action=bank_statement_reconciliation_view", "odoo.__DEBUG__.services['web_tour.tour'].run('bank_statement_reconciliation')", "odoo.__DEBUG__.services['web_tour.tour'].tours.bank_statement_reconciliation.ready", login="admin") + + +@odoo.tests.tagged('post_install', '-at_install') +class TestReconciliationWidget(TestReconciliation): + + def test_statement_suggestion_other_currency(self): + # company currency is EUR + # payment in USD + invoice = self.create_invoice(invoice_amount=50, currency_id=self.currency_usd_id) + + # journal currency in USD + bank_stmt = self.acc_bank_stmt_model.create({ + 'journal_id': self.bank_journal_usd.id, + 'date': time.strftime('%Y-07-15'), + 'name': 'payment %s' % invoice.number, + }) + + bank_stmt_line = self.acc_bank_stmt_line_model.create({'name': 'payment', + 'statement_id': bank_stmt.id, + 'partner_id': self.partner_agrolait_id, + 'amount': 50, + 'date': time.strftime('%Y-07-15'), + }) + + result = self.env['account.reconciliation.widget'].get_bank_statement_line_data(bank_stmt_line.ids) + self.assertEqual(result['lines'][0]['reconciliation_proposition'][0]['amount_str'], '$ 50.00') \ No newline at end of file diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml index 7837959615f..70d7c869d8f 100644 --- a/addons/account/views/account_payment_view.xml +++ b/addons/account/views/account_payment_view.xml @@ -71,7 +71,7 @@
- +
diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index 80d2d4e589e..4335e976826 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -1545,7 +1545,7 @@
- +
diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml index 75140895390..bd55957c7c1 100644 --- a/addons/account/views/report_invoice.xml +++ b/addons/account/views/report_invoice.xml @@ -84,7 +84,7 @@ - + diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py index 5703e018548..7b8e176c9ac 100644 --- a/addons/auth_signup/models/res_partner.py +++ b/addons/auth_signup/models/res_partner.py @@ -27,16 +27,16 @@ class ResPartner(models.Model): signup_token = fields.Char(copy=False, groups="base.group_erp_manager") signup_type = fields.Char(string='Signup Token Type', copy=False, groups="base.group_erp_manager") signup_expiration = fields.Datetime(copy=False, groups="base.group_erp_manager") - signup_valid = fields.Boolean(compute='_compute_signup_valid', compute_sudo=True, string='Signup Token is Valid') + signup_valid = fields.Boolean(compute='_compute_signup_valid', string='Signup Token is Valid') signup_url = fields.Char(compute='_compute_signup_url', string='Signup URL') @api.multi @api.depends('signup_token', 'signup_expiration') def _compute_signup_valid(self): dt = now() - for partner in self: - partner.signup_valid = bool(partner.signup_token) and \ - (not partner.signup_expiration or dt <= partner.signup_expiration) + for partner, partner_sudo in zip(self, self.sudo()): + partner.signup_valid = bool(partner_sudo.signup_token) and \ + (not partner_sudo.signup_expiration or dt <= partner_sudo.signup_expiration) @api.multi def _compute_signup_url(self): diff --git a/addons/base_automation/data/base_automation_demo.xml b/addons/base_automation/data/base_automation_demo.xml index 246320ecf4a..5ffdc94d6a3 100644 --- a/addons/base_automation/data/base_automation_demo.xml +++ b/addons/base_automation/data/base_automation_demo.xml @@ -71,4 +71,19 @@ if 'user_id' in env.context['old_values'][record.id]: + + Template Automation + + <div>Email automation</div> + + + Base Automation: test send an email + + + email + on_write + + [('deadline', '!=', False)] + [('deadline', '=', False)] + diff --git a/addons/base_automation/models/base_automation.py b/addons/base_automation/models/base_automation.py index ac3e6305c6b..a482d7d5911 100644 --- a/addons/base_automation/models/base_automation.py +++ b/addons/base_automation/models/base_automation.py @@ -161,14 +161,17 @@ class BaseAutomation(models.Model): return records def _filter_post(self, records): + return self._filter_post_export_domain(records)[0] + + def _filter_post_export_domain(self, records): """ Filter the records that satisfy the postcondition of action ``self``. """ if self.filter_domain and records: domain = [('id', 'in', records.ids)] + safe_eval(self.filter_domain, self._get_eval_context()) - return records.search(domain) + return records.search(domain), domain else: - return records + return records, None - def _process(self, records): + def _process(self, records, domain_post=None): """ Process action ``self`` on the ``records`` that have not been done yet. """ # filter out the records on which self has already been done action_done = self._context['__action_done'] @@ -193,7 +196,12 @@ class BaseAutomation(models.Model): # execute server actions if self.action_server_id: for record in records: - ctx = {'active_model': record._name, 'active_ids': record.ids, 'active_id': record.id} + ctx = { + 'active_model': record._name, + 'active_ids': record.ids, + 'active_id': record.id, + 'domain_post': domain_post, + } self.action_server_id.with_context(**ctx).run() @api.model_cr @@ -247,7 +255,8 @@ class BaseAutomation(models.Model): _write.origin(records, vals, **kw) # check postconditions, and execute actions on the records that satisfy them for action in actions.with_context(old_values=old_values): - action._process(action._filter_post(pre[action])) + records, domain_post = action._filter_post_export_domain(pre[action]) + action._process(records, domain_post=domain_post) return True return _write diff --git a/addons/base_automation/tests/test_base_automation.py b/addons/base_automation/tests/test_base_automation.py index d71423f4e69..c3fcf6e7ad5 100644 --- a/addons/base_automation/tests/test_base_automation.py +++ b/addons/base_automation/tests/test_base_automation.py @@ -1,5 +1,6 @@ # # -*- coding: utf-8 -*- # # Part of Odoo. See LICENSE file for full copyright and licensing details. +from unittest.mock import patch from odoo.tests import common @@ -9,13 +10,14 @@ class base_automation_test(common.TransactionCase): def setUp(self): super(base_automation_test, self).setUp() - self.user_admin = self.env.ref('base.user_root') + self.user_root = self.env.ref('base.user_root') + self.user_admin = self.env.ref('base.user_admin') self.user_demo = self.env.ref('base.user_demo') def create_lead(self, **kwargs): vals = { 'name': "Lead Test", - 'user_id': self.user_admin.id, + 'user_id': self.user_root.id, } vals.update(kwargs) return self.env['base.automation.lead.test'].create(vals) @@ -27,7 +29,7 @@ class base_automation_test(common.TransactionCase): """ lead = self.create_lead(state='open') self.assertEqual(lead.state, 'open') - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not change on creation of Lead with state 'open'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.") def test_01_check_to_state_draft_post(self): """ @@ -49,15 +51,15 @@ class base_automation_test(common.TransactionCase): """ lead = self.create_lead(state='open') self.assertEqual(lead.state, 'open', "Lead state should be 'open'") - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not change on creation of Lead with state 'open'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.") # change state to pending and check that responsible has not changed lead.write({'state': 'pending'}) self.assertEqual(lead.state, 'pending', "Lead state should be 'pending'") - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.") # change state to done and check that responsible has not changed lead.write({'state': 'done'}) self.assertEqual(lead.state, 'done', "Lead state should be 'done'") - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not chang on creation of Lead with state from 'pending' to 'done'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not chang on creation of Lead with state from 'pending' to 'done'.") def test_03_check_from_draft_to_done_without_steps(self): """ @@ -70,7 +72,7 @@ class base_automation_test(common.TransactionCase): """ lead = self.create_lead(state='open') self.assertEqual(lead.state, 'open', "Lead state should be 'open'") - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not change on creation of Lead with state 'open'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.") # change state to done and check that responsible has changed lead.write({'state': 'done'}) self.assertEqual(lead.state, 'done', "Lead state should be 'done'") @@ -85,7 +87,7 @@ class base_automation_test(common.TransactionCase): partner.write({'customer': False}) lead = self.create_lead(state='open', partner_id=partner.id) self.assertFalse(lead.customer, "Customer field should updated to False") - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.") # change partner, recompute on lead should trigger the rule partner.write({'customer': True}) self.assertTrue(lead.customer, "Customer field should updated to True") @@ -101,15 +103,52 @@ class base_automation_test(common.TransactionCase): self.assertFalse(lead.deadline, 'There should not be a deadline defined') # change priority and user; this triggers deadline recomputation, and # the server action should set the boolean field to True - lead.write({'priority': True, 'user_id': self.user_admin.id}) + lead.write({'priority': True, 'user_id': self.user_root.id}) self.assertTrue(lead.deadline, 'Deadline should be defined') self.assertTrue(lead.is_assigned_to_admin, 'Lead should be assigned to admin') + def test_11b_recomputed_field(self): + mail_automation = self.env.ref('base_automation.test_rule_on_write_recompute_send_email') + send_mail_count = 0 + + def _patched_get_actions(*args, **kwargs): + obj = args[0] + if '__action_done' not in obj._context: + obj = obj.with_context(__action_done={}) + return mail_automation.with_env(obj.env) + + def _patched_send_mail(*args, **kwargs): + nonlocal send_mail_count + send_mail_count += 1 + + patchers = [ + patch('odoo.addons.base_automation.models.base_automation.BaseAutomation._get_actions', _patched_get_actions), + patch('odoo.addons.mail.models.mail_template.MailTemplate.send_mail', _patched_send_mail), + ] + + patchers[0].start() + + lead = self.create_lead() + self.assertFalse(lead.priority) + self.assertFalse(lead.deadline) + + patchers[1].start() + + lead.write({'priority': True}) + + self.assertTrue(lead.priority) + self.assertTrue(lead.deadline) + + for patcher in patchers: + patcher.stop() + + self.assertEqual(send_mail_count, 1) + def test_12_recursive(self): """ Check that a rule is executed recursively by a secondary change. """ lead = self.create_lead(state='open') self.assertEqual(lead.state, 'open') - self.assertEqual(lead.user_id, self.user_admin) + self.assertEqual(lead.user_id, self.user_root) # change partner; this should trigger the rule that modifies the state partner = self.env.ref('base.res_partner_1') lead.write({'partner_id': partner.id}) diff --git a/addons/base_import/static/csv/o2m_customers_contacts.csv b/addons/base_import/static/csv/o2m_customers_contacts.csv index bf142142d3c..d29a600f18b 100644 --- a/addons/base_import/static/csv/o2m_customers_contacts.csv +++ b/addons/base_import/static/csv/o2m_customers_contacts.csv @@ -1,9 +1,9 @@ Name,Is a company,Related company,Address type,Customer,Supplier,Street,ZIP,City,State,Country -Aurora Shelves,1,,,1,0,25 Pacific Road,95101,San José,CA,United States -Roger Martins,0,Aurora Shelves,Invoice address,1,0,27 Pacific Road,95102,San José,CA,United States -House Sales Direct,1,,,1,0,104 Saint Mary Avenue,94059,Redwood,CA,United States -Yvan Holiday,0,House Sales Direct,Contact,1,0,104 Saint Mary Avenue,94060,Redwood,CA,United States -Jack Unsworth,0,House Sales Direct,Invoice address,1,0,227 Jackson Road,94061,Redwood,CA,United States -Michael Mason,0,,,1,0,16 5th Avenue,94104,San Francisco,CA,United States -International Wood,1,,,1,0,748 White House Boulevard,20004,Washington,DC,United States -Sharon Pecker,0,International Wood,Invoice address,1,0,755 White House Boulevard,20005,Washington,DC,United States +Aurora Shelves,1,,,1,0,25 Pacific Road,95101,San José,California,United States +Roger Martins,0,Aurora Shelves,Invoice address,1,0,27 Pacific Road,95102,San José,California,United States +House Sales Direct,1,,,1,0,104 Saint Mary Avenue,94059,Redwood,California,United States +Yvan Holiday,0,House Sales Direct,Contact,1,0,104 Saint Mary Avenue,94060,Redwood,California,United States +Jack Unsworth,0,House Sales Direct,Invoice address,1,0,227 Jackson Road,94061,Redwood,California,United States +Michael Mason,0,,,1,0,16 5th Avenue,94104,San Francisco,California,United States +International Wood,1,,,1,0,748 White House Boulevard,20004,Washington,D.C.,United States +Sharon Pecker,0,International Wood,Invoice address,1,0,755 White House Boulevard,20005,Washington,D.C.,United States diff --git a/addons/bus/static/src/js/longpolling_bus.js b/addons/bus/static/src/js/longpolling_bus.js index a24dcb593fd..750db65eba2 100644 --- a/addons/bus/static/src/js/longpolling_bus.js +++ b/addons/bus/static/src/js/longpolling_bus.js @@ -32,31 +32,34 @@ var LongpollingBus = Bus.extend(ServicesMixin, { init: function (parent, params) { this._super.apply(this, arguments); this._id = _.uniqueId('bus'); + + // the _id is modified by crosstab_bus, so we can't use it to unbind the events in the destroy. + this._longPollingBusId = this._id; this._options = {}; this._channels = []; // bus presence this._lastPresenceTime = new Date().getTime(); this._lastPartnersPresenceCheck = this._lastPresenceTime; - $(window).on("focus." + this._id, this._onFocusChange.bind(this, {focus: true})); - $(window).on("blur." + this._id, this._onFocusChange.bind(this, {focus: false})); - $(window).on("unload." + this._id, this._onFocusChange.bind(this, {focus: false})); + $(window).on("focus." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: true})); + $(window).on("blur." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: false})); + $(window).on("unload." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: false})); - $(window).on("click." + this._id, this._onPresence.bind(this)); - $(window).on("keydown." + this._id, this._onPresence.bind(this)); - $(window).on("keyup." + this._id, this._onPresence.bind(this)); + $(window).on("click." + this._longPollingBusId, this._onPresence.bind(this)); + $(window).on("keydown." + this._longPollingBusId, this._onPresence.bind(this)); + $(window).on("keyup." + this._longPollingBusId, this._onPresence.bind(this)); }, /** * @override */ destroy: function () { this.stopPolling(); - $(window).off("focus." + this._id); - $(window).off("blur." + this._id); - $(window).off("unload." + this._id); - $(window).off("click." + this._id); - $(window).off("keydown." + this._id); - $(window).off("keyup." + this._id); + $(window).off("focus." + this._longPollingBusId); + $(window).off("blur." + this._longPollingBusId); + $(window).off("unload." + this._longPollingBusId); + $(window).off("click." + this._longPollingBusId); + $(window).off("keydown." + this._longPollingBusId); + $(window).off("keyup." + this._longPollingBusId); this._super(); }, //-------------------------------------------------------------------------- diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py index 1b15093de03..fac1730bead 100644 --- a/addons/event_sale/models/sale_order.py +++ b/addons/event_sale/models/sale_order.py @@ -73,6 +73,11 @@ class SaleOrderLine(models.Model): if self.event_ticket_id and (not self.event_id or self.event_id != self.event_ticket_id.event_id): self.event_ticket_id = None + @api.onchange('product_uom', 'product_uom_qty') + def product_uom_change(self): + if not self.event_ticket_id: + super(SaleOrderLine, self).product_uom_change() + @api.onchange('event_ticket_id') def _onchange_event_ticket_id(self): company = self.event_id.company_id or self.env.user.company_id diff --git a/addons/gamification/models/badge.py b/addons/gamification/models/badge.py index 6686b29fee5..9997b443d33 100644 --- a/addons/gamification/models/badge.py +++ b/addons/gamification/models/badge.py @@ -231,7 +231,7 @@ class GamificationBadge(models.Model): elif self.rule_auth == 'users' and self.env.user not in self.rule_auth_user_ids: return self.USER_NOT_VIP elif self.rule_auth == 'having': - all_user_badges = self.env['gamification.badge.user'].search([('user_id', '=', self.env.uid)]) + all_user_badges = self.env['gamification.badge.user'].search([('user_id', '=', self.env.uid)]).mapped('badge_id') if self.rule_auth_badge_ids - all_user_badges: return self.BADGE_REQUIRED diff --git a/addons/hr_fleet/controllers/i18n/az.po b/addons/hr_fleet/controllers/i18n/az.po deleted file mode 100644 index 23fa2458e4d..00000000000 --- a/addons/hr_fleet/controllers/i18n/az.po +++ /dev/null @@ -1,115 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * crm_project -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server saas~11.5\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-09-21 13:17+0000\n" -"PO-Revision-Date: 2018-08-24 09:17+0000\n" -"Language-Team: Azerbaijani (https://www.transifex.com/odoo/teams/41243/az/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Language: az\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_view_form -msgid "" -"Convert To\n" -" Task" -msgstr "" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -msgid "Cancel" -msgstr "" - -#. module: crm_project -#: model:ir.actions.act_window,name:crm_project.crm_lead_convert2task_action -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_view_form -msgid "Convert to Task" -msgstr "" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -msgid "Create Task" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Create a new customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__create_uid -msgid "Created by" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__create_date -msgid "Created on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__partner_id -msgid "Customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__display_name -msgid "Display Name" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Do not link to a customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__id -msgid "ID" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task____last_update -msgid "Last Modified on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__write_uid -msgid "Last Updated by" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__write_date -msgid "Last Updated on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__lead_id -msgid "Lead" -msgstr "" - -#. module: crm_project -#: model:ir.model,name:crm_project.model_crm_lead_convert2task -msgid "Lead convert to Task" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Link to an existing customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__project_id -msgid "Project" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__action -msgid "Related Customer" -msgstr "" diff --git a/addons/hr_fleet/controllers/i18n/fil.po b/addons/hr_fleet/controllers/i18n/fil.po deleted file mode 100644 index f3c56bf488c..00000000000 --- a/addons/hr_fleet/controllers/i18n/fil.po +++ /dev/null @@ -1,115 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * crm_project -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server saas~11.5\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-09-21 13:17+0000\n" -"PO-Revision-Date: 2018-08-24 09:17+0000\n" -"Language-Team: Filipino (https://www.transifex.com/odoo/teams/41243/fil/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Language: fil\n" -"Plural-Forms: nplurals=2; plural=(n == 1 || n==2 || n==3) || (n % 10 != 4 || n % 10 != 6 || n % 10 != 9);\n" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_view_form -msgid "" -"Convert To\n" -" Task" -msgstr "" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -msgid "Cancel" -msgstr "" - -#. module: crm_project -#: model:ir.actions.act_window,name:crm_project.crm_lead_convert2task_action -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_view_form -msgid "Convert to Task" -msgstr "" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -msgid "Create Task" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Create a new customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__create_uid -msgid "Created by" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__create_date -msgid "Created on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__partner_id -msgid "Customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__display_name -msgid "Display Name" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Do not link to a customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__id -msgid "ID" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task____last_update -msgid "Last Modified on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__write_uid -msgid "Last Updated by" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__write_date -msgid "Last Updated on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__lead_id -msgid "Lead" -msgstr "" - -#. module: crm_project -#: model:ir.model,name:crm_project.model_crm_lead_convert2task -msgid "Lead convert to Task" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Link to an existing customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__project_id -msgid "Project" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__action -msgid "Related Customer" -msgstr "" diff --git a/addons/hr_gamification/wizard/gamification_badge_user_wizard.py b/addons/hr_gamification/wizard/gamification_badge_user_wizard.py index 4e486a0e048..7c4c174129f 100644 --- a/addons/hr_gamification/wizard/gamification_badge_user_wizard.py +++ b/addons/hr_gamification/wizard/gamification_badge_user_wizard.py @@ -10,7 +10,7 @@ class GamificationBadgeUserWizard(models.TransientModel): employee_id = fields.Many2one('hr.employee', string='Employee', required=True) user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', - store=False, readonly=True) + store=False, readonly=True, compute_sudo=True) @api.multi def action_grant_badge(self): diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 9d24ea47916..cded512698d 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -111,13 +111,12 @@ class HolidaysRequest(models.Model): "\nThe status is 'Approved', when leave request is approved by manager.") payslip_status = fields.Boolean('Reported in last payslips', help='Green this button when the leave has been taken into account in the payslip.') report_note = fields.Text('HR Comments') - user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True) + user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, compute_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True) # leave type configuration holiday_status_id = fields.Many2one( "hr.leave.type", string="Leave Type", required=True, readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, domain=[('valid', '=', True)]) - leave_type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True) validation_type = fields.Selection('Validation Type', related='holiday_status_id.validation_type', readonly=False) # HR data employee_id = fields.Many2one( diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index 5c128df5af3..840537796e4 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -252,7 +252,7 @@ class HolidaysAllocation(models.Model): if self.env.user.employee_ids: self.department_id = self.department_id or self.env.user.employee_ids[0].department_id self.employee_id = None - elif self.holiday_type == 'category': + else: self.employee_id = None self.department_id = None diff --git a/addons/hr_holidays/report/hr_leave_reports.xml b/addons/hr_holidays/report/hr_leave_reports.xml index f818b8fbec6..94502e269ea 100644 --- a/addons/hr_holidays/report/hr_leave_reports.xml +++ b/addons/hr_holidays/report/hr_leave_reports.xml @@ -77,7 +77,7 @@
- +
diff --git a/addons/hr_holidays/tests/test_leave_requests.py b/addons/hr_holidays/tests/test_leave_requests.py index 8dad86eae39..e6351430615 100644 --- a/addons/hr_holidays/tests/test_leave_requests.py +++ b/addons/hr_holidays/tests/test_leave_requests.py @@ -138,7 +138,7 @@ class TestLeaveRequests(TestHrHolidaysBase): 'number_of_days': 1, }) - @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail', 'odoo.models') def test_accrual_validity_time_not_valid(self): """ Employee ask leav during a not valid validity time """ with self.assertRaises(ValidationError): diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py index c606ce0972c..2215764fdf4 100644 --- a/addons/l10n_fr_fec/wizard/account_fr_fec.py +++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py @@ -76,6 +76,27 @@ class AccountFrFec(models.TransientModel): listrow = list(row) return listrow + def _get_company_legal_data(self, company): + """ + Dom-Tom are excluded from the EU's fiscal territory + Those regions do not have SIREN + sources: + https://www.service-public.fr/professionnels-entreprises/vosdroits/F23570 + http://www.douane.gouv.fr/articles/a11024-tva-dans-les-dom + """ + dom_tom_group = self.env.ref('l10n_fr.dom-tom') + is_dom_tom = company.country_id.code in dom_tom_group.country_ids.mapped('code') + if not is_dom_tom and not company.vat: + raise Warning( + _("Missing VAT number for company %s") % company.name) + if not is_dom_tom and company.vat[0:2] != 'FR': + raise Warning( + _("FEC is for French companies only !")) + + return { + 'siren': company.vat[4:13] if not is_dom_tom else '', + } + @api.multi def generate_fec(self): self.ensure_one() @@ -87,6 +108,9 @@ class AccountFrFec(models.TransientModel): # 2) CSV files are easier to read/use for a regular accountant. # So it will be easier for the accountant to check the file before # sending it to the fiscal administration + company = self.env.user.company_id + company_legal_data = self._get_company_legal_data(company) + header = [ u'JournalCode', # 0 u'JournalLib', # 1 @@ -108,18 +132,7 @@ class AccountFrFec(models.TransientModel): u'Idevise', # 17 ] - company = self.env.user.company_id - if not company.vat: - raise Warning( - _("Missing VAT number for company %s") % company.name) - if company.vat[0:2] != 'FR': - raise Warning( - _("FEC is for French companies only !")) - - fecfile = io.BytesIO() - w = pycompat.csv_writer(fecfile, delimiter='|') - w.writerow(header) - + rows_to_write = [header] # INITIAL BALANCE unaffected_earnings_xml_ref = self.env.ref('account.data_unaffected_earnings') unaffected_earnings_line = True # used to make sure that we add the unaffected earning initial balance only once @@ -195,7 +208,8 @@ class AccountFrFec(models.TransientModel): else: listrow[11] = '0,00' listrow[12] = str(-listrow_amount).replace('.', ',') - w.writerow(listrow) + rows_to_write.append(listrow) + #if the unaffected earnings account wasn't in the selection yet: add it manually if (not unaffected_earnings_line and unaffected_earnings_results @@ -206,7 +220,7 @@ class AccountFrFec(models.TransientModel): if unaffected_earnings_account: unaffected_earnings_results[4] = unaffected_earnings_account.code unaffected_earnings_results[5] = unaffected_earnings_account.name - w.writerow(unaffected_earnings_results) + rows_to_write.append(unaffected_earnings_results) # INITIAL BALANCE - receivable/payable sql_query = ''' @@ -264,7 +278,7 @@ class AccountFrFec(models.TransientModel): for row in self._cr.fetchall(): listrow = list(row) account_id = listrow.pop() - w.writerow(listrow) + rows_to_write.append(listrow) # LINES sql_query = ''' @@ -329,20 +343,19 @@ class AccountFrFec(models.TransientModel): sql_query, (self.date_from, self.date_to, company.id)) for row in self._cr.fetchall(): - w.writerow(list(row)) + rows_to_write.append(list(row)) - siren = company.vat[4:13] + fecvalue = self._csv_write_rows(rows_to_write) end_date = fields.Date.to_string(self.date_to).replace('-', '') suffix = '' if self.export_type == "nonofficial": suffix = '-NONOFFICIAL' - fecvalue = fecfile.getvalue() + self.write({ 'fec_data': base64.encodestring(fecvalue), # Filename = FECYYYYMMDD where YYYMMDD is the closing date - 'filename': '%sFEC%s%s.csv' % (siren, end_date, suffix), + 'filename': '%sFEC%s%s.csv' % (company_legal_data['siren'], end_date, suffix), }) - fecfile.close() action = { 'name': 'FEC', @@ -351,3 +364,29 @@ class AccountFrFec(models.TransientModel): 'target': 'self', } return action + + def _csv_write_rows(self, rows, lineterminator=u'\r\n'): + """ + Write FEC rows into a file + It seems that Bercy's bureaucracy is not too happy about the + empty new line at the End Of File. + + @param {list(list)} rows: the list of rows. Each row is a list of strings + @param {unicode string} [optional] lineterminator: effective line terminator + Has nothing to do with the csv writer parameter + The last line written won't be terminated with it + + @return the value of the file + """ + fecfile = io.BytesIO() + writer = pycompat.csv_writer(fecfile, delimiter='|', lineterminator='') + + rows_length = len(rows) + for i, row in enumerate(rows): + if not i == rows_length - 1: + row.append(lineterminator) + writer.writerow(row) + + fecvalue = fecfile.getvalue() + fecfile.close() + return fecvalue diff --git a/addons/l10n_multilang/models/l10n_multilang.py b/addons/l10n_multilang/models/l10n_multilang.py index 4bde8505d5c..08ecb01a665 100644 --- a/addons/l10n_multilang/models/l10n_multilang.py +++ b/addons/l10n_multilang/models/l10n_multilang.py @@ -87,11 +87,20 @@ class AccountChartTemplate(models.Model): return self.process_translations(langs, field, in_ids, out_ids) def _get_template_from_model(self, company_id, model): + """ Find the records and their matching template """ + # genereated records have an external id with the format _