summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-03-29 18:10:57 +0100
committerChristophe Simonis <chs@odoo.com>2019-03-29 18:10:57 +0100
commit2a06f4dcf3ab03d3d0b6ed14d6ccd12140063b27 (patch)
treec3bf62aedae655cdaddb61f6b86703c2a1d9448d
parent1a5ab9265425934a717fd6d57260146a524c208a (diff)
parent09fb2469b490d48ce937a41a63447e099b93c637 (diff)
[MERGE] forward port branch 12.0 up to 09fb2469b49
-rw-r--r--addons/account/models/account_invoice.py27
-rw-r--r--addons/account/models/account_journal_dashboard.py4
-rw-r--r--addons/account/models/account_move.py2
-rw-r--r--addons/account/models/account_payment.py4
-rw-r--r--addons/account/models/account_reconcile_model.py12
-rw-r--r--addons/account/tests/test_reconciliation.py61
-rw-r--r--addons/account/tests/test_reconciliation_matching_rules.py61
-rw-r--r--addons/account/views/report_invoice.xml39
-rw-r--r--addons/account_check_printing/models/account_payment.py2
-rw-r--r--addons/bus/models/bus.py10
-rw-r--r--addons/delivery/models/stock_picking.py2
-rw-r--r--addons/hr_holidays/models/hr.py3
-rw-r--r--addons/hr_holidays/models/hr_leave_allocation.py9
-rw-r--r--addons/hw_escpos/escpos/constants.py10
-rw-r--r--addons/im_livechat/models/mail_channel.py1
-rw-r--r--addons/l10n_fr/data/account_tax_data.xml2
-rw-r--r--addons/l10n_nl/data/account.account.template.csv3
-rw-r--r--addons/l10n_nl/data/account_fiscal_position_account_template.xml27
-rw-r--r--addons/l10n_nl/data/account_fiscal_position_tax_template.xml41
-rw-r--r--addons/l10n_nl/data/account_fiscal_position_template.xml68
-rw-r--r--addons/mass_mailing/models/mass_mailing.py4
-rw-r--r--addons/mrp/report/mrp_report_bom_structure.py4
-rw-r--r--addons/payment/models/account_payment.py5
-rw-r--r--addons/payment_buckaroo/tests/test_buckaroo.py4
-rw-r--r--addons/payment_paypal/tests/test_paypal.py3
-rw-r--r--addons/product/models/product_template.py6
-rw-r--r--addons/project/data/project_data.xml10
-rw-r--r--addons/purchase/models/account_invoice.py7
-rw-r--r--addons/purchase/models/product.py8
-rw-r--r--addons/purchase_stock/report/purchase_report_templates.xml42
-rw-r--r--addons/repair/models/repair.py38
-rw-r--r--addons/sale/report/sale_report_templates.xml39
-rw-r--r--addons/sale/views/sale_portal_templates.xml41
-rw-r--r--addons/stock/wizard/stock_warn_insufficient_qty.py4
-rw-r--r--addons/stock/wizard/stock_warn_insufficient_qty_views.xml3
-rw-r--r--addons/web/static/src/js/core/dialog.js10
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_model.js6
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js61
-rw-r--r--addons/web/static/tests/views/calendar_tests.js54
-rw-r--r--addons/website/static/src/scss/website.backend.scss1
-rw-r--r--addons/website_blog/views/website_blog_templates.xml4
-rw-r--r--addons/website_sale/data/data.xml2
-rw-r--r--addons/website_sale/models/sale_order.py3
-rw-r--r--odoo/addons/base/models/ir_actions_report.py8
-rw-r--r--odoo/addons/test_translation_import/tests/test_term_count.py60
-rw-r--r--odoo/tools/translate.py10
46 files changed, 602 insertions, 223 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index aee35e460a6..02a34a6d518 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
+from collections import OrderedDict
import json
import re
import uuid
@@ -517,10 +518,7 @@ class AccountInvoice(models.Model):
if not vals.get('journal_id') and vals.get('type'):
vals['journal_id'] = self.with_context(type=vals.get('type'))._default_journal().id
- onchanges = {
- '_onchange_partner_id': ['account_id', 'payment_term_id', 'fiscal_position_id', 'partner_bank_id'],
- '_onchange_journal_id': ['currency_id'],
- }
+ onchanges = self._get_onchange_create()
for onchange_method, changed_fields in onchanges.items():
if any(f not in vals for f in changed_fields):
invoice = self.new(vals)
@@ -658,7 +656,7 @@ class AccountInvoice(models.Model):
"""
# Split `From` and `CC` email address from received email to look for related partners to subscribe on the invoice
subscribed_emails = email_split((msg_dict.get('from') or '') + ',' + (msg_dict.get('cc') or ''))
- subscribed_partner_ids = [pid for pid in self._find_partner_from_emails(subscribed_emails) if pid]
+ seen_partner_ids = [pid for pid in self._find_partner_from_emails(subscribed_emails) if pid]
# Detection of the partner_id of the invoice:
# 1) check if the email_from correspond to a supplier
@@ -682,7 +680,7 @@ class AccountInvoice(models.Model):
# If the partner_id can be found, subscribe it to the bill, otherwise it's left empty to be manually filled
if partner_id:
- subscribed_partner_ids.append(partner_id)
+ seen_partner_ids.append(partner_id)
# Find the right purchase journal based on the "TO" email address
destination_emails = email_split((msg_dict.get('to') or '') + ',' + (msg_dict.get('cc') or ''))
@@ -698,9 +696,13 @@ class AccountInvoice(models.Model):
# Passing `type` in context so that _default_journal(...) can correctly set journal for new vendor bill
invoice = super(AccountInvoice, self.with_context(type=values.get('type'))).message_new(msg_dict, values)
- # Subscribe people on the newly created bill
- if subscribed_partner_ids:
- invoice.message_subscribe(subscribed_partner_ids)
+ # Subscribe internal users on the newly created bill
+ partners = self.env['res.partner'].browse(seen_partner_ids)
+ is_internal = lambda p: (p.user_ids and
+ all(p.user_ids.mapped(lambda u: u.user_has_groups('base.group_user'))))
+ partners_to_subscribe = partners.filtered(is_internal)
+ if partners_to_subscribe:
+ invoice.message_subscribe([p.id for p in partners_to_subscribe])
return invoice
@api.model
@@ -1615,6 +1617,13 @@ class AccountInvoice(models.Model):
def _get_intrastat_country_id(self):
return self.partner_id.country_id.id
+ def _get_onchange_create(self):
+ return OrderedDict([
+ ('_onchange_partner_id', ['account_id', 'payment_term_id', 'fiscal_position_id', 'partner_bank_id']),
+ ('_onchange_journal_id', ['currency_id']),
+ ])
+
+
class AccountInvoiceLine(models.Model):
_name = "account.invoice.line"
_description = "Invoice Line"
diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py
index 7d19678ebcf..34a81d82878 100644
--- a/addons/account/models/account_journal_dashboard.py
+++ b/addons/account/models/account_journal_dashboard.py
@@ -343,10 +343,10 @@ class account_journal(models.Model):
action_name = 'action_view_bank_statement_tree'
elif self.type == 'sale':
action_name = 'action_invoice_tree1'
- self = self.with_context(use_domain=[('type', 'in', ['out_invoice', 'out_refund'])])
+ self = self.with_context(use_domain=[('journal_id', '=', self.id)])
elif self.type == 'purchase':
action_name = 'action_vendor_bill_template'
- self = self.with_context(use_domain=[('type', 'in', ['in_invoice', 'in_refund'])])
+ self = self.with_context(use_domain=[('journal_id', '=', self.id)])
else:
action_name = 'action_move_journal_line'
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 847130113df..3007f56197a 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -1030,7 +1030,7 @@ class AccountMoveLine(models.Model):
writeoff_moves += writeoff_move
# writeoff_move.post()
- line_to_reconcile += writeoff_move.line_ids.filtered(lambda r: r.account_id == self[0].account_id)
+ line_to_reconcile += writeoff_move.line_ids.filtered(lambda r: r.account_id == self[0].account_id).sorted(key='id')[-1:]
if writeoff_moves:
writeoff_moves.post()
# Return the writeoff move.line which is to be reconciled
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index 3915b9f9c1f..f4cb84a3531 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -661,9 +661,7 @@ class account_payment(models.Model):
if any(len(record.invoice_ids) != 1 for record in self):
# For multiple invoices, there is account.register.payments wizard
raise UserError(_("This method should only be called to process a single invoice's payment."))
- res = self.post()
- self.mapped('payment_transaction_id').filtered(lambda x: x.state == 'done' and not x.is_processed)._post_process_after_done()
- return res
+ return self.post()
def _create_payment_entry(self, amount):
""" Create a journal entry corresponding to a payment, if the payment references invoice(s) they are reconciled.
diff --git a/addons/account/models/account_reconcile_model.py b/addons/account/models/account_reconcile_model.py
index 6794ab4ac0b..551edb7c3b4 100644
--- a/addons/account/models/account_reconcile_model.py
+++ b/addons/account/models/account_reconcile_model.py
@@ -395,6 +395,9 @@ class AccountReconcileModel(models.Model):
aml.date_maturity AS aml_date_maturity,
aml.amount_residual AS aml_amount_residual,
aml.amount_residual_currency AS aml_amount_residual_currency,
+ aml.balance AS aml_balance,
+ aml.amount_currency AS aml_amount_currency,
+ account.internal_type AS account_internal_type,
-- Determine a matching or not with the statement line communication using the move.name or move.ref.
regexp_split_to_array(TRIM(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g')),'\s+')
@@ -542,9 +545,12 @@ class AccountReconcileModel(models.Model):
return True
# Match total residual amount.
- total_residual = sum(
- aml['aml_currency_id'] and aml['aml_amount_residual_currency'] or aml['aml_amount_residual'] for aml in
- candidates)
+ total_residual = 0.0
+ for aml in candidates:
+ if aml['account_internal_type'] == 'liquidity':
+ total_residual += aml['aml_currency_id'] and aml['aml_amount_currency'] or aml['aml_balance']
+ else:
+ total_residual += aml['aml_currency_id'] and aml['aml_amount_residual_currency'] or aml['aml_amount_residual']
line_residual = statement_line.currency_id and statement_line.amount_currency or statement_line.amount
line_currency = statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.company_id.currency_id
diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py
index eab1c9f42fb..e9be12bcefb 100644
--- a/addons/account/tests/test_reconciliation.py
+++ b/addons/account/tests/test_reconciliation.py
@@ -470,6 +470,67 @@ class TestReconciliation(AccountingTestCase):
self.assertEquals(aml.amount_residual, 0, 'The journal item should be totally reconciled')
self.assertEquals(aml.amount_residual_currency, 0, 'The journal item should be totally reconciled')
+ def test_manual_reconcile_wizard_same_account(self):
+ move_ids = self.env['account.move']
+ debit_line_vals = {
+ 'name': '1',
+ 'debit': 728.35,
+ 'credit': 0.0,
+ 'account_id': self.account_rcv.id,
+ 'amount_currency': 795.05,
+ 'currency_id': self.currency_swiss_id,
+ }
+ credit_line_vals = {
+ 'name': '1',
+ 'debit': 0.0,
+ 'credit': 728.35,
+ 'account_id': self.account_rsa.id,
+ 'amount_currency': -795.05,
+ 'currency_id': self.currency_swiss_id,
+ }
+ vals = {
+ 'journal_id': self.bank_journal_euro.id,
+ 'date': time.strftime('%Y') + '-02-15',
+ 'line_ids': [(0,0, debit_line_vals), (0, 0, credit_line_vals)]
+ }
+ move_ids += self.env['account.move'].create(vals)
+ debit_line_vals = {
+ 'name': '2',
+ 'debit': 0.0,
+ 'credit': 737.10,
+ 'account_id': self.account_rcv.id,
+ 'amount_currency': -811.25,
+ 'currency_id': self.currency_swiss_id,
+ }
+ credit_line_vals = {
+ 'name': '2',
+ 'debit': 737.10,
+ 'credit': 0.0,
+ 'account_id': self.account_rsa.id,
+ 'amount_currency': 811.25,
+ 'currency_id': self.currency_swiss_id,
+ }
+ vals = {
+ 'journal_id': self.bank_journal_euro.id,
+ 'date': time.strftime('%Y') + '-07-15',
+ 'line_ids': [(0,0, debit_line_vals), (0, 0, credit_line_vals)]
+ }
+ move_ids += self.env['account.move'].create(vals)
+
+ account_move_line = move_ids.mapped('line_ids').filtered(lambda l: l.account_id == self.account_rcv)
+ writeoff_vals = [{
+ 'account_id': self.account_rcv.id,
+ 'journal_id': self.bank_journal_euro.id,
+ 'date': time.strftime('%Y') + '-04-15',
+ 'debit': 8.75,
+ 'credit': 0.0
+ }]
+ writeoff_line = account_move_line._create_writeoff(writeoff_vals)
+ (account_move_line + writeoff_line).reconcile()
+ self.assertEquals(len(writeoff_line), 1, "The writeoff_line (balance_line) should have only one moves line")
+ self.assertTrue(all(l.reconciled for l in writeoff_line), 'The balance lines should be totally reconciled')
+ self.assertTrue(all(l.reconciled for l in account_move_line), 'The move lines should be totally reconciled')
+
def test_reconcile_bank_statement_with_payment_and_writeoff(self):
# Use case:
# Company is in EUR, create a bill for 80 USD and register payment of 80 USD.
diff --git a/addons/account/tests/test_reconciliation_matching_rules.py b/addons/account/tests/test_reconciliation_matching_rules.py
index f50173296b5..cb10bee162e 100644
--- a/addons/account/tests/test_reconciliation_matching_rules.py
+++ b/addons/account/tests/test_reconciliation_matching_rules.py
@@ -24,8 +24,10 @@ class TestReconciliationMatchingRules(AccountingTestCase):
lines = invoice.move_id.line_ids
return lines.filtered(lambda l: l.account_id == invoice.account_id)
- def _check_statement_matching(self, rules, expected_values):
- statement_lines = (self.bank_st + self.cash_st).mapped('line_ids')
+ def _check_statement_matching(self, rules, expected_values, statements=None):
+ if statements is None:
+ statements = self.bank_st + self.cash_st
+ statement_lines = statements.mapped('line_ids')
matching_values = rules._apply_rules(statement_lines)
for st_line_id, values in matching_values.items():
values.pop('reconciled_lines', None)
@@ -34,6 +36,10 @@ class TestReconciliationMatchingRules(AccountingTestCase):
def setUp(self):
super(AccountingTestCase, self).setUp()
+ self.account_pay = self.env['account.account'].search([('internal_type', '=', 'payable')], limit=1)
+ self.account_liq = self.env['account.account'].search([('internal_type', '=', 'liquidity')], limit=1)
+ self.account_rcv = self.env['account.account'].search([('internal_type', '=', 'receivable')], limit=1)
+
self.partner_1 = self.env['res.partner'].create({'name': 'partner_1'})
self.partner_2 = self.env['res.partner'].create({'name': 'partner_2'})
@@ -45,7 +51,8 @@ class TestReconciliationMatchingRules(AccountingTestCase):
current_assets_account = self.env['account.account'].search(
[('user_type_id', '=', self.env.ref('account.data_account_type_current_assets').id)], limit=1)
- self.rule_1 = self.env.ref('account.reconciliation_model_default_rule')
+ self.rule_0 = self.env.ref('account.reconciliation_model_default_rule')
+ self.rule_1 = self.rule_0.copy()
self.rule_1.account_id = current_assets_account
self.rule_1.match_partner = True
self.rule_1.match_partner_ids |= self.partner_1 + self.partner_2
@@ -59,10 +66,10 @@ class TestReconciliationMatchingRules(AccountingTestCase):
invoice_number = self.invoice_line_1.move_id.name
- bank_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1)
+ self.bank_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1)
self.bank_st = self.env['account.bank.statement'].create({
- 'name': 'test bank journal', 'journal_id': bank_journal.id,
+ 'name': 'test bank journal', 'journal_id': self.bank_journal.id,
})
self.bank_line_1 = self.env['account.bank.statement.line'].create({
'statement_id': self.bank_st.id,
@@ -296,3 +303,47 @@ class TestReconciliationMatchingRules(AccountingTestCase):
{'partner_id': self.partner_2.id, 'debit': 0.0, 'credit': 1000.0},
{'partner_id': self.partner_2.id, 'debit': 1000.0, 'credit': 0.0},
])
+
+ def test_reverted_move_matching(self):
+ AccountMove = self.env['account.move']
+ move = AccountMove.create({
+ 'name': 'To Revert',
+ 'journal_id': self.bank_journal.id,
+ })
+
+ partner = self.env['res.partner'].create({'name': 'Eugene'})
+ AccountMoveLine = self.env['account.move.line'].with_context(check_move_validity=False)
+ payment_payable_line = AccountMoveLine.create({
+ 'account_id': self.account_pay.id,
+ 'move_id': move.id,
+ 'partner_id': partner.id,
+ 'name': 'One of these days',
+ 'debit': 10,
+ })
+ payment_bnk_line = AccountMoveLine.create({
+ 'account_id': self.account_liq.id,
+ 'move_id': move.id,
+ 'partner_id': partner.id,
+ 'name': 'I\'m gonna cut you into little pieces',
+ 'credit': 10,
+ })
+
+ move.post()
+ move_reversed = AccountMove.browse(move.reverse_moves())
+ self.assertTrue(move_reversed.exists())
+
+ bank_st = self.env['account.bank.statement'].create({
+ 'name': 'test bank journal', 'journal_id': self.bank_journal.id,
+ })
+ bank_line_1 = self.env['account.bank.statement.line'].create({
+ 'statement_id': bank_st.id,
+ 'name': '8',
+ 'partner_id': partner.id,
+ 'amount': -10,
+ 'sequence': 1,
+ })
+
+ expected_values = {
+ bank_line_1.id: {'aml_ids': [payment_bnk_line.id], 'model': self.rule_0}
+ }
+ self._check_statement_matching(self.rule_0, expected_values, statements=bank_st)
diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml
index c32413a4f1d..1c57b6892c9 100644
--- a/addons/account/views/report_invoice.xml
+++ b/addons/account/views/report_invoice.xml
@@ -51,29 +51,26 @@
<table class="table table-sm o_main_table" name="invoice_line_table">
<thead>
<tr>
- <t t-set="colspan" t-value="6"/>
<th class="text-left"><span>Description</span></th>
<th class="d-none text-left"><span>Source Document</span></th>
<th class="text-right"><span>Quantity</span></th>
<th t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}"><span>Unit Price</span></th>
<th t-if="display_discount" t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span>Disc.(%)</span>
- <t t-set="colspan" t-value="colspan+1"/>
</th>
<th t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}"><span>Taxes</span></th>
- <th class="text-right">
- <span groups="account.group_show_line_subtotals_tax_excluded">Amount</span>
- <span groups="account.group_show_line_subtotals_tax_included">Total Price</span>
- </th>
+ <th class="text-right" groups="account.group_show_line_subtotals_tax_excluded">Amount</th>
+ <th class="text-right" groups="account.group_show_line_subtotals_tax_included">Total Price</th>
</tr>
</thead>
<tbody class="invoice_tbody">
- <t t-set="current_subtotal" t-value="0"/>
+ <t t-set="current_subtotal_tax_excluded" t-value="0"/>
+ <t t-set="current_subtotal_tax_included" t-value="0"/>
<t t-foreach="o.invoice_line_ids" t-as="line">
- <t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
- <t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
+ <t t-set="current_subtotal_tax_excluded" t-value="current_subtotal_tax_excluded + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
+ <t t-set="current_subtotal_tax_included" t-value="current_subtotal_tax_included + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''">
<t t-if="not line.display_type" name="account_invoice_line_accountable">
@@ -92,20 +89,23 @@
<td t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.invoice_line_tax_ids))"/>
</td>
- <td class="text-right o_price_total">
- <span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
- <span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
+ <td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_excluded">
+ <span t-field="line.price_subtotal"/>
+ </td>
+ <td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_included">
+ <span t-field="line.price_total"/>
</td>
</t>
<t t-if="line.display_type == 'line_section'">
- <td t-att-colspan="colspan">
+ <td colspan="99">
<span t-field="line.name"/>
</td>
<t t-set="current_section" t-value="line"/>
- <t t-set="current_subtotal" t-value="0"/>
+ <t t-set="current_subtotal_tax_excluded" t-value="0"/>
+ <t t-set="current_subtotal_tax_included" t-value="0"/>
</t>
<t t-if="line.display_type == 'line_note'">
- <td t-att-colspan="colspan">
+ <td colspan="99">
<span t-field="line.name"/>
</td>
</t>
@@ -113,12 +113,17 @@
<t t-if="current_section and (line_last or o.invoice_line_ids[line_index+1].display_type == 'line_section')">
<tr class="is-subtotal text-right">
- <td t-att-colspan="colspan">
+ <td colspan="99">
<strong class="mr16">Subtotal</strong>
<span
- t-esc="current_subtotal"
+ groups="account.group_show_line_subtotals_tax_excluded"
+ t-esc="current_subtotal_tax_excluded"
t-options='{"widget": "monetary", "display_currency": o.currency_id}'
/>
+ <span groups="account.group_show_line_subtotals_tax_included"> | <span
+ t-esc="current_subtotal_tax_included"
+ t-options='{"widget": "monetary", "display_currency": o.currency_id}'
+ /></span>
</td>
</tr>
</t>
diff --git a/addons/account_check_printing/models/account_payment.py b/addons/account_check_printing/models/account_payment.py
index 28e9f285c1d..cfba4270da8 100644
--- a/addons/account_check_printing/models/account_payment.py
+++ b/addons/account_check_printing/models/account_payment.py
@@ -28,7 +28,7 @@ class AccountRegisterPayments(models.TransientModel):
def _onchange_amount(self):
if hasattr(super(AccountRegisterPayments, self), '_onchange_amount'):
super(AccountRegisterPayments, self)._onchange_amount()
- self.check_amount_in_words = self.currency_id.amount_to_text(self.amount)
+ self.check_amount_in_words = self.currency_id.amount_to_text(self.amount) if self.currency_id else False
def _prepare_payment_vals(self, invoices):
res = super(AccountRegisterPayments, self)._prepare_payment_vals(invoices)
diff --git a/addons/bus/models/bus.py b/addons/bus/models/bus.py
index 7a7f8b497db..d3ab23e6c1f 100644
--- a/addons/bus/models/bus.py
+++ b/addons/bus/models/bus.py
@@ -140,7 +140,7 @@ class ImDispatch(object):
event = self.Event()
for channel in channels:
- self.channels.setdefault(hashable(channel), []).append(event)
+ self.channels.setdefault(hashable(channel), set()).add(event)
try:
event.wait(timeout=timeout)
with registry.cursor() as cr:
@@ -149,6 +149,12 @@ class ImDispatch(object):
except Exception:
# timeout
pass
+ finally:
+ # gc pointers to event
+ for channel in channels:
+ channel_events = self.channels.get(hashable(channel))
+ if channel_events and event in channel_events:
+ channel_events.remove(event)
return notifications
def loop(self):
@@ -169,7 +175,7 @@ class ImDispatch(object):
# dispatch to local threads/greenlets
events = set()
for channel in channels:
- events.update(self.channels.pop(hashable(channel), []))
+ events.update(self.channels.pop(hashable(channel), set()))
for event in events:
event.set()
diff --git a/addons/delivery/models/stock_picking.py b/addons/delivery/models/stock_picking.py
index accc102c2b7..5d4da724ac6 100644
--- a/addons/delivery/models/stock_picking.py
+++ b/addons/delivery/models/stock_picking.py
@@ -111,7 +111,7 @@ class StockPicking(models.Model):
for picking in self:
picking.carrier_tracking_url = picking.carrier_id.get_tracking_link(picking) if picking.carrier_id and picking.carrier_tracking_ref else False
- @api.depends('move_lines')
+ @api.depends('move_lines', 'move_ids_without_package')
def _cal_weight(self):
for picking in self:
picking.weight = sum(move.weight for move in picking.move_lines if move.state != 'cancel')
diff --git a/addons/hr_holidays/models/hr.py b/addons/hr_holidays/models/hr.py
index 8fbbf89b8df..8a8d1716df0 100644
--- a/addons/hr_holidays/models/hr.py
+++ b/addons/hr_holidays/models/hr.py
@@ -87,6 +87,9 @@ class Employee(models.Model):
show_leaves = fields.Boolean('Able to see Remaining Leaves', compute='_compute_show_leaves')
is_absent_totay = fields.Boolean('Absent Today', compute='_compute_absent_employee', search='_search_absent_employee')
+ def _get_date_start_work(self):
+ return self.create_date
+
def _get_remaining_leaves(self):
""" Helper to compute the remaining leaves for the current employees
:returns dict where the key is the employee id, and the value is the remain leaves
diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py
index 840537796e4..023b3aebeaf 100644
--- a/addons/hr_holidays/models/hr_leave_allocation.py
+++ b/addons/hr_holidays/models/hr_leave_allocation.py
@@ -167,16 +167,15 @@ class HolidaysAllocation(models.Model):
# We have to check when the employee has been created
# in order to not allocate him/her too much leaves
- creation_date = fields.Datetime.from_string(holiday.employee_id.create_date)
-
+ start_date = holiday.employee_id._get_date_start_work()
# If employee is created after the period, we cancel the computation
- if period_end <= creation_date:
+ if period_end <= start_date:
holiday.write(values)
continue
# If employee created during the period, taking the date at which he has been created
- if period_start <= creation_date:
- period_start = creation_date
+ if period_start <= start_date:
+ period_start = start_date
worked = holiday.employee_id._get_work_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days']
left = holiday.employee_id._get_leave_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days']
diff --git a/addons/hw_escpos/escpos/constants.py b/addons/hw_escpos/escpos/constants.py
index 238e8bdbf0f..0000109a964 100644
--- a/addons/hw_escpos/escpos/constants.py
+++ b/addons/hw_escpos/escpos/constants.py
@@ -2,6 +2,9 @@
""" ESC/POS Commands (Constants) """
+# Control characters
+ESC = '\x1b'
+
# Feed control sequences
CTL_LF = '\x0a' # Print and line feed
CTL_FF = '\x0c' # Form feed
@@ -19,9 +22,10 @@ DLE_EOT_PAPER = '\x10\x04\x04'
HW_INIT = '\x1b\x40' # Clear data in buffer and reset modes
HW_SELECT = '\x1b\x3d\x01' # Printer select
HW_RESET = '\x1b\x3f\x0a\x00' # Reset printer hardware
-# Cash Drawer
-CD_KICK_2 = '\x1b\x70\x00' # Sends a pulse to pin 2 []
-CD_KICK_5 = '\x1b\x70\x01' # Sends a pulse to pin 5 []
+# Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>)
+_CASH_DRAWER = lambda m, t1='', t2='': ESC + 'p' + m + chr(t1) + chr(t2)
+CD_KICK_2 = _CASH_DRAWER('\x00', 50, 50) # Sends a pulse to pin 2 []
+CD_KICK_5 = _CASH_DRAWER('\x01', 50, 50) # Sends a pulse to pin 5 []
# Paper
PAPER_FULL_CUT = '\x1d\x56\x00' # Full cut paper
PAPER_PART_CUT = '\x1d\x56\x01' # Partial cut paper
diff --git a/addons/im_livechat/models/mail_channel.py b/addons/im_livechat/models/mail_channel.py
index 8b81501db20..cb3f57f689c 100644
--- a/addons/im_livechat/models/mail_channel.py
+++ b/addons/im_livechat/models/mail_channel.py
@@ -30,7 +30,6 @@ class MailChannel(models.Model):
_name = 'mail.channel'
_inherit = ['mail.channel', 'rating.mixin']
- _description = 'Discussion channel'
anonymous_name = fields.Char('Anonymous Name')
channel_type = fields.Selection(selection_add=[('livechat', 'Livechat Conversation')])
diff --git a/addons/l10n_fr/data/account_tax_data.xml b/addons/l10n_fr/data/account_tax_data.xml
index afd9efc4037..b7bdcf79e32 100644
--- a/addons/l10n_fr/data/account_tax_data.xml
+++ b/addons/l10n_fr/data/account_tax_data.xml
@@ -1024,7 +1024,7 @@
<field name="account_id" ref="pcg_445203"/>
<field name="sequence" eval="10"/>
<field name="type_tax_use">purchase</field>
- <field name="tag_ids" eval="[(6,0,[ref('l10n_fr.tag_fr_73'),ref('l10n_fr.tag_fr_82')])]"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_fr.tag_fr_73'),ref('l10n_fr.tag_fr_64')])]"/>
<field name="tax_group_id" ref="tax_group_intra_21"/>
</record>
diff --git a/addons/l10n_nl/data/account.account.template.csv b/addons/l10n_nl/data/account.account.template.csv
index ef2fd529278..2164c15119f 100644
--- a/addons/l10n_nl/data/account.account.template.csv
+++ b/addons/l10n_nl/data/account.account.template.csv
@@ -324,6 +324,9 @@
"8043","Omzet buiten EU werk derden","8043","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
"8044","Omzet Intercompany werk derden","8044","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
"8090","Diverse overige opbrengsten","8090","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
+"8110","Installatie/afstandsverkopen binnen EU handelsgoederen 1","80011","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
+"8120","Installatie/afstandsverkopen binnen EU handelsgoederen 2","80012","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
+"8130","Installatie/afstandsverkopen binnen EU handelsgoederen 3","80013","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False"
"8920","Koersverschillen","8920","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_31","False"
"9011","Resultaat deelneming 1","9011","account.data_account_type_other_income","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_12","False"
"9012","Resultaat deelneming 2","9012","account.data_account_type_other_income","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_12","False"
diff --git a/addons/l10n_nl/data/account_fiscal_position_account_template.xml b/addons/l10n_nl/data/account_fiscal_position_account_template.xml
index ab156438382..81647963b10 100644
--- a/addons/l10n_nl/data/account_fiscal_position_account_template.xml
+++ b/addons/l10n_nl/data/account_fiscal_position_account_template.xml
@@ -92,5 +92,30 @@
<field name="account_dest_id" ref="8022" />
</record>
+ <!-- Installatie / afstandsverkopen -->
+ <record
+ id="fiscal_position_account_installatie_afstand_1"
+ model="account.fiscal.position.account.template">
+ <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report" />
+ <field name="account_src_id" ref="8001" />
+ <field name="account_dest_id" ref="8110" />
+ </record>
+
+ <record
+ id="fiscal_position_account_installatie_afstand_2"
+ model="account.fiscal.position.account.template">
+ <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report" />
+ <field name="account_src_id" ref="8011" />
+ <field name="account_dest_id" ref="8120" />
+ </record>
+
+ <record
+ id="fiscal_position_account_installatie_afstand_3"
+ model="account.fiscal.position.account.template">
+ <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report" />
+ <field name="account_src_id" ref="8021" />
+ <field name="account_dest_id" ref="8130" />
+ </record>
+
</data>
-</openerp> \ No newline at end of file
+</openerp>
diff --git a/addons/l10n_nl/data/account_fiscal_position_tax_template.xml b/addons/l10n_nl/data/account_fiscal_position_tax_template.xml
index e59c852ef24..05e54233bcf 100644
--- a/addons/l10n_nl/data/account_fiscal_position_tax_template.xml
+++ b/addons/l10n_nl/data/account_fiscal_position_tax_template.xml
@@ -34,7 +34,7 @@
<record id="position_tax_extracom_d_0" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_non_eu"/>
<field name="tax_src_id" ref="btw_0_d"/>
- <field name="tax_dest_id" ref="btw_X3"/>
+ <field name="tax_dest_id" ref="btw_X1"/>
</record>
<record id="position_tax_extracom_d_6" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_non_eu"/>
@@ -49,12 +49,12 @@
<record id="position_tax_extracom_d_21" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_non_eu"/>
<field name="tax_src_id" ref="btw_21_d"/>
- <field name="tax_dest_id" ref="btw_X3"/>
+ <field name="tax_dest_id" ref="btw_X1"/>
</record>
<record id="position_tax_extracom_d_overig" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_non_eu"/>
<field name="tax_src_id" ref="btw_overig_d"/>
- <field name="tax_dest_id" ref="btw_X3"/>
+ <field name="tax_dest_id" ref="btw_X1"/>
</record>
<!-- VAT on buying from outside the EU -->
<record id="position_tax_extracom_6" model="account.fiscal.position.tax.template">
@@ -65,7 +65,7 @@
<record id="position_tax_extracom_9" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_non_eu"/>
<field name="tax_src_id" ref="btw_9_buy"/>
- <field name="tax_dest_id" ref="btw_E1"/>
+ <field name="tax_dest_id" ref="btw_E1_9"/>
</record>
<record id="position_tax_extracom_7" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_non_eu"/>
@@ -85,7 +85,7 @@
<record id="position_tax_extracom_d_9" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_non_eu"/>
<field name="tax_src_id" ref="btw_9_buy_d"/>
- <field name="tax_dest_id" ref="btw_E1"/>
+ <field name="tax_dest_id" ref="btw_E1_9"/>
</record>
<record id="position_tax_extracom_d_7" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_non_eu"/>
@@ -95,59 +95,59 @@
<record id="position_tax_extracom_d_8" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_non_eu"/>
<field name="tax_src_id" ref="btw_overig_buy_d"/>
- <field name="tax_dest_id" ref="btw_E_overig"/>
+ <field name="tax_dest_id" ref="btw_E_overig_d"/>
</record>
<!-- EU Countries -->
<record id="position_tax_intracom_1" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_0"/>
- <field name="tax_dest_id" ref="btw_X0_diensten"/>
+ <field name="tax_dest_id" ref="btw_X0_producten"/>
</record>
<record id="position_tax_intracom_2" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_6"/>
- <field name="tax_dest_id" ref="btw_X0_diensten"/>
+ <field name="tax_dest_id" ref="btw_X0_producten"/>
</record>
<record id="position_tax_intracom_2_9" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_9"/>
- <field name="tax_dest_id" ref="btw_X0_diensten"/>
+ <field name="tax_dest_id" ref="btw_X0_producten"/>
</record>
<record id="position_tax_intracom_3" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_21"/>
- <field name="tax_dest_id" ref="btw_X0_diensten"/>
+ <field name="tax_dest_id" ref="btw_X0_producten"/>
</record>
<record id="position_tax_intracom_4" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_overig"/>
- <field name="tax_dest_id" ref="btw_X0_diensten"/>
+ <field name="tax_dest_id" ref="btw_X0_producten"/>
</record>
<record id="position_tax_intracom_d_1" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_0_d"/>
- <field name="tax_dest_id" ref="btw_X2"/>
+ <field name="tax_dest_id" ref="btw_X0_diensten"/>
</record>
<record id="position_tax_intracom_d_2" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_6_d"/>
- <field name="tax_dest_id" ref="btw_X2"/>
+ <field name="tax_dest_id" ref="btw_X0_diensten"/>
</record>
<record id="position_tax_intracom_d_2_9" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_9_d"/>
- <field name="tax_dest_id" ref="btw_X2"/>
+ <field name="tax_dest_id" ref="btw_X0_diensten"/>
</record>
<record id="position_tax_intracom_d_3" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_21_d"/>
- <field name="tax_dest_id" ref="btw_X2"/>
+ <field name="tax_dest_id" ref="btw_X0_diensten"/>
</record>
<record id="position_tax_intracom_d_4" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_overig_d"/>
- <field name="tax_dest_id" ref="btw_X2"/>
+ <field name="tax_dest_id" ref="btw_X0_diensten"/>
</record>
<record id="position_tax_intracom_6" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
@@ -172,22 +172,22 @@
<record id="position_tax_intracom_d_6" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_6_buy_d"/>
- <field name="tax_dest_id" ref="btw_I_6"/>
+ <field name="tax_dest_id" ref="btw_I_6_d"/>
</record>
<record id="position_tax_intracom_d_9" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_9_buy_d"/>
- <field name="tax_dest_id" ref="btw_I_9"/>
+ <field name="tax_dest_id" ref="btw_I_9_d"/>
</record>
<record id="position_tax_intracom_d_7" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_21_buy_d"/>
- <field name="tax_dest_id" ref="btw_I_21"/>
+ <field name="tax_dest_id" ref="btw_I_21_d"/>
</record>
<record id="position_tax_intracom_d_8" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu"/>
<field name="tax_src_id" ref="btw_overig_buy_d"/>
- <field name="tax_dest_id" ref="btw_I_overig"/>
+ <field name="tax_dest_id" ref="btw_I_overig_d"/>
</record>
<!-- BTW verlegd -->
@@ -196,6 +196,5 @@
<field name="tax_src_id" ref="btw_21_buy"/>
<field name="tax_dest_id" ref="btw_ink_0"/>
</record>
-
</data>
</openerp>
diff --git a/addons/l10n_nl/data/account_fiscal_position_template.xml b/addons/l10n_nl/data/account_fiscal_position_template.xml
index f70c6fc6e1b..a6519cac9d7 100644
--- a/addons/l10n_nl/data/account_fiscal_position_template.xml
+++ b/addons/l10n_nl/data/account_fiscal_position_template.xml
@@ -28,83 +28,57 @@
<record id="position_tax_eu_no_taxes_report_1" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
<field name="tax_src_id" ref="btw_0"/>
+ <field name="tax_dest_id" ref="btw_X2"/>
</record>
<record id="position_tax_eu_no_taxes_report_2" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_6"/>
+ <field name="tax_src_id" ref="btw_verk_0"/>
+ <field name="tax_dest_id" ref="btw_X2"/>
</record>
<record id="position_tax_eu_no_taxes_report_3" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_21"/>
+ <field name="tax_src_id" ref="btw_6"/>
+ <field name="tax_dest_id" ref="btw_X2"/>
</record>
<record id="position_tax_eu_no_taxes_report_4" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_overig"/>
+ <field name="tax_src_id" ref="btw_9"/>
+ <field name="tax_dest_id" ref="btw_X2"/>
</record>
<record id="position_tax_eu_no_taxes_report_5" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_0_d"/>
+ <field name="tax_src_id" ref="btw_21"/>
+ <field name="tax_dest_id" ref="btw_X2"/>
</record>
<record id="position_tax_eu_no_taxes_report_6" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_6_d"/>
+ <field name="tax_src_id" ref="btw_overig"/>
+ <field name="tax_dest_id" ref="btw_X2"/>
</record>
<record id="position_tax_eu_no_taxes_report_7" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_21_d"/>
+ <field name="tax_src_id" ref="btw_0_d"/>
+ <field name="tax_dest_id" ref="btw_X2"/>
</record>
<record id="position_tax_eu_no_taxes_report_8" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_overig_d"/>
+ <field name="tax_src_id" ref="btw_6_d"/>
+ <field name="tax_dest_id" ref="btw_X2"/>
</record>
<record id="position_tax_eu_no_taxes_report_9" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_6_buy"/>
+ <field name="tax_src_id" ref="btw_9_d"/>
+ <field name="tax_dest_id" ref="btw_X2"/>
</record>
<record id="position_tax_eu_no_taxes_report_10" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_6_buy_incl"/>
+ <field name="tax_src_id" ref="btw_21_d"/>
+ <field name="tax_dest_id" ref="btw_X2"/>
</record>
<record id="position_tax_eu_no_taxes_report_11" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_21_buy"/>
- </record>
- <record id="position_tax_eu_no_taxes_report_12" model="account.fiscal.position.tax.template">
- <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_21_buy_incl"/>
- </record>
- <record id="position_tax_eu_no_taxes_report_13" model="account.fiscal.position.tax.template">
- <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_overig_buy"/>
- </record>
- <record id="position_tax_eu_no_taxes_report_14" model="account.fiscal.position.tax.template">
- <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_6_buy_d"/>
- </record>
- <record id="position_tax_eu_no_taxes_report_15" model="account.fiscal.position.tax.template">
- <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_21_buy_d"/>
- </record>
- <record id="position_tax_eu_no_taxes_report_16" model="account.fiscal.position.tax.template">
- <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_overig_buy_d"/>
- </record>
- <record id="position_tax_eu_no_taxes_report_17" model="account.fiscal.position.tax.template">
- <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_verk_0"/>
- </record>
- <record id="position_tax_eu_no_taxes_report_18" model="account.fiscal.position.tax.template">
- <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_ink_0_1"/>
- </record>
- <record id="position_tax_eu_no_taxes_report_19" model="account.fiscal.position.tax.template">
- <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_ink_0_2"/>
- </record>
- <record id="position_tax_eu_no_taxes_report_20" model="account.fiscal.position.tax.template">
- <field name="position_id" ref="fiscal_position_template_eu_no_taxes_report"/>
- <field name="tax_src_id" ref="btw_ink_0"/>
+ <field name="tax_src_id" ref="btw_overig_d"/>
+ <field name="tax_dest_id" ref="btw_X2"/>
</record>
-
</data>
</openerp>
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index e8a904f2d37..e48ca4cbd3d 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -318,9 +318,9 @@ class MassMailingCampaign(models.Model):
campaign_id = fields.Many2one('utm.campaign', 'campaign_id',
required=True, ondelete='cascade', help="This name helps you tracking your different campaign efforts, e.g. Fall_Drive, Christmas_Special")
source_id = fields.Many2one('utm.source', string='Source',
- help="This is the link source, e.g. Search Engine, another domain,or name of email list", default=lambda self: self.env.ref('utm.utm_source_newsletter'))
+ help="This is the link source, e.g. Search Engine, another domain,or name of email list", default=lambda self: self.env.ref('utm.utm_source_newsletter', False))
medium_id = fields.Many2one('utm.medium', string='Medium',
- help="This is the delivery method, e.g. Postcard, Email, or Banner Ad", default=lambda self: self.env.ref('utm.utm_medium_email'))
+ help="This is the delivery method, e.g. Postcard, Email, or Banner Ad", default=lambda self: self.env.ref('utm.utm_medium_email', False))
tag_ids = fields.Many2many(
'mail.mass_mailing.tag', 'mail_mass_mailing_tag_rel',
'tag_id', 'campaign_id', string='Tags')
diff --git a/addons/mrp/report/mrp_report_bom_structure.py b/addons/mrp/report/mrp_report_bom_structure.py
index f687c7e15a8..544c4aaa009 100644
--- a/addons/mrp/report/mrp_report_bom_structure.py
+++ b/addons/mrp/report/mrp_report_bom_structure.py
@@ -208,10 +208,10 @@ class ReportBomStructure(models.AbstractModel):
def _get_pdf_line(self, bom_id, product_id=False, qty=1, child_bom_ids=[], unfolded=False):
- data = self._get_bom(bom_id=bom_id, product_id=product_id, line_qty=qty)
+ data = self._get_bom(bom_id=bom_id, product_id=product_id.id, line_qty=qty)
def get_sub_lines(bom, product_id, line_qty, line_id, level):
- data = self._get_bom(bom_id=bom.id, product_id=product_id, line_qty=line_qty, line_id=line_id, level=level)
+ data = self._get_bom(bom_id=bom.id, product_id=product_id.id, line_qty=line_qty, line_id=line_id, level=level)
bom_lines = data['components']
lines = []
for bom_line in bom_lines:
diff --git a/addons/payment/models/account_payment.py b/addons/payment/models/account_payment.py
index 21c0b5530dd..220cb37db38 100644
--- a/addons/payment/models/account_payment.py
+++ b/addons/payment/models/account_payment.py
@@ -71,6 +71,11 @@ class AccountPayment(models.Model):
return transactions
+ def action_validate_invoice_payment(self):
+ res = super(AccountPayment, self).action_validate_invoice_payment()
+ self.mapped('payment_transaction_id').filtered(lambda x: x.state == 'done' and not x.is_processed)._post_process_after_done()
+ return res
+
@api.multi
def post(self):
# Post the payments "normally" if no transactions are needed.
diff --git a/addons/payment_buckaroo/tests/test_buckaroo.py b/addons/payment_buckaroo/tests/test_buckaroo.py
index e23b1f0c002..c78c998304d 100644
--- a/addons/payment_buckaroo/tests/test_buckaroo.py
+++ b/addons/payment_buckaroo/tests/test_buckaroo.py
@@ -32,12 +32,12 @@ class BuckarooForm(BuckarooCommon):
# ----------------------------------------
form_values = {
- 'add_returndata': '',
+ 'add_returndata': '/payment/process',
'Brq_websitekey': self.buckaroo.brq_websitekey,
'Brq_amount': '2240.0',
'Brq_currency': 'EUR',
'Brq_invoicenumber': 'SO004',
- 'Brq_signature': 'aa6fe072afdd9d1b463d55f43e6df4b272d3529a', # update me
+ 'Brq_signature': 'f09bb79451a7ddef3178462ea52b07e55d34e99e', # update me
'brq_test': 'True',
'Brq_return': urls.url_join(base_url, BuckarooController._return_url),
'Brq_returncancel': urls.url_join(base_url, BuckarooController._cancel_url),
diff --git a/addons/payment_paypal/tests/test_paypal.py b/addons/payment_paypal/tests/test_paypal.py
index b8158508a2a..c285829f08d 100644
--- a/addons/payment_paypal/tests/test_paypal.py
+++ b/addons/payment_paypal/tests/test_paypal.py
@@ -67,6 +67,7 @@ class PaypalForm(PaypalCommon):
'return': urls.url_join(base_url, PaypalController._return_url),
'notify_url': urls.url_join(base_url, PaypalController._notify_url),
'cancel_return': urls.url_join(base_url, PaypalController._cancel_url),
+ 'custom': '{"return_url": "/payment/process"}',
}
# check form result
@@ -140,7 +141,7 @@ class PaypalForm(PaypalCommon):
'item_name': u'test_ref_2',
'address_country': u'France',
'charset': u'windows-1252',
- 'custom': u'',
+ 'custom': u'{"return_url": "/payment/process"}',
'notify_version': u'3.7',
'address_name': u'Norbert Poilu',
'pending_reason': u'multi_currency',
diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py
index 8b1bbc0eaea..0928aab1815 100644
--- a/addons/product/models/product_template.py
+++ b/addons/product/models/product_template.py
@@ -346,6 +346,12 @@ class ProductTemplate(models.Model):
raise ValidationError(_('The default Unit of Measure and the purchase Unit of Measure must be in the same category.'))
return True
+ @api.constrains('attribute_line_ids')
+ def _check_attribute_line(self):
+ if any(len(template.attribute_line_ids) != len(template.attribute_line_ids.mapped('attribute_id')) for template in self):
+ raise ValidationError(_('You cannot define two attribute lines for the same attribute.'))
+ return True
+
@api.onchange('uom_id')
def _onchange_uom_id(self):
if self.uom_id:
diff --git a/addons/project/data/project_data.xml b/addons/project/data/project_data.xml
index ff624b5a232..33535f15c19 100644
--- a/addons/project/data/project_data.xml
+++ b/addons/project/data/project_data.xml
@@ -2,16 +2,6 @@
<odoo>
<data noupdate="1">
- <!-- sale_timesheet and project define the same field without depending on each, which causes the field to be deleted when the module that created it
- is deleted. To avoid this, we create xmlids manually for this field in both modules to prevent accidental deletion. To fix in saas-7 by moving the field definition-->
- <record id="duplicate_field_xmlid" model="ir.model.data">
- <field name="res_id" search="[('model','=','res.company'),('name','=','project_time_mode_id')]" model="ir.model.fields"/>
- <field name="model">ir.model.fields</field>
- <field name="module">project</field>
- <field name="name">project_time_mode_id_duplicate_xmlid</field>
- <field name="noupdate">True</field>
- </record>
-
<record id="ir_cron_rating_project" model="ir.cron">
<field name="name">Project: Send rating</field>
<field name="model_id" ref="project.model_project_project"/>
diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py
index eaa71775476..9f63039afd7 100644
--- a/addons/purchase/models/account_invoice.py
+++ b/addons/purchase/models/account_invoice.py
@@ -131,7 +131,7 @@ class AccountInvoice(models.Model):
res = super(AccountInvoice, self)._onchange_partner_id()
if payment_term_id:
self.payment_term_id = payment_term_id
- if not self.env.context.get('default_journal_id') and self.partner_id and self.currency_id and\
+ if not self.env.context.get('default_journal_id') and self.partner_id and\
self.type in ['in_invoice', 'in_refund'] and\
self.currency_id != self.partner_id.property_purchase_currency_id and\
self.partner_id.property_purchase_currency_id.id:
@@ -172,6 +172,11 @@ class AccountInvoice(models.Model):
invoice.message_post(body=message)
return result
+ def _get_onchange_create(self):
+ res = super()._get_onchange_create()
+ res['_onchange_partner_id'].append('currency_id')
+ return res
+
class AccountInvoiceLine(models.Model):
""" Override AccountInvoice_line to add the link to the purchase order line it is related to"""
diff --git a/addons/purchase/models/product.py b/addons/purchase/models/product.py
index 644bfc77b43..9567f28ce7e 100644
--- a/addons/purchase/models/product.py
+++ b/addons/purchase/models/product.py
@@ -90,3 +90,11 @@ class ProductCategory(models.Model):
'account.account', string="Price Difference Account",
company_dependent=True,
help="This account will be used to value price difference between purchase price and accounting cost.")
+
+
+class SupplierInfo(models.Model):
+ _inherit = "product.supplierinfo"
+
+ @api.onchange('name')
+ def _onchange_partner_id(self):
+ self.currency_id = self.name.property_purchase_currency_id or self.env.user.company_id.currency_id
diff --git a/addons/purchase_stock/report/purchase_report_templates.xml b/addons/purchase_stock/report/purchase_report_templates.xml
index 8e5adc9d45d..0c5a42fd0da 100644
--- a/addons/purchase_stock/report/purchase_report_templates.xml
+++ b/addons/purchase_stock/report/purchase_report_templates.xml
@@ -1,22 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
- <template id="report_purchaseorder_document" inherit_id="purchase.report_purchaseorder_document">
- <xpath expr="//div[@name='purchase_shipping_address']" position="after">
- <div t-if="not o.dest_address_id and o.picking_type_id and o.picking_type_id.warehouse_id">
- <span t-field="o.picking_type_id.warehouse_id.name"/>
- <div t-field="o.picking_type_id.warehouse_id.partner_id" t-options='{"widget": "contact", "fields": ["address", "phone"], "no_marker": True, "phone_icons": True}'/>
- </div>
- </xpath>
- </template>
+ <template id="report_purchaseorder_document" inherit_id="purchase.report_purchaseorder_document">
+ <xpath expr="//t[@t-if='o.dest_address_id']" position="after">
+ <t t-else="">
+ <t t-set="information_block">
+ <strong>Shipping address:</strong>
+ <div t-if="o.picking_type_id and o.picking_type_id.warehouse_id">
+ <span t-field="o.picking_type_id.warehouse_id.name"/>
+ <div t-field="o.picking_type_id.warehouse_id.partner_id" t-options='{"widget": "contact", "fields": ["address", "phone"], "no_marker": True, "phone_icons": True}'/>
+ </div>
+ </t>
+ </t>
+ </xpath>
+ </template>
- <template id="report_purchasequotation_document" inherit_id="purchase.report_purchasequotation_document">
- <xpath expr="//div[@name='purchase_shipping_address']" position="after">
- <div t-if="not o.dest_address_id and o.picking_type_id.warehouse_id">
- <span t-field="o.picking_type_id.warehouse_id.name"/>
- <div t-field="o.picking_type_id.warehouse_id.partner_id" t-options='{"widget": "contact", "fields": ["address", "phone"], "no_marker": True, "phone_icons": True}'/>
- </div>
- </xpath>
- </template>
+ <template id="report_purchasequotation_document" inherit_id="purchase.report_purchasequotation_document">
+ <xpath expr="//t[@t-if='o.dest_address_id']" position="after">
+ <t t-else="">
+ <t t-set="information_block">
+ <strong>Shipping address:</strong>
+ <div t-if="o.picking_type_id and o.picking_type_id.warehouse_id">
+ <span t-field="o.picking_type_id.warehouse_id.name"/>
+ <div t-field="o.picking_type_id.warehouse_id.partner_id" t-options='{"widget": "contact", "fields": ["address", "phone"], "no_marker": True, "phone_icons": True}'/>
+ </div>
+ </t>
+ </t>
+ </xpath>
+ </template>
</odoo>
diff --git a/addons/repair/models/repair.py b/addons/repair/models/repair.py
index de88001fb57..bcd44c95292 100644
--- a/addons/repair/models/repair.py
+++ b/addons/repair/models/repair.py
@@ -598,17 +598,24 @@ class RepairLine(models.Model):
'title': _('No pricelist found.'),
'message':
_('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.')}
+ return {'warning': warning}
else:
- price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner)
- if price is False:
- warning = {
- 'title': _('No valid pricelist line found.'),
- 'message':
- _("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")}
- else:
- self.price_unit = price
- if warning:
+ self._onchange_product_uom()
+
+ @api.onchange('product_uom')
+ def _onchange_product_uom(self):
+ partner = self.repair_id.partner_id
+ pricelist = self.repair_id.pricelist_id
+ if pricelist and self.product_id and self.type != 'remove':
+ price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner, uom_id=self.product_uom.id)
+ if price is False:
+ warning = {
+ 'title': _('No valid pricelist line found.'),
+ 'message':
+ _("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")}
return {'warning': warning}
+ else:
+ self.price_unit = price
class RepairFee(models.Model):
@@ -658,14 +665,21 @@ class RepairFee(models.Model):
'title': _('No pricelist found.'),
'message':
_('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.')}
+ return {'warning': warning}
else:
- price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner)
+ self._onchange_product_uom()
+
+ @api.onchange('product_uom')
+ def _onchange_product_uom(self):
+ partner = self.repair_id.partner_id
+ pricelist = self.repair_id.pricelist_id
+ if pricelist and self.product_id:
+ price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner, uom_id=self.product_uom.id)
if price is False:
warning = {
'title': _('No valid pricelist line found.'),
'message':
_("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")}
+ return {'warning': warning}
else:
self.price_unit = price
- if warning:
- return {'warning': warning}
diff --git a/addons/sale/report/sale_report_templates.xml b/addons/sale/report/sale_report_templates.xml
index edce182dcfc..28a5d105dfc 100644
--- a/addons/sale/report/sale_report_templates.xml
+++ b/addons/sale/report/sale_report_templates.xml
@@ -70,29 +70,26 @@
<table class="table table-sm o_main_table">
<thead>
<tr>
- <t t-set="colspan" t-value="5"/>
<th class="text-left">Description</th>
<th class="text-right">Quantity</th>
<th class="text-right">Unit Price</th>
<th t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">
<span>Disc.(%)</span>
- <t t-set="colspan" t-value="colspan+1"/>
</th>
<th class="text-right">Taxes</th>
- <th class="text-right">
- <t groups="account.group_show_line_subtotals_tax_excluded">Amount</t>
- <t groups="account.group_show_line_subtotals_tax_included">Total Price</t>
- </th>
+ <th class="text-right" groups="account.group_show_line_subtotals_tax_excluded">Amount</th>
+ <th class="text-right" groups="account.group_show_line_subtotals_tax_included">Total Price</th>
</tr>
</thead>
<tbody class="sale_tbody">
- <t t-set="current_subtotal" t-value="0"/>
+ <t t-set="current_subtotal_tax_excluded" t-value="0"/>
+ <t t-set="current_subtotal_tax_included" t-value="0"/>
<t t-foreach="doc.order_line" t-as="line">
- <t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
- <t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
+ <t t-set="current_subtotal_tax_excluded" t-value="current_subtotal_tax_excluded + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
+ <t t-set="current_subtotal_tax_included" t-value="current_subtotal_tax_included + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''">
<t t-if="not line.display_type">
@@ -110,20 +107,23 @@
<td class="text-right">
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_id))"/>
</td>
- <td class="text-right o_price_total">
- <span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
- <span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
+ <td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_excluded">
+ <span t-field="line.price_subtotal"/>
+ </td>
+ <td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_included">
+ <span t-field="line.price_total"/>
</td>
</t>
<t t-if="line.display_type == 'line_section'">
- <td t-att-colspan="99">
+ <td colspan="99">
<span t-field="line.name"/>
</td>
<t t-set="current_section" t-value="line"/>
- <t t-set="current_subtotal" t-value="0"/>
+ <t t-set="current_subtotal_tax_excluded" t-value="0"/>
+ <t t-set="current_subtotal_tax_included" t-value="0"/>
</t>
<t t-if="line.display_type == 'line_note'">
- <td t-att-colspan="99">
+ <td colspan="99">
<span t-field="line.name"/>
</td>
</t>
@@ -131,12 +131,17 @@
<t t-if="current_section and (line_last or doc.order_line[line_index+1].display_type == 'line_section')">
<tr class="is-subtotal text-right">
- <td t-att-colspan="99">
+ <td colspan="99" groups="account.group_show_line_subtotals_tax_excluded,account.group_show_line_subtotals_tax_included">
<strong class="mr16">Subtotal</strong>
<span
- t-esc="current_subtotal"
+ groups="account.group_show_line_subtotals_tax_excluded"
+ t-esc="current_subtotal_tax_excluded"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
+ <span groups="account.group_show_line_subtotals_tax_included"> | <span
+ t-esc="current_subtotal_tax_included"
+ t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
+ /></span>
</td>
</tr>
</t>
diff --git a/addons/sale/views/sale_portal_templates.xml b/addons/sale/views/sale_portal_templates.xml
index 66fd9f388fa..410d378edbc 100644
--- a/addons/sale/views/sale_portal_templates.xml
+++ b/addons/sale/views/sale_portal_templates.xml
@@ -423,29 +423,26 @@
<table class="table table-sm" id="sales_order_table">
<thead class="bg-100">
<tr>
- <t t-set="colspan" t-value="6"/>
<th class="text-left">Products</th>
<th class="text-right">Quantity</th>
<th t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">Unit Price</th>
<th t-if="display_discount" t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">
<span>Discount</span>
- <t t-set="colspan" t-value="colspan+1"/>
</th>
<th t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">Taxes</th>
- <th class="text-right" >
- <span groups="account.group_show_line_subtotals_tax_excluded">Amount</span>
- <span groups="account.group_show_line_subtotals_tax_included">Total Price</span>
- </th>
+ <th class="text-right" groups="account.group_show_line_subtotals_tax_excluded">Amount</th>
+ <th class="text-right" groups="account.group_show_line_subtotals_tax_included">Total Price</th>
</tr>
</thead>
<tbody class="sale_tbody">
- <t t-set="current_subtotal" t-value="0"/>
+ <t t-set="current_subtotal_tax_excluded" t-value="0"/>
+ <t t-set="current_subtotal_tax_included" t-value="0"/>
<t t-foreach="sale_order.order_line" t-as="line">
- <t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
- <t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
+ <t t-set="current_subtotal_tax_excluded" t-value="current_subtotal_tax_excluded + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
+ <t t-set="current_subtotal_tax_included" t-value="current_subtotal_tax_included + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''">
<t t-if="not line.display_type">
@@ -476,20 +473,23 @@
<td t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_id))"/>
</td>
- <td class="text-right">
- <span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
- <span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
+ <td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_excluded">
+ <span t-field="line.price_subtotal"/>
+ </td>
+ <td class="text-right o_price_total" groups="account.group_show_line_subtotals_tax_included">
+ <span t-field="line.price_total"/>
</td>
</t>
<t t-if="line.display_type == 'line_section'">
- <td t-att-colspan="colspan">
+ <td colspan="99">
<span t-field="line.name"/>
</td>
<t t-set="current_section" t-value="line"/>
- <t t-set="current_subtotal" t-value="0"/>
+ <t t-set="current_subtotal_tax_excluded" t-value="0"/>
+ <t t-set="current_subtotal_tax_included" t-value="0"/>
</t>
<t t-if="line.display_type == 'line_note'">
- <td t-att-colspan="colspan">
+ <td colspan="99">
<span t-field="line.name"/>
</td>
</t>
@@ -497,12 +497,17 @@
<t t-if="current_section and (line_last or sale_order.order_line[line_index+1].display_type == 'line_section')">
<tr class="is-subtotal text-right">
- <td t-att-colspan="colspan">
+ <td colspan="99">
<strong class="mr16">Subtotal</strong>
<span
- t-esc="current_subtotal"
- t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
+ groups="account.group_show_line_subtotals_tax_excluded"
+ t-esc="current_subtotal_tax_excluded"
+ t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
+ <span groups="account.group_show_line_subtotals_tax_included"> | <span
+ t-esc="current_subtotal_tax_included"
+ t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
+ /></span>
</td>
</tr>
</t>
diff --git a/addons/stock/wizard/stock_warn_insufficient_qty.py b/addons/stock/wizard/stock_warn_insufficient_qty.py
index 50247d130bf..3e480c73e83 100644
--- a/addons/stock/wizard/stock_warn_insufficient_qty.py
+++ b/addons/stock/wizard/stock_warn_insufficient_qty.py
@@ -34,3 +34,7 @@ class StockWarnInsufficientQtyScrap(models.TransientModel):
def action_done(self):
return self.scrap_id.do_scrap()
+
+ def action_cancel(self):
+ # FIXME in master: we should not have created the scrap in a first place
+ return self.scrap_id.sudo().unlink()
diff --git a/addons/stock/wizard/stock_warn_insufficient_qty_views.xml b/addons/stock/wizard/stock_warn_insufficient_qty_views.xml
index f6fb8eee99e..01de1f8e482 100644
--- a/addons/stock/wizard/stock_warn_insufficient_qty_views.xml
+++ b/addons/stock/wizard/stock_warn_insufficient_qty_views.xml
@@ -44,6 +44,9 @@
<xpath expr="//field[@name='product_id']" position="after">
<field name="scrap_id" invisible="1"/>
</xpath>
+ <xpath expr="//button[@name='cancel_button']" position="replace">
+ <button string="Discard" name="action_cancel" type="object" class="btn-primary"/>
+ </xpath>
</field>
</record>
</odoo>
diff --git a/addons/web/static/src/js/core/dialog.js b/addons/web/static/src/js/core/dialog.js
index 83eded92c8a..3c1013c2944 100644
--- a/addons/web/static/src/js/core/dialog.js
+++ b/addons/web/static/src/js/core/dialog.js
@@ -229,13 +229,13 @@ var Dialog = Widget.extend({
this.$modal.remove();
}
- if (!isFocusSet) {
- var modals = $('body > .modal').filter(':visible');
- if (modals.length) {
+ var modals = $('body > .modal').filter(':visible');
+ if (modals.length) {
+ if (!isFocusSet) {
modals.last().focus();
- // Keep class modal-open (deleted by bootstrap hide fnct) on body to allow scrolling inside the modal
- $('body').addClass('modal-open');
}
+ // Keep class modal-open (deleted by bootstrap hide fnct) on body to allow scrolling inside the modal
+ $('body').addClass('modal-open');
}
},
/**
diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js
index 7c06a7d818d..2b771ba9772 100644
--- a/addons/web/static/src/js/views/calendar/calendar_model.js
+++ b/addons/web/static/src/js/views/calendar/calendar_model.js
@@ -75,9 +75,11 @@ return AbstractModel.extend({
.utc();
} else {
// default hours in the user's timezone
- start.hours(7).add(-this.getSession().getTZOffset(start), 'minutes');
- end.hours(19).add(-this.getSession().getTZOffset(end), 'minutes');
+ start.hours(7);
+ end.hours(19);
}
+ start.add(-this.getSession().getTZOffset(start), 'minutes');
+ end.add(-this.getSession().getTZOffset(end), 'minutes');
}
} else {
start.add(-this.getSession().getTZOffset(start), 'minutes');
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 4d0f89c1976..b79e0d4a660 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -283,6 +283,67 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('focus when closing many2one modal in many2one modal', function (assert) {
+ assert.expect(12);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="trululu"/>' +
+ '</form>',
+ res_id: 2,
+ archs: {
+ 'partner,false,form': '<form><field name="trululu"/></form>'
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'get_formview_id') {
+ return $.when(false);
+ }
+ return this._super(route, args);
+ },
+ });
+
+ // Open many2one modal
+ form.$buttons.find('.btn.o_form_button_edit').click();
+ form.$('.o_external_button').click();
+ var $originalModal = $('.modal-dialog');
+ var $focusedModal = $(document.activeElement).closest('.modal-dialog');
+
+ assert.equal($originalModal.length, 1, 'There should be one modal');
+ assert.equal($originalModal[0], $focusedModal[0], 'Modal is focused');
+ assert.ok($('body').hasClass('modal-open'), 'Modal is said opened');
+
+ // Open many2one modal of field in many2one modal
+ $originalModal.find('.o_external_button').click();
+ var $modals = $('.modal-dialog');
+ $focusedModal = $(document.activeElement).closest('.modal-dialog');
+
+ assert.equal($modals.length, 2, 'There should be two modals');
+ assert.equal($modals[1], $focusedModal[0], 'Last modal is focused');
+ assert.ok($('body').hasClass('modal-open'), 'Modal is said opened');
+
+ // Close second modal
+ $modals.last().find('button[class="close"]').click();
+ var $modal = $('.modal-dialog');
+ $focusedModal = $(document.activeElement).closest('.modal-dialog');
+
+ assert.equal($modal.length, 1, 'There should be one modal');
+ assert.equal($modal[0], $originalModal[0], 'First modal is still opened');
+ assert.equal($modal[0], $focusedModal[0], 'Modal is focused');
+ assert.ok($('body').hasClass('modal-open'), 'Modal is said opened');
+
+ // Close first modal
+ $modal.find('button[class="close"]').click();
+ $modal = $('.modal-dialog.modal-lg');
+
+ assert.equal($modal.length, 0, 'There should be no modal');
+ assert.notOk($('body').hasClass('modal-open'), 'Modal is not said opened');
+
+ form.destroy();
+ });
+
QUnit.test('one2many from a model that has been sorted', function (assert) {
assert.expect(1);
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index 222afc7b9dd..4502211dabe 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -2279,6 +2279,60 @@ QUnit.module('Views', {
calendar.destroy();
});
+ QUnit.test('timezone does not affect drag and drop', function (assert) {
+ assert.expect(6);
+
+ var calendar = createView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch:
+ '<calendar date_start="start" mode="month">'+
+ '<field name="name"/>'+
+ '<field name="start"/>'+
+ '</calendar>',
+ archs: archs,
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ mockRPC: function (route, args) {
+ if (args.method === "write") {
+ assert.deepEqual(args.args[0], [6], "event 6 is moved")
+ assert.deepEqual(args.args[1].start, "2016-11-29 08:00:00",
+ "event moved to 27th nov 16h00 +40 hours timezone")
+ }
+ return this._super(route, args);
+ },
+ session: {
+ getTZOffset: function () {
+ return -2400; // 40 hours timezone
+ },
+ },
+ });
+
+ var events = calendar.$('.fc-event').map(function () {
+ return $(this).text().trim().replace(/\s+/g, '|');
+ });
+
+ assert.strictEqual(events[0], "event|1|12/09/2016|08:00:00");
+ assert.strictEqual(events[5], "event|6|12/16/2016|16:00:00");
+
+ // Move event 6 as on first day of month view (27th november 2016)
+ testUtils.dragAndDrop(
+ calendar.$('.fc-event').eq(5),
+ calendar.$('.fc-day-top').first()
+ );
+
+ var events = calendar.$('.fc-event').map(function () {
+ return $(this).text().trim().replace(/\s+/g, '|');
+ });
+
+ assert.strictEqual(events[0], "event|6|11/27/2016|16:00:00");
+ assert.strictEqual(events[1], "event|1|12/09/2016|08:00:00");
+
+ calendar.destroy();
+ });
+
QUnit.test('form_view_id attribute works (for creating events)', function (assert) {
assert.expect(1);
diff --git a/addons/website/static/src/scss/website.backend.scss b/addons/website/static/src/scss/website.backend.scss
index f669a8f4644..d066b2e0bd0 100644
--- a/addons/website/static/src/scss/website.backend.scss
+++ b/addons/website/static/src/scss/website.backend.scss
@@ -224,7 +224,6 @@
.ActiveUsers {
position: relative;
float: right;
- top: -60px;
right: 10px;
border: 1px solid gray('200');
font-weight: 300;
diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml
index 8c5724945d7..817387db989 100644
--- a/addons/website_blog/views/website_blog_templates.xml
+++ b/addons/website_blog/views/website_blog_templates.xml
@@ -437,10 +437,10 @@
<h4>Archives</h4>
<ul class="nav nav-pills flex-column">
<t t-foreach="nav_list" t-as="year">
- <li data-toggle="collapse" t-att-data-target="'#' + year" aria-expanded="false" class="nav-item blog_post_year_collapse mt8">
+ <li data-toggle="collapse" t-att-data-target="'#b_' + year" aria-expanded="false" class="nav-item blog_post_year_collapse mt8">
<i t-attf-class="fa #{'fa-chevron-down' if not date and year_index == 0 else 'fa-chevron-right'} mr4" role="img" aria-label="Next year" title="Next year"/><span t-esc="year"/>
</li>
- <ul t-attf-class="blog_post_year collapse nav nav-pills flex-column #{not date and year_index == 0 and 'in'}" t-att-id="year" role="menu">
+ <ul t-attf-class="blog_post_year collapse nav nav-pills flex-column #{not date and year_index == 0 and 'in'}" t-att-id="'b_' + year" role="menu">
<t t-foreach="nav_list[year]" t-as="months">
<li t-if="months['date_begin'] == date" class="nav-item">
<a role="menuitem" t-ignore="True" t-attf-href="#{blog_url(date_begin=False, date_end=False)}" class="nav-link active"><t t-esc="months['month']"/><span class="float-right badge badge-pill" t-esc="months['post_date_count']"/></a>
diff --git a/addons/website_sale/data/data.xml b/addons/website_sale/data/data.xml
index 7d70a0b45eb..21e1ea7cf5c 100644
--- a/addons/website_sale/data/data.xml
+++ b/addons/website_sale/data/data.xml
@@ -36,7 +36,7 @@
</record>
<record model="product.pricelist" id="product.list0">
- <field name="selectable" eval="False" />
+ <field name="selectable" eval="True" />
<field name="website_id" eval="False"/>
</record>
</data>
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index 0d95368a223..2d6704bc6ab 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -318,7 +318,8 @@ class SaleOrder(models.Model):
@api.multi
def action_recovery_email_send(self):
- self._portal_ensure_token()
+ for order in self:
+ order._portal_ensure_token()
composer_form_view_id = self.env.ref('mail.email_compose_message_wizard_form').id
try:
default_template = self.env.ref('website_sale.mail_template_sale_cart_recovery', raise_if_not_found=False)
diff --git a/odoo/addons/base/models/ir_actions_report.py b/odoo/addons/base/models/ir_actions_report.py
index 4136b4348f6..6987ca911d6 100644
--- a/odoo/addons/base/models/ir_actions_report.py
+++ b/odoo/addons/base/models/ir_actions_report.py
@@ -725,11 +725,15 @@ class IrActionsReport(models.Model):
return self.render_template(self.report_name, data), 'html'
@api.model
+ def _get_rendering_context_model(self):
+ report_model_name = 'report.%s' % self.report_name
+ return self.env.get(report_model_name)
+
+ @api.model
def _get_rendering_context(self, docids, data):
# If the report is using a custom model to render its html, we must use it.
# Otherwise, fallback on the generic html rendering.
- report_model_name = 'report.%s' % self.report_name
- report_model = self.env.get(report_model_name)
+ report_model = self._get_rendering_context_model()
data = data and dict(data) or {}
diff --git a/odoo/addons/test_translation_import/tests/test_term_count.py b/odoo/addons/test_translation_import/tests/test_term_count.py
index bde7bceac4f..58716ad6ec5 100644
--- a/odoo/addons/test_translation_import/tests/test_term_count.py
+++ b/odoo/addons/test_translation_import/tests/test_term_count.py
@@ -5,7 +5,7 @@ import base64
import io
import odoo
-from odoo.tests import common
+from odoo.tests import common, tagged
from odoo.tools.misc import file_open, mute_logger
from odoo.tools.translate import _
@@ -23,6 +23,18 @@ class TestTermCount(common.TransactionCase):
])
self.assertEqual(len(ids), 2)
+ def test_count_term_module(self):
+ """
+ Just make sure we have as many translation entries as we wanted and module deducted from file content
+ """
+ odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
+ translations = self.env['ir.translation'].search([
+ ('lang', '=', 'fr_FR'),
+ ('src', '=', 'Ijkl'),
+ ('module', '=', 'test_translation_import'),
+ ])
+ self.assertEqual(len(translations), 1)
+
def test_noupdate(self):
"""
Make sure no update do not overwrite translations
@@ -167,3 +179,49 @@ class TestTermCount(common.TransactionCase):
self.env.context = dict(self.env.context, lang="dot")
self.assertEqual(_("Accounting"), "samva", "The code translation was not applied")
+
+@tagged('post_install', '-at_install')
+class TestTranslationFlow(common.TransactionCase):
+
+ def test_export_import(self):
+ """ Ensure export+import gives the same result as loading a language """
+ # load language and generate missing terms to create missing empty terms
+ with mute_logger('odoo.addons.base.models.ir_translation'):
+ self.env["base.language.install"].create({'lang': 'fr_FR'}).lang_install()
+ self.env["base.update.translations"].create({'lang': 'fr_FR'}).act_update()
+
+ translations = self.env["ir.translation"].search([
+ ('lang', '=', 'fr_FR'),
+ ('module', '=', 'test_translation_import')
+ ])
+
+ # minus 3 as the original fr.po contains 3 fake code translations (cf
+ # test_no_duplicate test) which are not found by babel_extract_terms
+ init_translation_count = len(translations) - 3
+
+ module = self.env.ref('base.module_test_translation_import')
+ export = self.env["base.language.export"].create({
+ 'lang': 'fr_FR',
+ 'format': 'po',
+ 'modules': [(6, 0, [module.id])]
+ })
+ export.act_getfile()
+ po_file = export.data
+ self.assertIsNotNone(po_file)
+
+ translations.unlink()
+
+ import_fr = self.env["base.language.import"].create({
+ 'name': 'French',
+ 'code': 'fr_FR',
+ 'data': export.data,
+ 'filename': export.name,
+ })
+ with mute_logger('odoo.addons.base.models.res_lang'):
+ import_fr.with_context(create_empty_translation=True).import_lang()
+
+ import_translation = self.env["ir.translation"].search([
+ ('lang', '=', 'fr_FR'),
+ ('module', '=', 'test_translation_import')
+ ])
+ self.assertEqual(init_translation_count, len(import_translation))
diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py
index 5760a39f61c..16ce6af0bfd 100644
--- a/odoo/tools/translate.py
+++ b/odoo/tools/translate.py
@@ -509,7 +509,7 @@ class PoFile(object):
return self.lines_count - len(self.lines)
def next(self):
- trans_type = name = res_id = source = trad = None
+ trans_type = name = res_id = source = trad = module = None
if self.extra_lines:
trans_type, name, res_id, source, trad, comments = self.extra_lines.pop(0)
if not res_id:
@@ -530,6 +530,8 @@ class PoFile(object):
line = line[2:].strip()
if not line.startswith('module:'):
comments.append(line)
+ else:
+ module = line[7:].strip()
elif line.startswith('#:'):
# Process the `reference` comments. Each line can specify
# multiple targets (e.g. model, view, code, selection,
@@ -603,7 +605,7 @@ class PoFile(object):
_logger.warning('Missing "#:" formatted comment at line %d for the following source:\n\t%s',
self.cur_line(), source[:30])
return next(self)
- return trans_type, name, res_id, source, trad, '\n'.join(comments)
+ return trans_type, name, res_id, source, trad, '\n'.join(comments), module
__next__ = next
def write_infos(self, modules):
@@ -1045,7 +1047,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
elif fileformat == 'po':
reader = PoFile(fileobj)
- fields = ['type', 'name', 'res_id', 'src', 'value', 'comments']
+ fields = ['type', 'name', 'res_id', 'src', 'value', 'comments', 'module']
# Make a reader for the POT file and be somewhat defensive for the
# stable branch.
@@ -1079,7 +1081,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
self.comments = None
pot_targets = defaultdict(Target)
- for type, name, res_id, src, _ignored, comments in pot_reader:
+ for type, name, res_id, src, _ignored, comments, module in pot_reader:
if type is not None:
target = pot_targets[src]
target.targets.add((type, name, type != 'code' and res_id or 0))