summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-09-23 17:40:06 +0000
committerChristophe Simonis <chs@odoo.com>2019-09-24 09:44:56 +0000
commitac4fc6e7c7d71c3f8e2440a8ed8095c5aab15dd8 (patch)
treee143d6d261f22cb06f2c0d5b344a4353e5b3be1b /addons
parenta6a3df3d0eda0fc2476c59de61d59534815f7c20 (diff)
parentbd3e510edbcd62f508dae28f573ee4d1a51bcf87 (diff)
[MERGE] forward port branch saas-12.3 up to bd3e510edbc
closes odoo/odoo#37314 Signed-off-by: Christophe Simonis <chs@odoo.com>
Diffstat (limited to 'addons')
-rw-r--r--addons/account/i18n/account.pot6
-rw-r--r--addons/account/models/account_move.py12
-rw-r--r--addons/account/models/account_reconcile_model.py8
-rw-r--r--addons/account/models/reconciliation_widget.py11
-rw-r--r--addons/account/models/res_users.py8
-rw-r--r--addons/account/tests/test_reconciliation_widget.py30
-rw-r--r--addons/account/views/account_view.xml2
-rw-r--r--addons/auth_signup/models/res_partner.py2
-rw-r--r--addons/digest/data/res_config_settings_data.xml6
-rw-r--r--addons/event/controllers/main.py2
-rw-r--r--addons/l10n_fr/data/account_fiscal_position_template_data.xml80
-rw-r--r--addons/l10n_fr/data/account_tax_data.xml415
-rw-r--r--addons/l10n_in/data/account_data.xml6
-rw-r--r--addons/l10n_in/data/account_tax_template_data.xml755
-rw-r--r--addons/l10n_it_edi/models/account_invoice.py5
-rw-r--r--addons/l10n_it_edi/models/ir_mail_server.py14
-rw-r--r--addons/mail/static/src/js/discuss.js134
-rw-r--r--addons/mail/static/src/js/services/mail_document_thread_manager.js2
-rw-r--r--addons/mail/static/src/js/services/mail_manager.js7
-rw-r--r--addons/mail/static/src/js/systray/systray_activity_menu.js3
-rw-r--r--addons/mail/static/src/js/systray/systray_messaging_menu.js25
-rw-r--r--addons/mail/static/src/js/thread_field.js26
-rw-r--r--addons/mail/static/src/scss/discuss.scss12
-rw-r--r--addons/mail/static/src/xml/discuss.xml9
-rw-r--r--addons/mail/static/src/xml/systray.xml2
-rw-r--r--addons/mail/static/tests/discuss_tests.js50
-rw-r--r--addons/mail/static/tests/systray/systray_messaging_menu_tests.js47
-rw-r--r--addons/mrp/models/mrp_production.py1
-rw-r--r--addons/mrp/models/product.py5
-rw-r--r--addons/payment/models/payment_acquirer.py14
-rw-r--r--addons/payment_stripe_sca/models/payment.py41
-rw-r--r--addons/point_of_sale/models/pos_order.py2
-rw-r--r--addons/product/models/product_pricelist.py6
-rw-r--r--addons/product/models/product_template.py2
-rw-r--r--addons/snailmail/models/res_partner.py19
-rw-r--r--addons/stock/models/stock_move.py6
-rw-r--r--addons/stock/models/stock_picking.py36
-rw-r--r--addons/stock/tests/test_move2.py14
-rw-r--r--addons/survey/controllers/main.py16
-rw-r--r--addons/survey/static/src/js/survey.js10
-rw-r--r--addons/web/static/src/css/reset.min.css4
-rw-r--r--addons/web/static/src/js/core/dom.js2
-rw-r--r--addons/web/static/src/js/fields/abstract_field.js6
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js5
-rw-r--r--addons/web/static/src/js/fields/field_utils.js4
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js92
-rw-r--r--addons/web/static/src/js/views/list/list_controller.js160
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js72
-rw-r--r--addons/web/static/src/js/views/list/list_model.js10
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js21
-rw-r--r--addons/web/static/src/js/views/view_dialogs.js5
-rw-r--r--addons/web/static/src/js/widgets/date_picker.js9
-rw-r--r--addons/web/static/src/xml/base.xml4
-rw-r--r--addons/web/static/tests/fields/basic_fields_tests.js132
-rw-r--r--addons/web/static/tests/fields/field_utils_tests.js34
-rw-r--r--addons/web/static/tests/fields/relational_fields/field_many2one_tests.js75
-rw-r--r--addons/web/static/tests/views/list_tests.js398
-rw-r--r--addons/web/static/tests/views/search_view_tests.js1
-rw-r--r--addons/web/static/tests/widgets/domain_selector_tests.js1
-rw-r--r--addons/website/static/src/scss/website.scss8
-rw-r--r--addons/website_event_questions/report/report_event_question_view.xml3
-rw-r--r--addons/website_sale/data/mail_template_data.xml2
-rw-r--r--addons/website_sale_wishlist/controllers/main.py2
63 files changed, 2506 insertions, 395 deletions
diff --git a/addons/account/i18n/account.pot b/addons/account/i18n/account.pot
index 43deedf6f9d..1ea910942bf 100644
--- a/addons/account/i18n/account.pot
+++ b/addons/account/i18n/account.pot
@@ -3856,6 +3856,12 @@ msgid ""
msgstr ""
#. module: account
+#: code:addons/account/models/account_move.py:408
+#, python-format
+msgid "Difference debit - credit: "
+msgstr ""
+
+#. module: account
#: model:ir.model,name:account.model_digest_digest
msgid "Digest"
msgstr ""
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 8a6de4e1ccf..b0f559a283b 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -1224,10 +1224,15 @@ class AccountMove(models.Model):
lang_env = move.with_context(lang=move.partner_id.lang).env
tax_lines = move.line_ids.filtered(lambda line: line.tax_line_id)
res = {}
+ # There are as many tax line as there are repartition lines
+ done_taxes = set()
for line in tax_lines:
res.setdefault(line.tax_line_id.tax_group_id, {'base': 0.0, 'amount': 0.0})
res[line.tax_line_id.tax_group_id]['amount'] += line.price_subtotal
- res[line.tax_line_id.tax_group_id]['base'] += line.tax_base_amount
+ if line.tax_line_id.id not in done_taxes:
+ # The base should be added ONCE
+ res[line.tax_line_id.tax_group_id]['base'] += line.tax_base_amount
+ done_taxes.add(line.tax_line_id.id)
res = sorted(res.items(), key=lambda l: l[0].sequence)
move.amount_by_group = [(
group.name, amounts['amount'],
@@ -1304,7 +1309,7 @@ class AccountMove(models.Model):
# are already done. Then, this query MUST NOT depend of computed stored fields (e.g. balance).
# It happens as the ORM makes the create with the 'no_recompute' statement.
self._cr.execute('''
- SELECT line.move_id
+ SELECT line.move_id, ROUND(SUM(debit - credit), currency.decimal_places)
FROM account_move_line line
JOIN account_move move ON move.id = line.move_id
JOIN account_journal journal ON journal.id = move.journal_id
@@ -1318,7 +1323,8 @@ class AccountMove(models.Model):
query_res = self._cr.fetchall()
if query_res:
ids = [res[0] for res in query_res]
- raise UserError(_("Cannot create unbalanced journal entry. Ids: %s") % str(ids))
+ sums = [res[1] for res in query_res]
+ raise UserError(_("Cannot create unbalanced journal entry. Ids: %s\nDifferences debit - credit: %s") % (ids, sums))
@api.multi
def _check_fiscalyear_lock_date(self):
diff --git a/addons/account/models/account_reconcile_model.py b/addons/account/models/account_reconcile_model.py
index 6c7b28d89c7..43651c74bcd 100644
--- a/addons/account/models/account_reconcile_model.py
+++ b/addons/account/models/account_reconcile_model.py
@@ -229,6 +229,7 @@ class AccountReconcileModel(models.Model):
'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)],
'debit': line_balance > 0 and line_balance or 0,
'credit': line_balance < 0 and -line_balance or 0,
+ 'reconcile_model_id': self.id,
}
new_aml_dicts.append(writeoff_line)
@@ -243,7 +244,11 @@ class AccountReconcileModel(models.Model):
# Second write-off line.
if self.has_second_line and self.second_account_id:
- line_balance = balance - sum(aml['debit'] - aml['credit'] for aml in new_aml_dicts)
+ remaining_balance = balance - sum(aml['debit'] - aml['credit'] for aml in new_aml_dicts)
+ if self.second_amount_type == 'percentage':
+ line_balance = remaining_balance * (self.second_amount / 100.0)
+ else:
+ line_balance = self.second_amount * (1 if remaining_balance > 0.0 else -1)
second_writeoff_line = {
'name': self.second_label or st_line.name,
'account_id': self.second_account_id.id,
@@ -251,6 +256,7 @@ class AccountReconcileModel(models.Model):
'analytic_tag_ids': [(6, 0, self.second_analytic_tag_ids.ids)],
'debit': line_balance > 0 and line_balance or 0,
'credit': line_balance < 0 and -line_balance or 0,
+ 'reconcile_model_id': self.id,
}
new_aml_dicts.append(second_writeoff_line)
diff --git a/addons/account/models/reconciliation_widget.py b/addons/account/models/reconciliation_widget.py
index 85a755c2920..0544a413acd 100644
--- a/addons/account/models/reconciliation_widget.py
+++ b/addons/account/models/reconciliation_widget.py
@@ -522,22 +522,16 @@ class AccountReconciliation(models.AbstractModel):
])
domain = expression.OR([domain_reconciliation, domain_matching])
- partner_domain = []
if partner_id:
- partner_domain = [('partner_id', '=', partner_id)]
- domain = expression.AND([domain, partner_domain])
+ domain = expression.AND([domain, [('partner_id', '=', partner_id)]])
# Domain factorized for all reconciliation use cases
if search_str:
str_domain = self._domain_move_lines(search_str=search_str)
- if not partner_id:
- partner_domain = [('partner_id.name', 'ilike', search_str)]
-
str_domain = expression.OR([
str_domain,
- partner_domain,
+ [('partner_id.name', 'ilike', search_str)]
])
-
domain = expression.AND([
domain,
str_domain
@@ -553,7 +547,6 @@ class AccountReconciliation(models.AbstractModel):
if st_line.company_id.account_bank_reconciliation_start:
domain = expression.AND([domain, [('date', '>=', st_line.company_id.account_bank_reconciliation_start)]])
-
return domain
@api.model
diff --git a/addons/account/models/res_users.py b/addons/account/models/res_users.py
index 013acde54ff..cc423c2d3a5 100644
--- a/addons/account/models/res_users.py
+++ b/addons/account/models/res_users.py
@@ -13,8 +13,12 @@ class Users(models.Model):
def _check_one_user_type(self):
super(Users, self)._check_one_user_type()
- g1 = self.env.ref('account.group_show_line_subtotals_tax_included')
- g2 = self.env.ref('account.group_show_line_subtotals_tax_excluded')
+ g1 = self.env.ref('account.group_show_line_subtotals_tax_included', False)
+ g2 = self.env.ref('account.group_show_line_subtotals_tax_excluded', False)
+
+ if not g1 or not g2:
+ # A user cannot be in a non-existant group
+ return
if self._has_multiple_groups([g1.id, g2.id]):
raise ValidationError(_("A user cannot have both Tax B2B and Tax B2C.\n"
diff --git a/addons/account/tests/test_reconciliation_widget.py b/addons/account/tests/test_reconciliation_widget.py
index a588fccd42c..ebd23ddd434 100644
--- a/addons/account/tests/test_reconciliation_widget.py
+++ b/addons/account/tests/test_reconciliation_widget.py
@@ -57,6 +57,7 @@ class TestReconciliationWidget(TestReconciliation):
def test_filter_partner1(self):
inv1 = self.create_invoice(currency_id=self.currency_euro_id)
inv2 = self.create_invoice(currency_id=self.currency_euro_id)
+ partner = inv1.partner_id
receivable1 = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
receivable2 = inv2.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
@@ -75,7 +76,31 @@ class TestReconciliationWidget(TestReconciliation):
'date': time.strftime('%Y-07-15'),
})
- # This is like opening the widget, and type "deco" in the filter
+ # This is like input a partner in the widget
+ mv_lines_rec = self.env['account.reconciliation.widget'].get_move_lines_for_bank_statement_line(
+ bank_stmt_line.id,
+ partner_id=partner.id,
+ excluded_ids=[],
+ search_str=False
+ )
+ mv_lines_ids = [l['id'] for l in mv_lines_rec]
+
+ self.assertTrue(receivable1.id in mv_lines_ids)
+ self.assertTrue(receivable2.id in mv_lines_ids)
+
+ # With a partner set, type the invoice reference in the filter
+ mv_lines_rec = self.env['account.reconciliation.widget'].get_move_lines_for_bank_statement_line(
+ bank_stmt_line.id,
+ partner_id=partner.id,
+ excluded_ids=[],
+ search_str=inv1.invoice_payment_ref,
+ )
+ mv_lines_ids = [l['id'] for l in mv_lines_rec]
+
+ self.assertTrue(receivable1.id in mv_lines_ids)
+ self.assertFalse(receivable2.id in mv_lines_ids)
+
+ # Without a partner set, type "deco" in the filter
mv_lines_rec = self.env['account.reconciliation.widget'].get_move_lines_for_bank_statement_line(
bank_stmt_line.id,
partner_id=False,
@@ -87,8 +112,7 @@ class TestReconciliationWidget(TestReconciliation):
self.assertTrue(receivable1.id in mv_lines_ids)
self.assertTrue(receivable2.id in mv_lines_ids)
- # This is like clicking on the first receivable
- partner = inv1.partner_id
+ # With a partner set, type "deco" in the filter and click on the first receivable
mv_lines_rec = self.env['account.reconciliation.widget'].get_move_lines_for_bank_statement_line(
bank_stmt_line.id,
partner_id=partner.id,
diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml
index c70b70e7dc9..c709897ca69 100644
--- a/addons/account/views/account_view.xml
+++ b/addons/account/views/account_view.xml
@@ -1128,7 +1128,7 @@ action = model.setting_init_bank_account_action()
<field name="include_base_amount" attrs="{'invisible':[('amount_type','=', 'group')]}" />
<field name="hide_tax_exigibility" invisible="1"/>
<field name="tax_exigibility" widget="radio" attrs="{'invisible':['|', ('amount_type','=', 'group'), ('hide_tax_exigibility', '=', False)]}" groups="account.group_account_user"/>
- <field name="cash_basis_base_account_id" options="{'no_create': True}" attrs="{'invisible': [('tax_exigibility', '=', 'on_invoice')], 'required': [('tax_exigibility', '=', 'on_payment')]}" groups="account.group_account_user"/>
+ <field name="cash_basis_base_account_id" options="{'no_create': True}" attrs="{'invisible': [('tax_exigibility', '=', 'on_invoice')]}" groups="account.group_account_user"/>
<field name="cash_basis_transition_account_id" options="{'no_create': True}" attrs="{'invisible': [('tax_exigibility', '=', 'on_invoice')], 'required': [('tax_exigibility', '=', 'on_payment')]}" groups="account.group_account_user"/>
</group>
</group>
diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py
index c041ffd3ccf..6f484117fa0 100644
--- a/addons/auth_signup/models/res_partner.py
+++ b/addons/auth_signup/models/res_partner.py
@@ -113,7 +113,7 @@ class ResPartner(models.Model):
allow_signup = self.env['res.users']._get_signup_invitation_scope() == 'b2c'
for partner in self:
- if allow_signup and not partner.user_ids:
+ if allow_signup and not partner.sudo().user_ids:
partner = partner.sudo()
partner.signup_prepare()
res[partner.id]['auth_signup_token'] = partner.signup_token
diff --git a/addons/digest/data/res_config_settings_data.xml b/addons/digest/data/res_config_settings_data.xml
index cbd93e4fef0..cacc318daf0 100644
--- a/addons/digest/data/res_config_settings_data.xml
+++ b/addons/digest/data/res_config_settings_data.xml
@@ -1,10 +1,10 @@
<?xml version='1.0' encoding='utf-8'?>
-<odoo>
- <record id="default_emails_digest" model="ir.config_parameter">
+<odoo noupdate="1">
+ <record id="default_emails_digest" model="ir.config_parameter" forcecreate="0">
<field name="key">digest.default_digest_emails</field>
<field name="value">True</field>
</record>
- <record id="default_digest" model="ir.config_parameter">
+ <record id="default_digest" model="ir.config_parameter" forcecreate="0">
<field name="key">digest.default_digest_id</field>
<field name="value" ref="digest.digest_digest_default"/>
</record>
diff --git a/addons/event/controllers/main.py b/addons/event/controllers/main.py
index f844e6a5d51..723d30aaa51 100644
--- a/addons/event/controllers/main.py
+++ b/addons/event/controllers/main.py
@@ -10,8 +10,6 @@ class EventController(Controller):
@route(['''/event/<model("event.event", "[('state', 'in', ('confirm', 'done'))]"):event>/ics'''], type='http', auth="public")
def event_ics_file(self, event, **kwargs):
- if not event or not event.registration_ids:
- return request.not_found()
files = event._get_ics_file()
if not event.id in files:
return NotFound()
diff --git a/addons/l10n_fr/data/account_fiscal_position_template_data.xml b/addons/l10n_fr/data/account_fiscal_position_template_data.xml
index c1b3663520f..c82d89442e7 100644
--- a/addons/l10n_fr/data/account_fiscal_position_template_data.xml
+++ b/addons/l10n_fr/data/account_fiscal_position_template_data.xml
@@ -54,6 +54,11 @@
<field name="tax_src_id" ref="tva_normale_ttc" />
<field name="tax_dest_id" ref="tva_intra_0" />
</record>
+ <record id="fp_tax_template_intraeub2b_vt_intermediaire_encaissement" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_intraeub2b" />
+ <field name="tax_src_id" ref="tva_intermediaire_encaissement" />
+ <field name="tax_dest_id" ref="tva_intra_0" />
+ </record>
<record id="fp_tax_template_intraeub2b_vt_normale_encaissement" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_intraeub2b" />
<field name="tax_src_id" ref="tva_normale_encaissement" />
@@ -64,6 +69,11 @@
<field name="tax_src_id" ref="tva_normale_encaissement_ttc" />
<field name="tax_dest_id" ref="tva_intra_0" />
</record>
+ <record id="fp_tax_template_intraeub2b_vt_intermediaire_encaissement_ttc" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_intraeub2b" />
+ <field name="tax_src_id" ref="tva_intermediaire_encaissement_ttc" />
+ <field name="tax_dest_id" ref="tva_intra_0" />
+ </record>
<!-- Taux DOM-TOM -->
<record id="fp_tax_template_intraeub2b_vt_specifique" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_intraeub2b" />
@@ -146,11 +156,31 @@
<field name="tax_src_id" ref="tva_acq_specifique" />
<field name="tax_dest_id" ref="tva_intra_specifique" />
</record>
+ <record id="fp_tax_template_intraeub2b_ha_encaissement_deduc" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_intraeub2b" />
+ <field name="tax_src_id" ref="tva_acq_encaissement" />
+ <field name="tax_dest_id" ref="tva_intra_normale" />
+ </record>
<record id="fp_tax_template_intraeub2b_ha_specifique_acq" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_intraeub2b" />
<field name="tax_src_id" ref="tva_acq_specifique" />
<field name="tax_dest_id" ref="tva_acq_intra_specifique" />
</record>
+ <record id="fp_tax_template_intraeub2b_ha_encaissement_acq" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_intraeub2b" />
+ <field name="tax_src_id" ref="tva_acq_encaissement" />
+ <field name="tax_dest_id" ref="tva_acq_intra_normale" />
+ </record>
+ <record id="fp_tax_template_intraeub2b_ha_encaissement_deduc" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_intraeub2b" />
+ <field name="tax_src_id" ref="tva_acq_intermediaire_encaissement" />
+ <field name="tax_dest_id" ref="tva_intra_intermediaire" />
+ </record>
+ <record id="fp_tax_template_intraeub2b_ha_encaissement_acq" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_intraeub2b" />
+ <field name="tax_src_id" ref="tva_acq_intermediaire_encaissement" />
+ <field name="tax_dest_id" ref="tva_acq_intra_intermediaire" />
+ </record>
<!-- Taux Intermédiaire -->
<record id="fp_tax_template_intraeub2b_ha_intermediaire_deduc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_intraeub2b" />
@@ -168,22 +198,42 @@
<field name="tax_src_id" ref="tva_acq_reduite" />
<field name="tax_dest_id" ref="tva_intra_reduite" />
</record>
+ <record id="fp_tax_template_intraeub2b_ha_encaissement_reduite_deduc" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_intraeub2b" />
+ <field name="tax_src_id" ref="tva_acq_encaissement_reduite" />
+ <field name="tax_dest_id" ref="tva_intra_reduite" />
+ </record>
<record id="fp_tax_template_intraeub2b_ha_reduite_acq" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_intraeub2b" />
<field name="tax_src_id" ref="tva_acq_reduite" />
<field name="tax_dest_id" ref="tva_acq_intra_reduite" />
</record>
+ <record id="fp_tax_template_intraeub2b_ha_encaissement_reduite_acq" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_intraeub2b" />
+ <field name="tax_src_id" ref="tva_acq_encaissement_reduite" />
+ <field name="tax_dest_id" ref="tva_acq_intra_reduite" />
+ </record>
<!-- Taux super réduit -->
<record id="fp_tax_template_intraeub2b_ha_super_reduite_deduc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_intraeub2b" />
<field name="tax_src_id" ref="tva_acq_super_reduite" />
<field name="tax_dest_id" ref="tva_intra_super_reduite" />
</record>
+ <record id="fp_tax_template_intraeub2b_ha_encaissement_super_reduite_deduc" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_intraeub2b" />
+ <field name="tax_src_id" ref="tva_acq_encaissement_super_reduite" />
+ <field name="tax_dest_id" ref="tva_intra_super_reduite" />
+ </record>
<record id="fp_tax_template_intraeub2b_ha_super_reduite_acq" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_intraeub2b" />
<field name="tax_src_id" ref="tva_acq_super_reduite" />
<field name="tax_dest_id" ref="tva_acq_intra_super_reduite" />
</record>
+ <record id="fp_tax_template_intraeub2b_ha_encaissement_super_reduite_acq" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_intraeub2b" />
+ <field name="tax_src_id" ref="tva_acq_encaissement_super_reduite" />
+ <field name="tax_dest_id" ref="tva_acq_intra_super_reduite" />
+ </record>
<!-- Import/Export + DOM/TOM -->
<!-- ventes -->
@@ -203,11 +253,21 @@
<field name="tax_src_id" ref="tva_normale_encaissement" />
<field name="tax_dest_id" ref="tva_export_0" />
</record>
+ <record id="fp_tax_template_impexp_vt_intermediaire_encaissement" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_import_export" />
+ <field name="tax_src_id" ref="tva_intermediaire_encaissement" />
+ <field name="tax_dest_id" ref="tva_export_0" />
+ </record>
<record id="fp_tax_template_impexp_vt_normale_encaissement_ttc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_import_export" />
<field name="tax_src_id" ref="tva_normale_encaissement_ttc" />
<field name="tax_dest_id" ref="tva_export_0" />
</record>
+ <record id="fp_tax_template_impexp_vt_intermediaire_encaissement_ttc" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_import_export" />
+ <field name="tax_src_id" ref="tva_intermediaire_encaissement_ttc" />
+ <field name="tax_dest_id" ref="tva_export_0" />
+ </record>
<!-- Taux DOM-TOM -->
<record id="fp_tax_template_impexp_vt_specifique" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_import_export" />
@@ -280,6 +340,11 @@
<field name="tax_dest_id" ref="tva_import_0" />
</record>
<!-- Taux DOM-TOM -->
+ <record id="fp_tax_template_impexp_ha_encaissement" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_import_export" />
+ <field name="tax_src_id" ref="tva_acq_encaissement" />
+ <field name="tax_dest_id" ref="tva_import_0" />
+ </record>
<record id="fp_tax_template_impexp_ha_specifique" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_import_export" />
<field name="tax_src_id" ref="tva_acq_specifique" />
@@ -291,12 +356,27 @@
<field name="tax_src_id" ref="tva_acq_intermediaire" />
<field name="tax_dest_id" ref="tva_import_0" />
</record>
+ <record id="fp_tax_template_impexp_ha_intermediaire" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_import_export" />
+ <field name="tax_src_id" ref="tva_acq_intermediaire_encaissement" />
+ <field name="tax_dest_id" ref="tva_import_0" />
+ </record>
<!-- Taux Réduit -->
<record id="fp_tax_template_impexp_ha_reduite" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_import_export" />
<field name="tax_src_id" ref="tva_acq_reduite" />
<field name="tax_dest_id" ref="tva_import_0" />
</record>
+ <record id="fp_tax_template_impexp_ha_encaissement_super_reduite" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_import_export" />
+ <field name="tax_src_id" ref="tva_acq_encaissement_super_reduite" />
+ <field name="tax_dest_id" ref="tva_import_0" />
+ </record>
+ <record id="fp_tax_template_impexp_ha_encaissement_reduite" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_template_import_export" />
+ <field name="tax_src_id" ref="tva_acq_encaissement_reduite" />
+ <field name="tax_dest_id" ref="tva_import_0" />
+ </record>
<!-- Taux super réduit -->
<record id="fp_tax_template_impexp_ha_super_reduite" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_import_export" />
diff --git a/addons/l10n_fr/data/account_tax_data.xml b/addons/l10n_fr/data/account_tax_data.xml
index 3f72c43b63e..41fc79e15a1 100644
--- a/addons/l10n_fr/data/account_tax_data.xml
+++ b/addons/l10n_fr/data/account_tax_data.xml
@@ -46,6 +46,47 @@
]"/>
</record>
+ <record model="account.tax.template" id="tva_intermediaire_encaissement">
+ <field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
+ <field name="name">TVA à l'encaissement (vente) 10,0%</field>
+ <field name="description">TVA 10%</field>
+ <field name="amount" eval="10.0"/>
+ <field name="amount_type">percent</field>
+ <field name="tax_exigibility">on_payment</field>
+ <field name="cash_basis_transition_account_id" ref="pcg_445712" />
+ <field name="sequence" eval="10"/>
+ <field name="type_tax_use">sale</field>
+ <field name="tax_group_id" ref="tax_group_tva_10"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_10')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_10')],
+ 'account_id': ref('pcg_445800'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_10')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_10')],
+ 'account_id': ref('pcg_445712'),
+ }),
+ ]"/>
+ </record>
+
<record model="account.tax.template" id="tva_normale_encaissement">
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
<field name="name">TVA à l'encaissement (vente) 20,0%</field>
@@ -367,6 +408,48 @@
]"/>
</record>
+ <record model="account.tax.template" id="tva_intermediaire_encaissement_ttc">
+ <field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
+ <field name="name">TVA à l'encaissement (vente) 10,0% TTC</field>
+ <field name="description">TVA 10% TTC</field>
+ <field name="price_include" eval="1"/>
+ <field name="amount" eval="10"/>
+ <field name="amount_type">percent</field>
+ <field name="tax_exigibility">on_payment</field>
+ <field name="cash_basis_transition_account_id" ref="pcg_445712" />
+ <field name="sequence" eval="10"/>
+ <field name="type_tax_use">sale</field>
+ <field name="tax_group_id" ref="tax_group_tva_10"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_10')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_10')],
+ 'account_id': ref('pcg_445800'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_10')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_10')],
+ 'account_id': ref('pcg_445712'),
+ }),
+ ]"/>
+ </record>
+
<record model="account.tax.template" id="tva_normale_encaissement_ttc">
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
<field name="name">TVA à l'encaissement (vente) 20,0% TTC</field>
@@ -693,6 +776,88 @@
]"/>
</record>
+ <record model="account.tax.template" id="tva_acq_encaissement">
+ <field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
+ <field name="name">TVA à l'encaissement (achat) 20,0%</field>
+ <field name="description">TVA 20%</field>
+ <field name="amount" eval="20.0"/>
+ <field name="amount_type">percent</field>
+ <field name="tax_exigibility">on_payment</field>
+ <field name="cash_basis_transition_account_id" ref="pcg_44566" />
+ <field name="sequence" eval="10"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="tax_group_id" ref="tax_group_tva_20"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_20')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_20')],
+ 'account_id': ref('pcg_445800'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_20')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_20')],
+ 'account_id': ref('pcg_44566'),
+ }),
+ ]"/>
+ </record>
+
+ <record model="account.tax.template" id="tva_acq_intermediaire_encaissement">
+ <field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
+ <field name="name">TVA à l'encaissement (achat) 10,0%</field>
+ <field name="description">TVA 10%</field>
+ <field name="amount" eval="10.0"/>
+ <field name="amount_type">percent</field>
+ <field name="tax_exigibility">on_payment</field>
+ <field name="cash_basis_transition_account_id" ref="pcg_44566" />
+ <field name="sequence" eval="10"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="tax_group_id" ref="tax_group_tva_10"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_10')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_10')],
+ 'account_id': ref('pcg_445800'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_10')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_10')],
+ 'account_id': ref('pcg_44566'),
+ }),
+ ]"/>
+ </record>
+
<record model="account.tax.template" id="tva_acq_specifique">
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
<field name="name">TVA déductible (achat) 8,5%</field>
@@ -810,6 +975,47 @@
]"/>
</record>
+ <record model="account.tax.template" id="tva_acq_encaissement_reduite">
+ <field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
+ <field name="name">TVA à l'encaissement (achat) 5,5%</field>
+ <field name="description">TVA 5,5%</field>
+ <field name="amount" eval="5.5"/>
+ <field name="amount_type">percent</field>
+ <field name="tax_exigibility">on_payment</field>
+ <field name="cash_basis_transition_account_id" ref="pcg_44566" />
+ <field name="sequence" eval="10"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="tax_group_id" ref="tax_group_tva_55"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_5_5')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_5_5')],
+ 'account_id': ref('pcg_445800'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_5_5')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_5_5')],
+ 'account_id': ref('pcg_44566'),
+ }),
+ ]"/>
+ </record>
+
<record model="account.tax.template" id="tva_acq_super_reduite">
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
<field name="name">TVA déductible (achat) 2,1%</field>
@@ -851,6 +1057,47 @@
<!-- ACHATS Tax included in price -->
+ <record model="account.tax.template" id="tva_acq_encaissement_super_reduite">
+ <field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
+ <field name="name">TVA à l'encaissement (achat) 2,1% </field>
+ <field name="description">TVA 2,1%</field>
+ <field name="amount" eval="2.1"/>
+ <field name="amount_type">percent</field>
+ <field name="tax_exigibility">on_payment</field>
+ <field name="cash_basis_transition_account_id" ref="pcg_44566" />
+ <field name="sequence" eval="10"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="tax_group_id" ref="tax_group_tva_21"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_2_1')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_2_1')],
+ 'account_id': ref('pcg_445800'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_2_1')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_2_1')],
+ 'account_id': ref('pcg_44566'),
+ }),
+ ]"/>
+ </record>
+
<record model="account.tax.template" id="tva_acq_normale_TTC">
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
<field name="name">TVA déductible (achat) 20,0% TTC</field>
@@ -891,6 +1138,48 @@
]"/>
</record>
+ <record model="account.tax.template" id="tva_acq_encaissement_TTC">
+ <field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
+ <field name="name">TVA à l'encaissement (achat) 20,0% TTC</field>
+ <field name="description">TVA 20% TTC</field>
+ <field name="price_include" eval="1"/>
+ <field name="amount" eval="20.0"/>
+ <field name="amount_type">percent</field>
+ <field name="tax_exigibility">on_payment</field>
+ <field name="cash_basis_transition_account_id" ref="pcg_44566" />
+ <field name="sequence" eval="10"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="tax_group_id" ref="tax_group_tva_20"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_20')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_20')],
+ 'account_id': ref('pcg_445800'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_20')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_20')],
+ 'account_id': ref('pcg_44566'),
+ }),
+ ]"/>
+ </record>
+
<record model="account.tax.template" id="tva_acq_specifique_TTC">
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
<field name="name">TVA déductible (achat) 8,5% TTC</field>
@@ -931,6 +1220,48 @@
]"/>
</record>
+ <record model="account.tax.template" id="tva_acq_intermediaire_encaissement_TTC">
+ <field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
+ <field name="name">TVA à l'encaissement (achat) 10,0% TTC</field>
+ <field name="description">TVA 10% TTC</field>
+ <field name="price_include" eval="1"/>
+ <field name="amount" eval="10.0"/>
+ <field name="amount_type">percent</field>
+ <field name="tax_exigibility">on_payment</field>
+ <field name="cash_basis_transition_account_id" ref="pcg_44566" />
+ <field name="sequence" eval="10"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="tax_group_id" ref="tax_group_tva_10"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_10')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_10')],
+ 'account_id': ref('pcg_445800'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_10')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_10')],
+ 'account_id': ref('pcg_44566'),
+ }),
+ ]"/>
+ </record>
+
<record model="account.tax.template" id="tva_acq_intermediaire_TTC">
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
<field name="name">TVA déductible (achat) 10,0% TTC</field>
@@ -1011,6 +1342,48 @@
]"/>
</record>
+ <record model="account.tax.template" id="tva_acq_encaissement_reduite_TTC">
+ <field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
+ <field name="name">TVA à l'encaissement (achat) 5,5% TTC</field>
+ <field name="description">TVA 5,5% TTC</field>
+ <field name="price_include" eval="1"/>
+ <field name="amount" eval="5.5"/>
+ <field name="amount_type">percent</field>
+ <field name="tax_exigibility">on_payment</field>
+ <field name="cash_basis_transition_account_id" ref="pcg_44566" />
+ <field name="sequence" eval="10"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="tax_group_id" ref="tax_group_tva_55"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_5_5')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_5_5')],
+ 'account_id': ref('pcg_445800'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_5_5')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_5_5')],
+ 'account_id': ref('pcg_44566'),
+ }),
+ ]"/>
+ </record>
+
<record model="account.tax.template" id="tva_acq_super_reduite_TTC">
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
<field name="name">TVA déductible (achat) 2,1% TTC</field>
@@ -1051,6 +1424,48 @@
]"/>
</record>
+ <record model="account.tax.template" id="tva_acq_encaissement_super_reduite_TTC">
+ <field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
+ <field name="name">TVA à l'encaissement (achat) 2,1% TTC</field>
+ <field name="description">TVA 2,1% TTC</field>
+ <field name="price_include" eval="1"/>
+ <field name="amount" eval="2.1"/>
+ <field name="amount_type">percent</field>
+ <field name="tax_exigibility">on_payment</field>
+ <field name="cash_basis_transition_account_id" ref="pcg_44566" />
+ <field name="sequence" eval="10"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="tax_group_id" ref="tax_group_tva_21"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_2_1')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'plus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_2_1')],
+ 'account_id': ref('pcg_445800'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_base_coll_2_1')],
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'minus_report_line_ids': [ref('l10n_fr.tax_report_tva_coll_2_1')],
+ 'account_id': ref('pcg_44566'),
+ }),
+ ]"/>
+ </record>
+
<!-- ImmoBILISATIONS (achats) -->
diff --git a/addons/l10n_in/data/account_data.xml b/addons/l10n_in/data/account_data.xml
index c1fb9299c1e..c20cfa8839f 100644
--- a/addons/l10n_in/data/account_data.xml
+++ b/addons/l10n_in/data/account_data.xml
@@ -3,6 +3,12 @@
<data noupdate="1">
<!-- Account Tax Group -->
+ <record id="sgst_group" model="account.tax.group">
+ <field name="name">SGST</field>
+ </record>
+ <record id="cgst_group" model="account.tax.group">
+ <field name="name">CGST</field>
+ </record>
<record id="igst_group" model="account.tax.group">
<field name="name">IGST</field>
</record>
diff --git a/addons/l10n_in/data/account_tax_template_data.xml b/addons/l10n_in/data/account_tax_template_data.xml
index 6c0d3b8892e..374628bb05d 100644
--- a/addons/l10n_in/data/account_tax_template_data.xml
+++ b/addons/l10n_in/data/account_tax_template_data.xml
@@ -459,14 +459,14 @@ if tax > result:result=tax</field>
<!-- SGST & CGST Sales Group Tax -->
- <record id="sgst_sale_1" model="account.tax.template">
- <field name="name">GST 1%</field>
- <field name="description">GST 1%</field>
- <field name="type_tax_use">sale</field>
+ <record id="sgst_sale_0_5" model="account.tax.template">
+ <field name="name">SGST Sale 0.5%</field>
+ <field name="description">SGST 0.5%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">1.0</field>
+ <field name="amount">0.5</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tax_group_id" ref="gst_group"/>
+ <field name="tax_group_id" ref="sgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
@@ -474,17 +474,92 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11232'),
'plus_report_line_ids': [ref('tax_report_line_sgst')],
}),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p10051'),
+ 'minus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="cgst_sale_0_5" model="account.tax.template">
+ <field name="name">CGST Sale 0.5%</field>
+ <field name="description">CGST 0.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">0.5</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="cgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11233'),
'plus_report_line_ids': [ref('tax_report_line_cgst')],
+ })
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
}),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p10052'),
+ 'minus_report_line_ids': [ref('tax_report_line_cgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="sgst_sale_1" model="account.tax.template">
+ <field name="name">GST 1%</field>
+ <field name="description">GST 1%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">1.0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="gst_group"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_0_5'), ref('cgst_sale_0_5'),])]"/>
+ </record>
+
+ <record id="sgst_sale_1_2" model="account.tax.template">
+ <field name="name">SGST Sale 1%</field>
+ <field name="description">SGST 1%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">1</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="sgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p11232'),
+ 'plus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -493,17 +568,45 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10051'),
'minus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="cgst_sale_1_2" model="account.tax.template">
+ <field name="name">CGST Sale 1%</field>
+ <field name="description">CGST 1%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">1</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="cgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p11233'),
+ 'plus_report_line_ids': [ref('tax_report_line_cgst')],
+ })
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10052'),
'minus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
</record>
@@ -511,10 +614,21 @@ if tax > result:result=tax</field>
<field name="name">GST 2%</field>
<field name="description">GST 2%</field>
<field name="type_tax_use">sale</field>
- <field name="amount_type">percent</field>
+ <field name="amount_type">group</field>
<field name="amount">2.0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tax_group_id" ref="gst_group"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_1_2'), ref('cgst_sale_1_2'),])]"/>
+ </record>
+
+ <record id="sgst_sale_14" model="account.tax.template">
+ <field name="name">SGST Sale 14%</field>
+ <field name="description">SGST 14%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">14</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="sgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
@@ -522,17 +636,11 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11232'),
'plus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p11233'),
- 'plus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -541,17 +649,45 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10051'),
'minus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="cgst_sale_14" model="account.tax.template">
+ <field name="name">CGST Sale 14%</field>
+ <field name="description">CGST 14%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">14</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="cgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p11233'),
+ 'plus_report_line_ids': [ref('tax_report_line_cgst')],
+ })
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10052'),
'minus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
</record>
@@ -559,10 +695,21 @@ if tax > result:result=tax</field>
<field name="name">GST 28%</field>
<field name="description">GST 28%</field>
<field name="type_tax_use">sale</field>
- <field name="amount_type">percent</field>
+ <field name="amount_type">group</field>
<field name="amount">28.0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tax_group_id" ref="gst_group"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_14'),ref('cgst_sale_14'),])]"/>
+ </record>
+
+ <record id="sgst_sale_9" model="account.tax.template">
+ <field name="name">SGST Sale 9%</field>
+ <field name="description">SGST 9%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">9</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="sgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
@@ -570,17 +717,11 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11232'),
'plus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p11233'),
- 'plus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -589,17 +730,45 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10051'),
'minus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="cgst_sale_9" model="account.tax.template">
+ <field name="name">CGST Sale 9%</field>
+ <field name="description">CGST 9%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">9</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="cgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p11233'),
+ 'plus_report_line_ids': [ref('tax_report_line_cgst')],
+ })
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+ (0,0, {
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10052'),
'minus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
</record>
@@ -607,43 +776,74 @@ if tax > result:result=tax</field>
<field name="name">GST 18%</field>
<field name="description">GST 18%</field>
<field name="type_tax_use">sale</field>
- <field name="amount_type">percent</field>
+ <field name="amount_type">group</field>
<field name="amount">18.0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tax_group_id" ref="gst_group"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_9'),ref('cgst_sale_9'),])]"/>
+ </record>
+
+ <record id="sgst_sale_6" model="account.tax.template">
+ <field name="name">SGST Sale 6%</field>
+ <field name="description">SGST 6%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">6</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="sgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
}),
-
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11232'),
'plus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p11233'),
- 'plus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
}),
-
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10051'),
'minus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="cgst_sale_6" model="account.tax.template">
+ <field name="name">CGST Sale 6%</field>
+ <field name="description">CGST 6%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">6</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="cgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p11233'),
+ 'plus_report_line_ids': [ref('tax_report_line_cgst')],
+ })
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+ (0,0, {
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10052'),
'minus_report_line_ids': [ref('tax_report_line_cgst')],
@@ -655,10 +855,21 @@ if tax > result:result=tax</field>
<field name="name">GST 12%</field>
<field name="description">GST 12%</field>
<field name="type_tax_use">sale</field>
- <field name="amount_type">percent</field>
+ <field name="amount_type">group</field>
<field name="amount">12.0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tax_group_id" ref="gst_group"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_6'),ref('cgst_sale_6'),])]"/>
+ </record>
+
+ <record id="sgst_sale_2_5" model="account.tax.template">
+ <field name="name">SGST Sale 2.5%</field>
+ <field name="description">SGST 2.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">2.5</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="sgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
@@ -666,17 +877,11 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11232'),
'plus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p11233'),
- 'plus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -685,69 +890,60 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10051'),
'minus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p10052'),
- 'minus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
</record>
- <record id="sgst_sale_5" model="account.tax.template">
- <field name="name">GST 5%</field>
- <field name="description">GST 5%</field>
- <field name="type_tax_use">sale</field>
+ <record id="cgst_sale_2_5" model="account.tax.template">
+ <field name="name">CGST Sale 2.5%</field>
+ <field name="description">CGST 2.5%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">5.0</field>
+ <field name="amount">2.5</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tax_group_id" ref="gst_group"/>
- <field name="sequence">0</field>
+ <field name="tax_group_id" ref="cgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
}),
-
(0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p11232'),
- 'plus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11233'),
'plus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
}),
-
(0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p10051'),
- 'minus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10052'),
'minus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
</record>
+ <record id="sgst_sale_5" model="account.tax.template">
+ <field name="name">GST 5%</field>
+ <field name="description">GST 5%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">5.0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="gst_group"/>
+ <field name="sequence">0</field>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_2_5'), ref('cgst_sale_2_5'),])]"/>
+ </record>
+
<!-- Purchase Taxes-->
<!-- CESS Taxes -->
@@ -1166,97 +1362,232 @@ if tax > result:result=tax</field>
<!-- SGST & CGST -->
- <record id="sgst_purchase_1" model="account.tax.template">
- <field name="name">GST 1%</field>
- <field name="description">GST 1%</field>
- <field name="type_tax_use">purchase</field>
+ <record id="sgst_purchase_0_5" model="account.tax.template">
+ <field name="name">SGST Purchase 0.5%</field>
+ <field name="description">SGST 0.5%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">1.0</field>
+ <field name="amount">0.5</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tax_group_id" ref="gst_group"/>
+ <field name="tax_group_id" ref="sgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10051'),
'plus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p11232'),
+ 'minus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="cgst_purchase_0_5" model="account.tax.template">
+ <field name="name">CGST Purchase 0.5%</field>
+ <field name="description">CGST 0.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">0.5</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="cgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10052'),
'plus_report_line_ids': [ref('tax_report_line_cgst')],
+ })
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
}),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p11233'),
+ 'minus_report_line_ids': [ref('tax_report_line_cgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="sgst_purchase_1" model="account.tax.template">
+ <field name="name">GST 1%</field>
+ <field name="description">GST 1%</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="amount_type">group</field>
+ <field name="amount">1.0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="gst_group"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_0_5'),ref('cgst_purchase_0_5'),])]"/>
+ </record>
+
+ <record id="sgst_purchase_1_2" model="account.tax.template">
+ <field name="name">SGST Purchase 1%</field>
+ <field name="description">SGST 1%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">1</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="sgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p10051'),
+ 'plus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
}),
+
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11232'),
'minus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="cgst_purchase_1_2" model="account.tax.template">
+ <field name="name">CGST Purchase 1%</field>
+ <field name="description">CGST 1%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">1</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="cgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p10052'),
+ 'plus_report_line_ids': [ref('tax_report_line_cgst')],
+ })
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11233'),
'minus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
+
</record>
<record id="sgst_purchase_2" model="account.tax.template">
<field name="name">GST 2%</field>
<field name="description">GST 2%</field>
<field name="type_tax_use">purchase</field>
- <field name="amount_type">percent</field>
+ <field name="amount_type">group</field>
<field name="amount">2.0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tax_group_id" ref="gst_group"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_1_2'),ref('cgst_purchase_1_2'),])]"/>
+ </record>
+
+ <record id="sgst_purchase_14" model="account.tax.template">
+ <field name="name">SGST Purchase 14%</field>
+ <field name="description">SGST 14%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">14</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="sgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
}),
-
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10051'),
'plus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p10052'),
- 'plus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
}),
-
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11232'),
'minus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="cgst_purchase_14" model="account.tax.template">
+ <field name="name">CGST Purchase 14%</field>
+ <field name="description">CGST 14%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">14</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="cgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p10052'),
+ 'plus_report_line_ids': [ref('tax_report_line_cgst')],
+ })
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11233'),
'minus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
</record>
@@ -1264,10 +1595,21 @@ if tax > result:result=tax</field>
<field name="name">GST 28%</field>
<field name="description">GST 28%</field>
<field name="type_tax_use">purchase</field>
- <field name="amount_type">percent</field>
+ <field name="amount_type">group</field>
<field name="amount">28.0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tax_group_id" ref="gst_group"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_14'),ref('cgst_purchase_14'),])]"/>
+ </record>
+
+ <record id="sgst_purchase_9" model="account.tax.template">
+ <field name="name">SGST Purchase 9%</field>
+ <field name="description">SGST 9%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">9</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="sgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
@@ -1275,17 +1617,11 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10051'),
'plus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p10052'),
- 'plus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -1294,17 +1630,45 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11232'),
'minus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="cgst_purchase_9" model="account.tax.template">
+ <field name="name">CGST Purchase 9%</field>
+ <field name="description">CGST 9%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">9</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="cgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p10052'),
+ 'plus_report_line_ids': [ref('tax_report_line_cgst')],
+ })
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+ (0,0, {
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11233'),
'minus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
</record>
@@ -1312,10 +1676,21 @@ if tax > result:result=tax</field>
<field name="name">GST 18%</field>
<field name="description">GST 18%</field>
<field name="type_tax_use">purchase</field>
- <field name="amount_type">percent</field>
+ <field name="amount_type">group</field>
<field name="amount">18.0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tax_group_id" ref="gst_group"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_9'),ref('cgst_purchase_9'),])]"/>
+ </record>
+
+ <record id="sgst_purchase_6" model="account.tax.template">
+ <field name="name">SGST Purchase 6%</field>
+ <field name="description">SGST 6%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">6</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="sgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
@@ -1323,17 +1698,11 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10051'),
'plus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p10052'),
- 'plus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -1342,17 +1711,45 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11232'),
'minus_report_line_ids': [ref('tax_report_line_sgst')],
+ })
+ ]"/>
+ </record>
+
+ <record id="cgst_purchase_6" model="account.tax.template">
+ <field name="name">CGST Purchase 6%</field>
+ <field name="description">CGST 6%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">6</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="cgst_group"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('p10052'),
+ 'plus_report_line_ids': [ref('tax_report_line_cgst')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ }),
+ (0,0, {
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11233'),
'minus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
</record>
@@ -1360,10 +1757,21 @@ if tax > result:result=tax</field>
<field name="name">GST 12%</field>
<field name="description">GST 12%</field>
<field name="type_tax_use">purchase</field>
- <field name="amount_type">percent</field>
+ <field name="amount_type">group</field>
<field name="amount">12.0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tax_group_id" ref="gst_group"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_6'),ref('cgst_purchase_6'),])]"/>
+ </record>
+
+ <record id="sgst_purchase_2_5" model="account.tax.template">
+ <field name="name">SGST Purchase 2.5%</field>
+ <field name="description">SGST 2.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">2.5</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="sgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
@@ -1371,17 +1779,11 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10051'),
'plus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p10052'),
- 'plus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -1390,67 +1792,58 @@ if tax > result:result=tax</field>
}),
(0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11232'),
'minus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p11233'),
- 'minus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
</record>
- <record id="sgst_purchase_5" model="account.tax.template">
- <field name="name">GST 5%</field>
- <field name="description">GST 5%</field>
- <field name="type_tax_use">purchase</field>
+ <record id="cgst_purchase_2_5" model="account.tax.template">
+ <field name="name">CGST Purchase 2.5%</field>
+ <field name="description">CGST 2.5%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">5.0</field>
+ <field name="amount">2.5</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tax_group_id" ref="gst_group"/>
- <field name="sequence">0</field>
+ <field name="tax_group_id" ref="cgst_group"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
}),
-
(0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p10051'),
- 'plus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p10052'),
'plus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
}),
-
(0,0, {
- 'factor_percent': 50,
- 'repartition_type': 'tax',
- 'account_id': ref('p11232'),
- 'minus_report_line_ids': [ref('tax_report_line_sgst')],
- }),
- (0,0, {
- 'factor_percent': 50,
+ 'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('p11233'),
'minus_report_line_ids': [ref('tax_report_line_cgst')],
- }),
+ })
]"/>
</record>
+ <record id="sgst_purchase_5" model="account.tax.template">
+ <field name="name">GST 5%</field>
+ <field name="description">GST 5%</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="amount_type">group</field>
+ <field name="amount">5.0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tax_group_id" ref="gst_group"/>
+ <field name="sequence">0</field>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_2_5'),ref('cgst_purchase_2_5'),])]"/>
+ </record>
+
</odoo>
diff --git a/addons/l10n_it_edi/models/account_invoice.py b/addons/l10n_it_edi/models/account_invoice.py
index 3a8d8361b95..77185ea4e3d 100644
--- a/addons/l10n_it_edi/models/account_invoice.py
+++ b/addons/l10n_it_edi/models/account_invoice.py
@@ -110,7 +110,7 @@ class AccountMove(models.Model):
raise UserError(_("%s must have a country.") % (seller.display_name))
# <1.4.1>
- if not buyer.vat and not buyer.l10n_it_codice_fiscale and buyer.country_id.code != 'IT':
+ if not buyer.vat and not buyer.l10n_it_codice_fiscale and buyer.country_id.code == 'IT':
raise UserError(_("The buyer, %s, or his company must have either a VAT number either a tax code (Codice Fiscale).") % (buyer.display_name))
# <1.4.2>
@@ -273,11 +273,12 @@ class AccountMove(models.Model):
'body': _('Sending file: %s to ES: %s') % (self.l10n_it_einvoice_id.name, self.env.company.l10n_it_address_recipient_fatturapa),
'author_id': self.env.user.partner_id.id,
'email_from': self.env.company.l10n_it_address_send_fatturapa,
+ 'reply_to': self.env.company.l10n_it_address_send_fatturapa,
'mail_server_id': self.env.company.l10n_it_mail_pec_server_id.id,
'attachment_ids': [(6, 0, self.l10n_it_einvoice_id.ids)],
})
- mail_fattura = self.env['mail.mail'].create({
+ mail_fattura = self.env['mail.mail'].with_context(wo_return_path=True).create({
'mail_message_id': message.id,
'email_to': self.env.company.l10n_it_address_recipient_fatturapa,
})
diff --git a/addons/l10n_it_edi/models/ir_mail_server.py b/addons/l10n_it_edi/models/ir_mail_server.py
index 581274ac404..18c4a66af3c 100644
--- a/addons/l10n_it_edi/models/ir_mail_server.py
+++ b/addons/l10n_it_edi/models/ir_mail_server.py
@@ -355,3 +355,17 @@ class FetchmailServer(models.Model):
if descrizione:
output_str += "<li>Errore %s: %s</li>" % (element[0].text, descrizione)
return output_str + "</ul>"
+
+class IrMailServer(models.Model):
+ _name = "ir.mail_server"
+ _inherit = "ir.mail_server"
+
+ def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
+ attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None,
+ body_alternative=None, subtype_alternative='plain'):
+
+ if self.env.context.get('wo_return_path') and headers:
+ headers.pop('Return-Path', False)
+ return super(IrMailServer, self).build_email(email_from, email_to, subject, body, email_cc=email_cc, email_bcc=email_bcc, reply_to=reply_to,
+ attachments=attachments, message_id=message_id, references=references, object_id=object_id, subtype=subtype, headers=headers,
+ body_alternative=body_alternative, subtype_alternative=subtype_alternative)
diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js
index 6eeeaecf7c3..de5ce88b742 100644
--- a/addons/mail/static/src/js/discuss.js
+++ b/addons/mail/static/src/js/discuss.js
@@ -280,6 +280,8 @@ var Discuss = AbstractAction.extend({
this.options.channelQuickSearchThreshold = 20;
}
+ this._isMessagingReady = this.call('mail_service', 'isReady');
+ this._isStarted = false;
this._threadsScrolltop = {};
this._composerStates = {};
this._defaultThreadID = this.options.active_id ||
@@ -291,68 +293,20 @@ var Discuss = AbstractAction.extend({
this._updateThreads.bind(this), 100, { leading: false });
this.controlPanelParams.modelName = 'mail.message';
- },
- /**
- * @override
- */
- willStart: function () {
- return Promise.all([this._super(), this.call('mail_service', 'isReady')]);
+ this.call('mail_service', 'getMailBus').on('messaging_ready', this, this._onMessagingReady);
},
/**
* @override
*/
start: function () {
var self = this;
-
- this._basicComposer = new BasicComposer(this, {
- mentionPartnersRestricted: true,
- showTyping: true,
- });
- this._extendedComposer = new ExtendedComposer(this, {
- mentionPartnersRestricted: true,
- showTyping: true,
+ this._isStarted = true;
+ return this._super.apply(this, arguments).then(function () {
+ if (!self._isMessagingReady) {
+ return;
+ }
+ return self._initRender();
});
- this._basicComposer
- .on('post_message', this, this._onPostMessage)
- .on('input_focused', this, this._onComposerFocused);
- this._extendedComposer
- .on('post_message', this, this._onPostMessage)
- .on('input_focused', this, this._onComposerFocused);
- this._renderButtons();
-
- var defs = [];
- defs.push(
- this._renderThread()
- );
- defs.push(
- this._basicComposer.appendTo(this.$('.o_mail_discuss_content')));
- defs.push(
- this._extendedComposer.appendTo(this.$('.o_mail_discuss_content')));
- defs.push(this._super.apply(this, arguments));
-
- return Promise.all(defs)
- .then(function () {
- return self._setThread(self._defaultThreadID);
- })
- .then(function () {
- self._initThreads();
- self._startListening();
- self._threadWidget.$el.on('scroll', null, _.debounce(function () {
- if (
- self._threadWidget.getScrolltop() < 20 &&
- !self._threadWidget.$('.o_mail_no_content').length &&
- !self._thread.isAllHistoryLoaded(self.domain)
- ) {
- self._loadMoreMessages();
- }
- if (
- self._threadWidget.isAtBottom() &&
- self._thread.getType() !== 'mailbox'
- ) {
- self._thread.markAsRead();
- }
- }, 100));
- });
},
/**
* @override
@@ -381,8 +335,8 @@ var Discuss = AbstractAction.extend({
this.call('mail_service', 'getMailBus').trigger('discuss_open', true);
if (this._thread) {
this._threadWidget.scrollToPosition(this._threadsScrolltop[this._thread.getID()]);
+ this._loadEnoughMessages();
}
- this._loadEnoughMessages();
},
/**
* @override
@@ -547,6 +501,60 @@ var Discuss = AbstractAction.extend({
}
},
/**
+ * @private
+ */
+ _initRender: function () {
+ var self = this;
+ this._basicComposer = new BasicComposer(this, {
+ mentionPartnersRestricted: true,
+ showTyping: true,
+ });
+ this._extendedComposer = new ExtendedComposer(this, {
+ mentionPartnersRestricted: true,
+ showTyping: true,
+ });
+ this._basicComposer
+ .on('post_message', this, this._onPostMessage)
+ .on('input_focused', this, this._onComposerFocused);
+ this._extendedComposer
+ .on('post_message', this, this._onPostMessage)
+ .on('input_focused', this, this._onComposerFocused);
+ this._renderButtons();
+
+ var defs = [];
+ defs.push(
+ this._renderThread()
+ );
+ defs.push(
+ this._basicComposer.appendTo(this.$('.o_mail_discuss_content')));
+ defs.push(
+ this._extendedComposer.appendTo(this.$('.o_mail_discuss_content')));
+
+ return Promise.all(defs)
+ .then(function () {
+ return self._setThread(self._defaultThreadID);
+ })
+ .then(function () {
+ self._initThreads();
+ self._startListening();
+ self._threadWidget.$el.on('scroll', null, _.debounce(function () {
+ if (
+ self._threadWidget.getScrolltop() < 20 &&
+ !self._threadWidget.$('.o_mail_no_content').length &&
+ !self._thread.isAllHistoryLoaded(self.domain)
+ ) {
+ self._loadMoreMessages();
+ }
+ if (
+ self._threadWidget.isAtBottom() &&
+ self._thread.getType() !== 'mailbox'
+ ) {
+ self._thread.markAsRead();
+ }
+ }, 100));
+ });
+ },
+ /**
* Renders the mainside bar with current threads
*
* @private
@@ -1402,6 +1410,20 @@ var Discuss = AbstractAction.extend({
},
/**
* @private
+ */
+ _onMessagingReady: function () {
+ if (this._isMessagingReady) {
+ return;
+ }
+ this._isMessagingReady = true;
+ if (!this._isStarted) {
+ return;
+ }
+ this.$('.o_mail_discuss_loading').remove();
+ this._initRender();
+ },
+ /**
+ * @private
* @param {MouseEvent} ev
*/
_onModerateAllClicked: function (ev) {
diff --git a/addons/mail/static/src/js/services/mail_document_thread_manager.js b/addons/mail/static/src/js/services/mail_document_thread_manager.js
index 6bb66b1b439..652567baf14 100644
--- a/addons/mail/static/src/js/services/mail_document_thread_manager.js
+++ b/addons/mail/static/src/js/services/mail_document_thread_manager.js
@@ -34,7 +34,7 @@ MailManager.include({
this.call('local_storage', 'getItem', key);
}
}
- this.isReady().then(this._updateDocumentThreadWindows.bind(this, this._documentThreadWindowStates));
+ this._mailBus.on('messaging_ready', this, this._updateDocumentThreadWindows.bind(this, this._documentThreadWindowStates));
// listen to localStorage changes to synchronize DocumentThread's
// windows between tabs
this.call('local_storage', 'onStorage', this, this._onStorage);
diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js
index 3ed4031abeb..950ac8c5cd6 100644
--- a/addons/mail/static/src/js/services/mail_manager.js
+++ b/addons/mail/static/src/js/services/mail_manager.js
@@ -46,6 +46,7 @@ var PREVIEW_MSG_MAX_SIZE = 350; // optimal for native english speakers
var MailManager = AbstractService.extend({
dependencies: ['ajax', 'bus_service', 'local_storage'],
+ _isReady: false,
_ODOOBOT_ID: ["ODOOBOT", "ODOOBOT"], // authorID for transient messages
/**
@@ -294,7 +295,7 @@ var MailManager = AbstractService.extend({
* This is the case when it has fetched the initial state from the server,
* by means of the route 'mail/init_messaging'
*
- * @returns {Promise}
+ * @returns {boolean}
*/
isReady: function () {
return this._isReady;
@@ -636,7 +637,7 @@ var MailManager = AbstractService.extend({
*/
_fetchMailStateFromServer: function () {
var self = this;
- this._isReady = session.is_bound.then(function () {
+ session.is_bound.then(function () {
var context = _.extend(
{ isMobile: config.device.isMobile },
session.user_context
@@ -648,6 +649,8 @@ var MailManager = AbstractService.extend({
}).then(function (result) {
self._updateInternalStateFromServer(result);
self.call('bus_service', 'startPolling');
+ self._isReady = true;
+ self._mailBus.trigger('messaging_ready');
});
},
/**
diff --git a/addons/mail/static/src/js/systray/systray_activity_menu.js b/addons/mail/static/src/js/systray/systray_activity_menu.js
index 641ff4be1bc..8afa88d7e20 100644
--- a/addons/mail/static/src/js/systray/systray_activity_menu.js
+++ b/addons/mail/static/src/js/systray/systray_activity_menu.js
@@ -19,9 +19,6 @@ var ActivityMenu = Widget.extend({
'click .o_mail_preview': '_onActivityFilterClick',
'show.bs.dropdown': '_onActivityMenuShow',
},
- willStart: function () {
- return Promise.resolve(this.call('mail_service', 'isReady'));
- },
start: function () {
this._$activitiesPreview = this.$('.o_mail_systray_dropdown_items');
this.call('mail_service', 'getMailBus').on('activity_updated', this, this._updateCounter);
diff --git a/addons/mail/static/src/js/systray/systray_messaging_menu.js b/addons/mail/static/src/js/systray/systray_messaging_menu.js
index f4c4eb48f39..1093a22d27a 100644
--- a/addons/mail/static/src/js/systray/systray_messaging_menu.js
+++ b/addons/mail/static/src/js/systray/systray_messaging_menu.js
@@ -30,18 +30,14 @@ var MessagingMenu = Widget.extend({
/**
* @override
*/
- willStart: function () {
- return Promise.all([this._super.apply(this, arguments), this.call('mail_service', 'isReady')]);
- },
- /**
- * @override
- */
start: function () {
this._$filterButtons = this.$('.o_filter_button');
this._$previews = this.$('.o_mail_systray_dropdown_items');
this._filter = false;
+ this._isMessagingReady = this.call('mail_service', 'isReady');
this._updateCounter();
var mailBus = this.call('mail_service', 'getMailBus');
+ mailBus.on('messaging_ready', this, this._onMessagingReady);
mailBus.on('update_needaction', this, this._updateCounter);
mailBus.on('new_channel', this, this._updateCounter);
mailBus.on('update_thread_unread_counter', this, this._updateCounter);
@@ -207,6 +203,9 @@ var MessagingMenu = Widget.extend({
_updatePreviews: function () {
// Display spinner while waiting for conversations preview
this._$previews.html(QWeb.render('Spinner'));
+ if (!this._isMessagingReady) {
+ return;
+ }
this._getPreviews()
.then(this._renderPreviews.bind(this));
},
@@ -229,7 +228,11 @@ var MessagingMenu = Widget.extend({
* @private
*/
_updateCounter: function () {
+ if (!this._isMessagingReady) {
+ return;
+ }
var counter = this._computeCounter();
+ this.$('.o_mail_messaging_menu_icon').removeClass('fa-spinner fa-spin');
this.$('.o_notification_counter').text(counter);
this.$el.toggleClass('o_no_notification', !counter);
if (this._isShown()) {
@@ -338,6 +341,16 @@ var MessagingMenu = Widget.extend({
}
}
},
+ /**
+ * @private
+ */
+ _onMessagingReady: function () {
+ if (this._isMessagingReady) {
+ return;
+ }
+ this._isMessagingReady = true;
+ this._updateCounter();
+ },
});
// Systray menu items display order matches order in the list
diff --git a/addons/mail/static/src/js/thread_field.js b/addons/mail/static/src/js/thread_field.js
index ab8023b06ad..c8a59850016 100644
--- a/addons/mail/static/src/js/thread_field.js
+++ b/addons/mail/static/src/js/thread_field.js
@@ -20,6 +20,9 @@ var ThreadField = AbstractField.extend({
*/
init: function () {
this._super.apply(this, arguments);
+ this._isMessagingReady = this.call('mail_service', 'isReady');
+ this._isStarted = false;
+ this.call('mail_service', 'getMailBus').on('messaging_ready', this, this._onMessagingReady);
// Used to automatically mark document thread as read at the moment we
// access the document and render the thread.
this._markAsReadOnNextRender = false;
@@ -28,15 +31,10 @@ var ThreadField = AbstractField.extend({
/**
* @override
*/
- willStart: function () {
- return this.alive(this.call('mail_service', 'isReady'));
- },
- /**
- * @override
- */
start: function () {
var self = this;
+ this._isStarted = true;
this.dp = new concurrency.DropPrevious();
this._threadWidget = new ThreadWidget(this, {
@@ -159,6 +157,9 @@ var ThreadField = AbstractField.extend({
* If it is a new document in create mode, unset the document thread.
*/
_setDocumentThread: function () {
+ if (!this._isMessagingReady) {
+ return;
+ }
var params = {
messageIDs: this.value.res_ids,
name: this.recordData.display_name,
@@ -187,6 +188,19 @@ var ThreadField = AbstractField.extend({
},
/**
* @private
+ */
+ _onMessagingReady: function () {
+ if (this._isMessagingReady) {
+ return;
+ }
+ this._isMessagingReady = true;
+ if (!this._isStarted) {
+ return;
+ }
+ this._setDocumentThread();
+ },
+ /**
+ * @private
* @param {mail.model.Message}
*/
_onNewMessage: function (message) {
diff --git a/addons/mail/static/src/scss/discuss.scss b/addons/mail/static/src/scss/discuss.scss
index 8284fdc70b5..4cb902e4539 100644
--- a/addons/mail/static/src/scss/discuss.scss
+++ b/addons/mail/static/src/scss/discuss.scss
@@ -7,6 +7,18 @@
height: 100%;
overflow: hidden;
+ .o_mail_discuss_loading {
+ display: flex;
+ flex: 1 1 auto;
+ align-items: center;
+ justify-content: center;
+ min-height: 100%;
+
+ > i {
+ margin-right: 5px;
+ }
+ }
+
.o_mail_discuss_sidebar {
width: $o-mail-chat-sidebar-width;
height: 100%;
diff --git a/addons/mail/static/src/xml/discuss.xml b/addons/mail/static/src/xml/discuss.xml
index afee3a84da2..781a03c016d 100644
--- a/addons/mail/static/src/xml/discuss.xml
+++ b/addons/mail/static/src/xml/discuss.xml
@@ -8,8 +8,15 @@
-->
<t t-name="mail.discuss">
<div class="o_mail_discuss">
- <div class="o_mail_discuss_sidebar"/>
+ <div class="o_mail_discuss_sidebar">
+ <div class="o_mail_discuss_loading">
+ <i class="fa fa-spinner fa-spin"/> Please wait...
+ </div>
+ </div>
<div class="o_mail_discuss_content">
+ <div class="o_mail_discuss_loading">
+ <i class="fa fa-spinner fa-spin"/> Please wait...
+ </div>
</div>
</div>
</t>
diff --git a/addons/mail/static/src/xml/systray.xml b/addons/mail/static/src/xml/systray.xml
index 7bcda0fb273..204af3700e4 100644
--- a/addons/mail/static/src/xml/systray.xml
+++ b/addons/mail/static/src/xml/systray.xml
@@ -7,7 +7,7 @@
<t t-name="mail.systray.MessagingMenu">
<li class="o_mail_systray_item">
<a class="dropdown-toggle o-no-caret" data-toggle="dropdown" data-display="static" aria-expanded="false" title="Conversations" href="#" role="button">
- <i class="fa fa-comments" role="img" aria-label="Messages"/> <span class="o_notification_counter badge badge-pill"/>
+ <i class="o_mail_messaging_menu_icon fa fa-comments fa-spinner fa-spin" role="img" aria-label="Messages"/> <span class="o_notification_counter badge badge-pill"/>
</a>
<div class="o_mail_systray_dropdown dropdown-menu dropdown-menu-right" role="menu">
<t t-if="widget.isMobile()">
diff --git a/addons/mail/static/tests/discuss_tests.js b/addons/mail/static/tests/discuss_tests.js
index 226fd1e398a..1119ff2164f 100644
--- a/addons/mail/static/tests/discuss_tests.js
+++ b/addons/mail/static/tests/discuss_tests.js
@@ -114,6 +114,56 @@ QUnit.test('basic rendering', async function (assert) {
discuss.destroy();
});
+QUnit.test('messaging not ready', async function (assert) {
+ assert.expect(9);
+
+ const messagingReadyProm = testUtils.makeTestPromise();
+ const discuss = await createDiscuss({
+ id: 1,
+ context: {},
+ params: {},
+ data: this.data,
+ services: this.services,
+ async mockRPC(route, args) {
+ if (route === '/mail/init_messaging') {
+ const _super = this._super.bind(this, ...arguments); // limitation of class.js
+ assert.step('/mail/init_messaging:pending');
+ await messagingReadyProm;
+ assert.step('/mail/init_messaging:resolved');
+ return _super();
+ }
+ return this._super(...arguments);
+ },
+ });
+
+ assert.verifySteps(['/mail/init_messaging:pending']);
+ assert.ok(
+ discuss.el,
+ "discuss should be rendered");
+ assert.strictEqual(
+ $('.o_action')[0],
+ discuss.el,
+ "should display discuss even when messaging is not ready");
+ assert.containsOnce(
+ discuss,
+ '.o_mail_discuss_sidebar .o_mail_discuss_loading',
+ "should display sidebar is loading (messaging not yet ready)");
+ assert.containsOnce(
+ discuss,
+ '.o_mail_discuss_content .o_mail_discuss_loading',
+ "should display content is loading (messaging not yet ready)");
+
+ messagingReadyProm.resolve();
+ await testUtils.nextTick();
+ assert.verifySteps(['/mail/init_messaging:resolved']);
+ assert.containsNone(
+ discuss,
+ '.o_mail_discuss_loading',
+ "should no longer display sidebar or content is loading (messaging is ready)");
+
+ discuss.destroy();
+});
+
QUnit.test('searchview options visibility', async function (assert) {
assert.expect(5);
diff --git a/addons/mail/static/tests/systray/systray_messaging_menu_tests.js b/addons/mail/static/tests/systray/systray_messaging_menu_tests.js
index 047d723a425..9281e8a3ba4 100644
--- a/addons/mail/static/tests/systray/systray_messaging_menu_tests.js
+++ b/addons/mail/static/tests/systray/systray_messaging_menu_tests.js
@@ -120,6 +120,53 @@ QUnit.test('messaging menu widget: menu with no records', async function (assert
messagingMenu.destroy();
});
+QUnit.test('messaging menu widget: messaging not ready', async function (assert) {
+ assert.expect(8);
+
+ const messagingReadyProm = testUtils.makeTestPromise();
+
+ const messagingMenu = new MessagingMenu();
+ testUtils.mock.addMockEnvironment(messagingMenu, {
+ services: this.services,
+ async mockRPC(route, args) {
+ if (route === '/mail/init_messaging') {
+ const _super = this._super.bind(this, ...arguments); // limitation of class.js
+ assert.step('/mail/init_messaging:pending');
+ await messagingReadyProm;
+ assert.step('/mail/init_messaging:resolved');
+ return _super();
+ }
+ if (args.method === 'message_fetch') {
+ return [];
+ }
+ return this._super(...arguments);
+ },
+ });
+ await messagingMenu.appendTo($('#qunit-fixture'));
+ assert.verifySteps(['/mail/init_messaging:pending']);
+ assert.ok(
+ messagingMenu.el,
+ "messaging menu should be rendered");
+ assert.strictEqual(
+ document.querySelector('.o_mail_systray_item'),
+ messagingMenu.el,
+ "should display messaging menu even when messaging is not ready");
+ assert.hasClass(
+ messagingMenu.$('.o_mail_messaging_menu_icon'),
+ 'fa-spinner',
+ "Messaging menu icon should be a spinner when messaging menu is not ready");
+
+ messagingReadyProm.resolve();
+ await testUtils.nextTick();
+ assert.verifySteps(['/mail/init_messaging:resolved']);
+ assert.doesNotHaveClass(
+ messagingMenu.$('.o_mail_messaging_menu_icon'),
+ 'fa-spinner',
+ "Messaging menu icon should no longer be a spinner when messaging menu is ready");
+
+ messagingMenu.destroy();
+});
+
QUnit.test('messaging menu widget: messaging menu with 1 record', async function (assert) {
assert.expect(3);
var messagingMenu = new MessagingMenu();
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index 76e547c6916..0d9db0a5745 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -394,6 +394,7 @@ class MrpProduction(models.Model):
def _onchange_bom_id(self):
self.product_qty = self.bom_id.product_qty
self.product_uom_id = self.bom_id.product_uom_id.id
+ self.picking_type_id = self.bom_id.picking_type_id or self.picking_type_id
@api.onchange('date_planned_start')
def _onchange_date_planned_start(self):
diff --git a/addons/mrp/models/product.py b/addons/mrp/models/product.py
index 7ea6e332123..93f7f49ad6e 100644
--- a/addons/mrp/models/product.py
+++ b/addons/mrp/models/product.py
@@ -94,9 +94,11 @@ class ProductProduct(models.Model):
- 'incoming_qty'
- 'outgoing_qty'
"""
+ kits = self.env['product.product']
for product in self:
bom_kit = self.env['mrp.bom']._bom_find(product=product, bom_type='phantom')
if bom_kit:
+ kits |= product
boms, bom_sub_lines = bom_kit.explode(product, 1)
ratios_virtual_available = []
ratios_qty_available = []
@@ -115,8 +117,7 @@ class ProductProduct(models.Model):
product.qty_available = min(ratios_qty_available) // 1
product.incoming_qty = min(ratios_incoming_qty) // 1
product.outgoing_qty = min(ratios_incoming_qty) // 1
- else:
- super(ProductProduct, self)._compute_quantities()
+ super(ProductProduct, self - kits)._compute_quantities()
@api.multi
def action_view_bom(self):
diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py
index 62d35059978..f11a4f4a5c1 100644
--- a/addons/payment/models/payment_acquirer.py
+++ b/addons/payment/models/payment_acquirer.py
@@ -13,6 +13,7 @@ from odoo.addons.base.models import ir_module
from odoo.exceptions import ValidationError
from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools.misc import formatLang
+from odoo.http import request
_logger = logging.getLogger(__name__)
@@ -212,6 +213,19 @@ class PaymentAcquirer(models.Model):
(_check_required_if_provider, 'Required fields not filled', []),
]
+ def get_base_url(self):
+ self.ensure_one()
+ # priority is always given to url_root
+ # from the request
+ url = ''
+ if request:
+ url = request.httprequest.url_root
+
+ if not url and 'website_id' in self and self.website_id:
+ url = self.website_id._get_http_domain()
+
+ return url or self.env['ir.config_parameter'].sudo().get_param('web.base.url')
+
def _get_feature_support(self):
"""Get advanced feature support by provider.
diff --git a/addons/payment_stripe_sca/models/payment.py b/addons/payment_stripe_sca/models/payment.py
index 01093ab6d7f..fec16081c5d 100644
--- a/addons/payment_stripe_sca/models/payment.py
+++ b/addons/payment_stripe_sca/models/payment.py
@@ -20,7 +20,7 @@ class PaymentAcquirerStripeSCA(models.Model):
def stripe_form_generate_values(self, tx_values):
self.ensure_one()
- base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
+ base_url = self.get_base_url()
stripe_session_data = {
"payment_method_types[]": "card",
"line_items[][amount]": int(
@@ -177,6 +177,9 @@ class PaymentTransactionStripeSCA(models.Model):
"_stripe_create_payment_intent: Values received:\n%s", pprint.pformat(res)
)
return res
+ if not self.payment_token_id.stripe_payment_method:
+ # old token before installing stripe_sca, need to fetch data from the api
+ self.payment_token_id._stripe_sca_migrate_customer()
charge_params = {
"amount": int(
self.amount
@@ -366,3 +369,39 @@ class PaymentTokenStripeSCA(models.Model):
raise NotImplementedError(
"This method can no longer be used with the payment_stripe_sca module."
)
+
+ def _stripe_sca_migrate_customer(self):
+ """Migrate a token from the old implementation of Stripe to the SCA one.
+
+ In the old implementation, it was possible to create a valid charge just by
+ giving the customer ref to ask Stripe to use the default source (= default
+ card). Since we have a one-to-one matching between a saved card, this used to
+ work well - but now we need to specify the payment method for each call and so
+ we have to contact stripe to get the default source for the customer and save it
+ in the payment token.
+ This conversion will happen once per token, the first time it gets used following
+ the installation of the module."""
+ self.ensure_one()
+ url = "customers/%s" % (self.acquirer_ref)
+ data = self.acquirer_id._stripe_request(url, method="GET")
+ sources = data.get('sources', {}).get('data', [])
+ pm_ref = False
+ if sources:
+ if len(sources) > 1:
+ _logger.warning('stripe sca customer conversion: there should be a single saved source per customer!')
+ pm_ref = sources[0].get('id')
+ else:
+ url = 'payment_methods'
+ params = {
+ 'type': 'card',
+ 'customer': self.acquirer_ref,
+ }
+ payment_methods = self.acquirer_id._stripe_request(url, params, method='GET')
+ cards = payment_methods.get('data', [])
+ if len(cards) > 1:
+ _logger.warning('stripe sca customer conversion: there should be a single saved source per customer!')
+ pm_ref = cards and cards[0].get('id')
+ if not pm_ref:
+ raise ValidationError(_('Unable to convert Stripe customer for SCA compatibility. Is there at least one card for this customer in the Stripe backend?'))
+ self.stripe_payment_method = pm_ref
+ _logger.info('converted old customer ref to sca-compatible record for payment token %s', self.id) \ No newline at end of file
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index ba2e4db2cd3..016356fcb32 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -754,7 +754,7 @@ class PosOrder(models.Model):
for order in self:
if order.lines and not order.amount_total:
continue
- if (not order.lines) or (not order.statement_ids) or (abs(order.amount_total - order.amount_paid) > 0.00001):
+ if (not order.lines) or (not order.statement_ids) or (not float_is_zero((order.amount_total - order.amount_paid), precision_rounding=order.pricelist_id.currency_id.rounding)):
return False
return True
diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py
index 0797b05d4a5..4e432dd6ce1 100644
--- a/addons/product/models/product_pricelist.py
+++ b/addons/product/models/product_pricelist.py
@@ -130,7 +130,7 @@ class Pricelist(models.Model):
""" Low-level method - Mono pricelist, multi products
Returns: dict{product_id: (price, suitable_rule) for the given pricelist}
- If date in context: Date of the pricelist (%Y-%m-%d)
+ Date in context can be a date, datetime, ...
:param products_qty_partner: list of typles products, quantity, partner
:param datetime date: validity date
@@ -138,8 +138,8 @@ class Pricelist(models.Model):
"""
self.ensure_one()
if not date:
- date = self._context.get('date') or fields.Date.context_today(self)
- date = fields.Date.to_date(date) # handwritten query below does not work with non-dates (e.g. datetimes)
+ date = self._context.get('date') or fields.Date.today()
+ date = fields.Date.to_date(date) # boundary conditions differ if we have a datetime
if not uom_id and self._context.get('uom'):
uom_id = self._context['uom']
if uom_id:
diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py
index 5867cce9092..d1bcc1adb3a 100644
--- a/addons/product/models/product_template.py
+++ b/addons/product/models/product_template.py
@@ -400,7 +400,7 @@ class ProductTemplate(models.Model):
@api.multi
def name_get(self):
# Prefetch the fields used by the `name_get`, so `browse` doesn't fetch other fields
- self.read(['name', 'default_code'])
+ self.browse(self.ids).read(['name', 'default_code'])
return [(template.id, '%s%s' % (template.default_code and '[%s] ' % template.default_code or '', template.name))
for template in self]
diff --git a/addons/snailmail/models/res_partner.py b/addons/snailmail/models/res_partner.py
index c4b3b576bf0..493846c865d 100644
--- a/addons/snailmail/models/res_partner.py
+++ b/addons/snailmail/models/res_partner.py
@@ -11,15 +11,16 @@ class ResPartner(models.Model):
@api.multi
def write(self, vals):
- letter_address_vals = {}
- address_fields = ['street', 'street2', 'city', 'zip', 'state_id', 'country_id']
- for field in address_fields:
- if field in vals:
- letter_address_vals[field] = vals[field]
+ for id in self.ids:
+ letter_address_vals = {}
+ address_fields = ['street', 'street2', 'city', 'zip', 'state_id', 'country_id']
+ for field in address_fields:
+ if field in vals:
+ letter_address_vals[field] = vals[field]
- if len(letter_address_vals):
- letter_ids = self.env['snailmail.letter'].search([('state', 'not in', ['sent', 'canceled']), ('partner_id', '=', self.id)])
- letter_ids.write(letter_address_vals)
+ if len(letter_address_vals):
+ letter_ids = self.env['snailmail.letter'].search([('state', 'not in', ['sent', 'canceled']), ('partner_id', '=', id)])
+ letter_ids.write(letter_address_vals)
return super(ResPartner, self).write(vals)
@@ -40,4 +41,4 @@ class ResPartner(models.Model):
if self.env.context.get('snailmail_layout') and self.street2:
return "%(street)s, %(street2)s\n%(city)s %(state_code)s %(zip)s\n%(country_name)s"
- return super(ResPartner, self)._get_address_format() \ No newline at end of file
+ return super(ResPartner, self)._get_address_format()
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 6b219fa71d7..879274ed3e1 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -896,6 +896,7 @@ class StockMove(models.Model):
# cache invalidation when actually reserving the move.
reserved_availability = {move: move.reserved_availability for move in self}
roundings = {move: move.product_id.uom_id.rounding for move in self}
+ move_line_vals_list = []
for move in self.filtered(lambda m: m.state in ['confirmed', 'waiting', 'partially_available']):
rounding = roundings[move]
missing_reserved_uom_quantity = move.product_uom_qty - reserved_availability[move]
@@ -905,7 +906,7 @@ class StockMove(models.Model):
# create the move line(s) but do not impact quants
if move.product_id.tracking == 'serial' and (move.picking_type_id.use_create_lots or move.picking_type_id.use_existing_lots):
for i in range(0, int(missing_reserved_quantity)):
- self.env['stock.move.line'].create(move._prepare_move_line_vals(quantity=1))
+ move_line_vals_list.append(move._prepare_move_line_vals(quantity=1))
else:
to_update = move.move_line_ids.filtered(lambda ml: ml.product_uom_id == move.product_uom and
ml.location_id == move.location_id and
@@ -917,7 +918,7 @@ class StockMove(models.Model):
if to_update:
to_update[0].product_uom_qty += missing_reserved_uom_quantity
else:
- self.env['stock.move.line'].create(move._prepare_move_line_vals(quantity=missing_reserved_quantity))
+ move_line_vals_list.append(move._prepare_move_line_vals(quantity=missing_reserved_quantity))
assigned_moves |= move
else:
if not move.move_orig_ids:
@@ -1008,6 +1009,7 @@ class StockMove(models.Model):
assigned_moves |= move
break
partially_available_moves |= move
+ self.env['stock.move.line'].create(move_line_vals_list)
partially_available_moves.write({'state': 'partially_available'})
assigned_moves.write({'state': 'assigned'})
self.mapped('picking_id')._check_entire_pack()
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index 653f77eef16..64ee09445bf 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -644,21 +644,31 @@ class Picking(models.Model):
@api.depends('state', 'move_lines', 'move_lines.state', 'move_lines.package_level_id', 'move_lines.move_line_ids.package_level_id')
def _compute_move_without_package(self):
for picking in self:
- move_ids_without_package = self.env['stock.move']
- if picking.picking_type_entire_packs == False:
- move_ids_without_package = picking.move_lines
- else:
- for move in picking.move_lines:
- if not move.package_level_id:
- if move.state in ('assigned', 'done'):
- if any(not ml.package_level_id for ml in move.move_line_ids):
- move_ids_without_package |= move
- else:
- move_ids_without_package |= move
- picking.move_ids_without_package = move_ids_without_package.filtered(lambda move: not move.scrap_ids)
+ picking.move_ids_without_package = picking._get_move_ids_without_package()
def _set_move_without_package(self):
- self.move_lines |= self.move_ids_without_package
+ new_mwp = self[0].move_ids_without_package
+ for picking in self:
+ old_mwp = picking._get_move_ids_without_package()
+ picking.move_lines = (picking.move_lines - old_mwp) | new_mwp
+ moves_to_unlink = old_mwp - new_mwp
+ if moves_to_unlink:
+ moves_to_unlink.unlink()
+
+ def _get_move_ids_without_package(self):
+ self.ensure_one()
+ move_ids_without_package = self.env['stock.move']
+ if not self.picking_type_entire_packs:
+ move_ids_without_package = self.move_lines
+ else:
+ for move in self.move_lines:
+ if not move.package_level_id:
+ if move.state in ('assigned', 'done'):
+ if any(not ml.package_level_id for ml in move.move_line_ids):
+ move_ids_without_package |= move
+ else:
+ move_ids_without_package |= move
+ return move_ids_without_package.filtered(lambda move: not move.scrap_ids)
def _check_move_lines_map_quant_package(self, package):
""" This method checks that all product of the package (quant) are well present in the move_line_ids of the picking. """
diff --git a/addons/stock/tests/test_move2.py b/addons/stock/tests/test_move2.py
index c0cbf4db0e0..34ebbb905f6 100644
--- a/addons/stock/tests/test_move2.py
+++ b/addons/stock/tests/test_move2.py
@@ -1831,6 +1831,20 @@ class TestSinglePicking(TestStockCommon):
back_order = self.env['stock.picking'].search([('backorder_id', '=', delivery_order.id)])
self.assertFalse(back_order, 'There should be no back order')
+ def test_unlink_move_1(self):
+ picking = Form(self.env['stock.picking'])
+ ptout = self.env['stock.picking.type'].browse(self.picking_type_out)
+ picking.picking_type_id = ptout
+ with picking.move_ids_without_package.new() as move:
+ move.product_id = self.productA
+ move.product_uom_qty = 10
+ picking = picking.save()
+
+ picking = Form(picking)
+ picking.move_ids_without_package.remove(0)
+ picking = picking.save()
+ self.assertEqual(len(picking.move_ids_without_package), 0)
+
class TestStockUOM(TestStockCommon):
def setUp(self):
diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py
index e8552c45b7a..73aaedb45d1 100644
--- a/addons/survey/controllers/main.py
+++ b/addons/survey/controllers/main.py
@@ -403,10 +403,11 @@ class Survey(http.Controller):
answer_tag = "%s_%s" % (survey_sudo.id, question.id)
request.env['survey.user_input_line'].sudo().save_lines(answer_sudo.id, question, post, answer_tag)
+ vals = {}
if answer_sudo.is_time_limit_reached or survey_sudo.questions_layout == 'one_page':
go_back = False
answer_sudo._mark_done()
- else:
+ elif 'button_submit' in post:
go_back = post['button_submit'] == 'previous'
next_page, last = request.env['survey.survey'].next_page_or_question(answer_sudo, page_or_question_id, go_back=go_back)
vals = {'last_displayed_page_id': page_or_question_id}
@@ -415,11 +416,16 @@ class Survey(http.Controller):
answer_sudo._mark_done()
else:
vals.update({'state': 'skip'})
- answer_sudo.write(vals)
- ret['redirect'] = '/survey/fill/%s/%s' % (survey_sudo.access_token, answer_token)
- if go_back:
- ret['redirect'] += '?prev=prev'
+ if 'breadcrumb_redirect' in post:
+ ret['redirect'] = post['breadcrumb_redirect']
+ else:
+ if vals:
+ answer_sudo.write(vals)
+
+ ret['redirect'] = '/survey/fill/%s/%s' % (survey_sudo.access_token, answer_token)
+ if go_back:
+ ret['redirect'] += '?prev=prev'
return json.dumps(ret)
diff --git a/addons/survey/static/src/js/survey.js b/addons/survey/static/src/js/survey.js
index d232c3073f7..86d8728d9be 100644
--- a/addons/survey/static/src/js/survey.js
+++ b/addons/survey/static/src/js/survey.js
@@ -129,6 +129,15 @@ if(!the_form.length) {
}
}
+ $('.o_survey_header .breadcrumb-item a').each(function() {
+ var $valObj = $(this);
+ $valObj.click(function(event) {
+ event.preventDefault();
+ $('<input>').attr({type: 'hidden', name: 'breadcrumb_redirect', value: $valObj.attr('href')}).appendTo('.js_surveyform');
+ $('.js_surveyform').submit();
+ });
+ });
+
// Parameters for form submission
$('.js_surveyform').ajaxForm({
url: submit_controller,
@@ -229,5 +238,4 @@ if(!the_form.length) {
display_scores();
}
});
-
});
diff --git a/addons/web/static/src/css/reset.min.css b/addons/web/static/src/css/reset.min.css
index 97c0b78b002..b6163764a5c 100644
--- a/addons/web/static/src/css/reset.min.css
+++ b/addons/web/static/src/css/reset.min.css
@@ -55,10 +55,6 @@ ul ul {
list-style: circle;
}
-blockquote, q {
- quotes: none;
-}
-
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js
index 7be17700f98..e369cea9c8c 100644
--- a/addons/web/static/src/js/core/dom.js
+++ b/addons/web/static/src/js/core/dom.js
@@ -540,7 +540,7 @@ var dom = {
menuItemsWidth += computeFloatOuterWidthWithMargins($extraItemsToggle[0]);
do {
menuItemsWidth -= computeFloatOuterWidthWithMargins($items.eq(--nbItems)[0]);
- } while (!(maxWidth - menuItemsWidth >= -0.001));
+ } while (!(maxWidth - menuItemsWidth >= -0.001) && (nbItems > 0));
var $extraItems = $items.slice(nbItems).detach();
$extraItems.removeClass('nav-item');
diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js
index dc456d21ae0..317ea30e0ad 100644
--- a/addons/web/static/src/js/fields/abstract_field.js
+++ b/addons/web/static/src/js/fields/abstract_field.js
@@ -533,6 +533,12 @@ var AbstractField = Widget.extend({
}
break;
case $.ui.keyCode.ENTER:
+ // We preventDefault the ENTER key because of two coexisting behaviours:
+ // - In HTML5, pressing ENTER on a <button> triggers two events: a 'keydown' AND a 'click'
+ // - When creating and opening a dialog, the focus is automatically given to the primary button
+ // The end result caused some issues where a modal opened by an ENTER keypress (e.g. saving
+ // changes in multiple edition) confirmed the modal without any intentionnal user input.
+ ev.preventDefault();
ev.stopPropagation();
this.trigger_up('navigation_move', {direction: 'next_line'});
break;
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index eeda3d220b6..93a636c141a 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -492,6 +492,9 @@ var FieldDate = InputField.extend({
className: "o_field_date",
tagName: "span",
supportedFieldTypes: ['date', 'datetime'],
+ // we don't need to listen on 'input' nor 'change' events because the
+ // datepicker widget is already listening, and will correctly notify changes
+ events: AbstractField.prototype.events,
/**
* @override
@@ -888,6 +891,8 @@ var FieldBoolean = AbstractField.extend({
_onKeydown: function (ev) {
switch (ev.which) {
case $.ui.keyCode.ENTER:
+ // prevent subsequent 'click' event (see _onKeydown of AbstractField)
+ ev.preventDefault();
this.$input.prop('checked', !this.value);
this._setValue(!this.value);
return;
diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js
index ec99f4fa0f6..fd73b7a9ed2 100644
--- a/addons/web/static/src/js/fields/field_utils.js
+++ b/addons/web/static/src/js/fields/field_utils.js
@@ -415,7 +415,7 @@ function parseDate(value, field, options) {
if (options && options.isUTC) {
date = moment.utc(value);
} else {
- date = moment.utc(value, [datePattern, datePatternWoZero, moment.ISO_8601], true);
+ date = moment.utc(value, [datePattern, datePatternWoZero, moment.ISO_8601]);
}
if (date.isValid()) {
if (date.year() === 0) {
@@ -459,7 +459,7 @@ function parseDateTime(value, field, options) {
// phatomjs crash if we don't use this format
datetime = moment.utc(value.replace(' ', 'T') + 'Z');
} else {
- datetime = moment.utc(value, [pattern1, pattern2, moment.ISO_8601], true);
+ datetime = moment.utc(value, [pattern1, pattern2, moment.ISO_8601]);
if (options && options.timezone) {
datetime.add(-session.getTZOffset(datetime), 'minutes');
}
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index be35395aab7..6213fc8de6a 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -395,8 +395,10 @@ var FieldMany2One = AbstractField.extend({
no_create: !self.can_create,
on_selected: function (records) {
self.reinitialize(records[0]);
+ },
+ on_closed: function () {
self.activate();
- }
+ },
};
},
/**
@@ -805,6 +807,48 @@ var Many2oneBarcode = FieldMany2One.extend({
});
var ListFieldMany2One = FieldMany2One.extend({
+ events: _.extend({}, FieldMany2One.prototype.events, {
+ 'focusin input': '_onInputFocusin',
+ }),
+
+ /**
+ * Should never be allowed to be opened while in readonly mode in a list
+ *
+ * @override
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ // when we empty the input, we delay the setValue to prevent from
+ // triggering the 'fieldChanged' event twice when the user wants set
+ // another m2o value ; the following attribute is used to determine when
+ // we skipped the setValue, s.t. we can perform it later on if the user
+ // didn't select another value
+ this.mustSetValue = false;
+ this.m2oDialogFocused = false;
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * If in readonly, will never be considered as an active widget.
+ *
+ * @override
+ */
+ activate: function () {
+ if (this.mode === 'readonly') {
+ return false;
+ }
+ return this._super.apply(this, arguments);
+ },
+ /**
+ * @override
+ */
+ reinitialize: function () {
+ this.mustSetValue = false;
+ return this._super.apply(this, arguments);
+ },
//--------------------------------------------------------------------------
// Private
@@ -816,6 +860,52 @@ var ListFieldMany2One = FieldMany2One.extend({
_renderReadonly: function () {
this.$el.text(this.m2o_value);
},
+ /**
+ * @override
+ * @private
+ */
+ _searchCreatePopup: function () {
+ this.m2oDialogFocused = true;
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onInputFocusin: function () {
+ this.m2oDialogFocused = false;
+ },
+ /**
+ * In list views, we don't want to try to trigger a fieldChange when the field
+ * is being emptied. Instead, it will be triggered as the user leaves the field
+ * while it is empty.
+ *
+ * @override
+ * @private
+ */
+ _onInputFocusout: function () {
+ this._super.apply(this, arguments);
+ if (!this.m2oDialogFocused && this.$input.val() === "" && this.mustSetValue) {
+ this.reinitialize(false);
+ }
+ },
+ /**
+ * Prevents the triggering of an immediate _onFieldChanged when emptying the field.
+ *
+ * @override
+ * @private
+ */
+ _onInputKeyup: function () {
+ if (this.$input.val() !== "") {
+ this._super.apply(this, arguments);
+ } else {
+ this.mustSetValue = true;
+ }
+ },
});
var KanbanFieldMany2One = AbstractField.extend({
diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js
index b864063ee92..e0598fcf9cc 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -50,6 +50,21 @@ var ListController = BasicController.extend({
this.editable = params.editable;
this.noLeaf = params.noLeaf;
this.selectedRecords = params.selectedRecords || [];
+ this.multipleRecordsSavingPromise = null;
+ this.fieldChangedPrevented = false;
+ this._onMouseupWindowDiscard = null;
+ },
+
+ /**
+ * Overriden to properly unbind any mouseup listeners still bound to the window
+ *
+ * @override
+ */
+ destroy: function () {
+ if (this._onMouseupWindowDiscard) {
+ window.removeEventListener('mouseup', this._onMouseupWindowDiscard, true);
+ }
+ return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
@@ -134,6 +149,7 @@ var ListController = BasicController.extend({
},
trigger: 'manual',
});
+ this.$buttons.on('mousedown', '.o_list_button_discard', this._onDiscardMousedown.bind(this));
this.$buttons.on('click', '.o_list_button_discard', this._onDiscard.bind(this));
this.$buttons.appendTo($node);
}
@@ -342,15 +358,6 @@ var ListController = BasicController.extend({
return _.extend(env, {domain: record.getDomain()});
},
/**
- * @private
- * @returns {boolean}
- */
- _inMultipleRecordEdition: function (recordId) {
- var record = this.model.get(recordId, { raw: true });
- var recordIds = _.union([recordId], this.selectedRecords);
- return recordIds.length > 1 && record.res_id;
- },
- /**
* Only display the pager when there are data to display.
*
* @override
@@ -361,13 +368,20 @@ var ListController = BasicController.extend({
return !!state.count;
},
/**
+ * Saves multiple records at once. This method is called by the _onFieldChanged method
+ * since the record must be confirmed as soon as the focus leaves a dirty cell.
+ * Pseudo-validation is performed with registered modifiers.
+ * Returns a promise that is resolved when confirming and rejected in any other case.
+ *
* @private
* @param {string} recordId
* @param {Object} node
* @param {Object} changes
+ * @returns {Promise}
*/
_saveMultipleRecords: function (recordId, node, changes) {
var self = this;
+ var fieldName = Object.keys(changes)[0];
var value = Object.values(changes)[0];
var recordIds = _.union([recordId], this.selectedRecords);
var validRecordIds = recordIds.reduce(function (result, recordId) {
@@ -378,22 +392,52 @@ var ListController = BasicController.extend({
}
return result;
}, []);
- var message = _.str.sprintf(
- _t('Do you want to set the value on the %d valid selected records?'),
- validRecordIds.length);
- if (recordIds.length !== validRecordIds.length) {
- var nbInvalid = recordIds.length - validRecordIds.length;
- message += ' ' + _.str.sprintf(_t('(%d invalid)'), nbInvalid);
- }
- Dialog.confirm(this, message, {
- confirm_callback: function () {
- self.model.saveRecords(recordId, validRecordIds)
- .then(function () {
- self._updateButtons('readonly');
- var state = self.model.get(self.handle);
- self.renderer.updateState(state, {});
- });
- },
+ var nbInvalid = recordIds.length - validRecordIds.length;
+
+ return new Promise(function (resolve, reject) {
+ var dialog;
+ var rejectAndDiscard = function () {
+ self.model.discardChanges(recordId);
+ return self._confirmSave(recordId).then(reject);
+ };
+ if (validRecordIds.length > 0) {
+ var message;
+ if (nbInvalid === 0) {
+ message = _.str.sprintf(
+ _t("Do you want to set the value on the %d selected records?"),
+ validRecordIds.length);
+ } else {
+ message = _.str.sprintf(
+ _t("Do you want to set the value on the %d valid selected records? (%d invalid)"),
+ validRecordIds.length, nbInvalid);
+ }
+ dialog = Dialog.confirm(self, message, {
+ confirm_callback: function () {
+ return self.model.saveRecords(recordId, validRecordIds, fieldName)
+ .then(function () {
+ self._updateButtons('readonly');
+ var state = self.model.get(self.handle);
+ return self.renderer.updateState(state, {}).then(function () {
+ resolve(Object.keys(changes));
+ });
+ })
+ .guardedCatch(rejectAndDiscard);
+ },
+ cancel_callback: rejectAndDiscard,
+ });
+ } else {
+ dialog = Dialog.alert(self, _t("No valid record to save"), {
+ confirm_callback: rejectAndDiscard,
+ });
+ }
+ dialog.on('closed', self, function () {
+ // we need to wait for the dialog to be actually closed, but
+ // the 'closed' event is triggered just before, and it prevents
+ // from focussing the cell
+ Promise.resolve().then(function () {
+ self.renderer.focusCell(recordId, node);
+ });
+ });
});
},
/**
@@ -404,10 +448,11 @@ var ListController = BasicController.extend({
*/
_saveRecord: function (recordId) {
var record = this.model.get(recordId, { raw: true });
- if (record.isDirty() && this._inMultipleRecordEdition(recordId)) {
+ if (record.isDirty() && this.renderer.inMultipleRecordEdition(recordId)) {
// do not save the record (see _saveMultipleRecords)
- return Promise.resolve();
-
+ var prom = this.multipleRecordsSavingPromise || Promise.reject();
+ this.multipleRecordsSavingPromise = null;
+ return prom;
}
return this._super.apply(this, arguments);
},
@@ -563,6 +608,31 @@ var ListController = BasicController.extend({
this._discardChanges();
},
/**
+ * Used to detect if the discard button is about to be clicked.
+ * Some focusout events might occur and trigger a save which
+ * is not always wanted when clicking "Discard".
+ *
+ * @param {MouseEvent} ev
+ * @private
+ */
+ _onDiscardMousedown: function (ev) {
+ var self = this;
+ this.fieldChangedPrevented = true;
+ this._onMouseupWindowDiscard = (function (mouseupEvent) {
+ window.removeEventListener('mouseup', self._onMouseupWindowDiscard, true);
+ self._onMouseupWindowDiscard = null;
+ var preventedEvent = self.fieldChangedPrevented;
+ self.fieldChangedPrevented = false;
+ // If the user starts clicking (mousedown) on the button and stops clicking
+ // (mouseup) outside of the button, we want to trigger the original onFieldChanged
+ // Event that was prevented in the meantime.
+ if (ev.target !== mouseupEvent.target && preventedEvent.constructor.name === 'OdooEvent') {
+ self._onFieldChanged(preventedEvent);
+ }
+ }).bind(this);
+ window.addEventListener('mouseup', this._onMouseupWindowDiscard, true);
+ },
+ /**
* Called when the user asks to edit a row -> Updates the controller buttons
*
* @param {OdooEvent} ev
@@ -618,15 +688,18 @@ var ListController = BasicController.extend({
_onFieldChanged: function (ev) {
ev.stopPropagation();
var self = this;
+ var recordId = ev.data.dataPointID;
- if (this._inMultipleRecordEdition(ev.data.dataPointID)) {
+ if (this.fieldChangedPrevented) {
+ this.fieldChangedPrevented = ev;
+ } else if (this.renderer.inMultipleRecordEdition(recordId)) {
// deal with edition of multiple lines
- var _onSuccess = ev.data.onSuccess;
ev.data.onSuccess = function () {
- Promise.resolve(_onSuccess()).then(function () {
+ self.multipleRecordsSavingPromise =
self._saveMultipleRecords(ev.data.dataPointID, ev.target.__node, ev.data.changes);
- });
};
+ // disable onchanges as we'll save directly
+ ev.data.notifyChange = false;
}
this._super.apply(this, arguments);
},
@@ -653,6 +726,29 @@ var ListController = BasicController.extend({
this._toggleSidebar();
},
/**
+ * If the record is set as dirty while in multiple record edition,
+ * we want to immediatly discard the change.
+ *
+ * @private
+ * @override
+ * @param {OdooEvent} ev
+ */
+ _onSetDirty: function (ev) {
+ var self = this;
+ var recordId = ev.data.dataPointID;
+ if (this.renderer.inMultipleRecordEdition(recordId)) {
+ ev.stopPropagation();
+ Dialog.alert(this, _t("No valid record to save"), {
+ confirm_callback: function () {
+ self.model.discardChanges(recordId);
+ self._confirmSave(recordId);
+ },
+ });
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+ /**
* When the user clicks on one of the sortable column headers, we need to
* tell the model to sort itself properly, to update the pager and to
* rerender the view.
diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js
index f87268ddb1e..4d5fd24124e 100644
--- a/addons/web/static/src/js/views/list/list_editable_renderer.js
+++ b/addons/web/static/src/js/views/list/list_editable_renderer.js
@@ -277,6 +277,17 @@ ListRenderer.include({
this._selectCell(rowIndex, 0);
},
/**
+ * Gives focus to a specific cell, given its row and its related column.
+ *
+ * @param {string} recordId
+ * @param {Object} column
+ */
+ focusCell: function (recordId, column) {
+ var $row = this._getRow(recordId);
+ var cellIndex = this.columns.indexOf(column);
+ $row.find('.o_data_cell')[cellIndex].focus();
+ },
+ /**
* Returns the recordID associated to the line which is currently in edition
* or null if there is no line in edition.
*
@@ -289,6 +300,16 @@ ListRenderer.include({
return null;
},
/**
+ * Returns whether the list is in multiple record edition from a given record.
+ *
+ * @private
+ * @param {string} recordId
+ * @returns {boolean}
+ */
+ inMultipleRecordEdition: function (recordId) {
+ return this.editable && this.selection.length > 1 && this.selection.indexOf(recordId) > -1;
+ },
+ /**
* Removes the line associated to the given recordID (the index of the row
* is found thanks to the old state), then updates the state.
*
@@ -303,6 +324,7 @@ ListRenderer.include({
}
if ($row.prop('rowIndex') - 1 === this.currentRow) {
this.currentRow = null;
+ this._enableRecordSelectors();
}
// destroy widgets first
@@ -397,7 +419,11 @@ ListRenderer.include({
// Toggle selected class here so that style is applied at the end
$row.toggleClass('o_selected_row', editMode);
- $row.find('.o_list_record_selector input').prop('disabled', !record.res_id);
+ if (editMode) {
+ this._disableRecordSelectors();
+ } else {
+ this._enableRecordSelectors();
+ }
return Promise.all(defs).then(function () {
// necessary to trigger resize on fieldtexts
@@ -427,17 +453,21 @@ ListRenderer.include({
var recordWidgets = this.allFieldWidgets[recordID];
toggleWidgets(true);
- var prom = new Promise(function (resolve, reject) {
+ return new Promise(function (resolve, reject) {
self.trigger_up('save_line', {
recordID: recordID,
onSuccess: resolve,
onFailure: reject,
});
- });
- prom.guardedCatch(function() {
+ }).then(function (changedFields) {
+ self._enableRecordSelectors();
+ // If any field has changed and if the list is in multiple edition,
+ // we send a truthy boolean to _selectRow to tell it not to select
+ // the following record.
+ return changedFields && changedFields.length && self.inMultipleRecordEdition(recordID);
+ }).guardedCatch(function () {
toggleWidgets(false);
});
- return prom;
function toggleWidgets(disabled) {
_.each(recordWidgets, function (widget) {
@@ -464,6 +494,14 @@ ListRenderer.include({
//--------------------------------------------------------------------------
/**
+ * When editing a row, we want to disable all record selectors.
+ *
+ * @private
+ */
+ _disableRecordSelectors: function () {
+ this.$('.o_list_record_selector input').attr('disabled', 'disabled');
+ },
+ /**
* Destroy all field widgets corresponding to a record. Useful when we are
* removing a useless row.
*
@@ -477,6 +515,12 @@ ListRenderer.include({
}
},
/**
+ * @private
+ */
+ _enableRecordSelectors: function () {
+ this.$('.o_list_record_selector input').attr('disabled', false);
+ },
+ /**
* Returns the relative width according to the widget or the field type.
* @see _renderHeader
*
@@ -992,7 +1036,10 @@ ListRenderer.include({
var recordId = this._getRecordID(rowIndex);
// To select a row, the currently selected one must be unselected first
var self = this;
- return this.unselectRow().then(function () {
+ return this.unselectRow().then(function (noSelectNext) {
+ if (noSelectNext) {
+ return Promise.resolve();
+ }
if (!recordId) {
// The row to selected doesn't exist anymore (probably because
// an onchange triggered when unselecting the previous one
@@ -1003,7 +1050,10 @@ ListRenderer.include({
return new Promise(function (resolve) {
self.trigger_up('edit_line', {
recordId: recordId,
- onSuccess: resolve,
+ onSuccess: function () {
+ self._disableRecordSelectors();
+ resolve();
+ },
});
});
});
@@ -1035,7 +1085,10 @@ ListRenderer.include({
ev.stopPropagation();
var self = this;
- var groupId = $(ev.target).data('group-id');
+ // This method can be called when selecting the parent of the link.
+ // We need to ensure that the link is the actual target
+ var target = ev.target.tagName !== 'A' ? ev.target.getElementsByTagName('A')[0] : ev.target;
+ var groupId = target.dataset.groupId;
this.currentGroupId = groupId;
this.unselectRow().then(function () {
self.trigger_up('add_record', {
@@ -1213,12 +1266,13 @@ ListRenderer.include({
this.trigger_up('discard_changes', {
recordID: ev.target.dataPointID,
onSuccess: function () {
+ self._enableRecordSelectors();
var recordId = self._getRecordID(rowIndex);
if (recordId) {
var correspondingRow = self._getRow(recordId);
correspondingRow.children().eq(cellIndex).focus();
} else if (self.currentGroupId) {
- self.$('a[data-group-id=' + self.currentGroupId + ']').focus();
+ self.$('a[data-group-id="' + self.currentGroupId + '"]').focus();
} else {
self.$('.o_field_x2many_list_row_add a:first').focus(); // FIXME
}
diff --git a/addons/web/static/src/js/views/list/list_model.js b/addons/web/static/src/js/views/list/list_model.js
index 25fdf28954c..7a2e0c51ada 100644
--- a/addons/web/static/src/js/views/list/list_model.js
+++ b/addons/web/static/src/js/views/list/list_model.js
@@ -40,12 +40,18 @@ odoo.define('web.ListModel', function (require) {
* @param {string} referenceRecordId the record datapoint used to
* generate the changes to apply to recordIds
* @param {string[]} recordIds a list of record datapoint ids
+ * @param {string} fieldName the field to write
*/
- saveRecords: function (referenceRecordId, recordIds) {
+ saveRecords: function (referenceRecordId, recordIds, fieldName) {
var self = this;
var referenceRecord = this.localData[referenceRecordId];
var list = this.localData[referenceRecord.parentID];
- var changes = this._generateChanges(referenceRecord, {});
+ // generate all record values to ensure that we'll write something
+ // (e.g. 2 records selected, edit a many2one in the first one, but
+ // reset same value, we still want to save this value on the other
+ // record)
+ var allChanges = this._generateChanges(referenceRecord, {changesOnly: false});
+ var changes = _.pick(allChanges, fieldName);
var records = recordIds.map(function (recordId) {
return self.localData[recordId];
});
diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js
index afa8f09a857..8aaf09fc4c7 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -417,6 +417,7 @@ var ListRenderer = BasicRenderer.extend({
data: record.data,
escape: true,
isPassword: 'password' in node.attrs,
+ digits: node.attrs.digits ? JSON.parse(node.attrs.digits) : undefined,
});
this._handleAttributes($td, node);
var title = field.type !== 'boolean' ? formattedValue : '';
@@ -979,11 +980,18 @@ var ListRenderer = BasicRenderer.extend({
* @private
*/
_updateSelection: function () {
- var $selectedRows = this.$('tbody .o_list_record_selector input:checked')
- .closest('tr');
- this.selection = _.map($selectedRows, function (row) {
- return $(row).data('id');
+ this.selection = [];
+ var self = this;
+ var $inputs = this.$('tbody .o_list_record_selector input:visible:not(:disabled)');
+ var allChecked = $inputs.length > 0;
+ $inputs.each(function (index, input) {
+ if (input.checked) {
+ self.selection.push($(input).closest('tr').data('id'));
+ } else {
+ allChecked = false;
+ }
});
+ this.$('thead .o_list_record_selector input').prop('checked', allChecked);
this.trigger_up('selection_changed', { selection: this.selection });
this._updateFooter();
},
@@ -1156,9 +1164,6 @@ var ListRenderer = BasicRenderer.extend({
_onSelectRecord: function (ev) {
ev.stopPropagation();
this._updateSelection();
- if (!$(ev.currentTarget).find('input').prop('checked')) {
- this.$('thead .o_list_record_selector input').prop('checked', false);
- }
},
/**
* @private
@@ -1175,11 +1180,13 @@ var ListRenderer = BasicRenderer.extend({
_onToggleGroup: function (ev) {
ev.preventDefault();
ev.stopPropagation();
+ var self = this;
var group = $(ev.currentTarget).closest('tr').data('group');
if (group.count) {
this.trigger_up('toggle_group', {
group: group,
onSuccess: function() {
+ self._updateSelection();
// Refocus the header after re-render unless the user
// already focused something else by now
if (document.activeElement.tagName === 'BODY') {
diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js
index f665bf49791..abfad01558e 100644
--- a/addons/web/static/src/js/views/view_dialogs.js
+++ b/addons/web/static/src/js/views/view_dialogs.js
@@ -329,6 +329,7 @@ var SelectCreateDialog = ViewDialog.extend({
this._super.apply(this, arguments);
_.defaults(this.options, { initial_view: 'search' });
this.on_selected = this.options.on_selected || (function () {});
+ this.on_closed = this.options.on_closed || (function () {});
this.initialIDs = this.options.initial_ids;
},
@@ -412,6 +413,10 @@ var SelectCreateDialog = ViewDialog.extend({
return fragment;
});
},
+ close: function () {
+ this._super.apply(this, arguments);
+ this.on_closed();
+ },
create_edit_record: function () {
var self = this;
var dialog = new FormViewDialog(this, _.extend({}, this.options, {
diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js
index ac8e83c8e30..bde1d793374 100644
--- a/addons/web/static/src/js/widgets/date_picker.js
+++ b/addons/web/static/src/js/widgets/date_picker.js
@@ -12,7 +12,6 @@ var DateWidget = Widget.extend({
template: "web.datepicker",
type_of_date: "date",
events: {
- 'change.datetimepicker': 'changeDatetime',
'error.datetimepicker': 'errorDatetime',
'change .o_datepicker_input': 'changeDatetime',
'click input': '_onInputClicked',
@@ -92,6 +91,13 @@ var DateWidget = Widget.extend({
* set datetime value
*/
changeDatetime: function () {
+ if (this.__libInput > 0) {
+ if (this.options.warn_future) {
+ this._warnFuture(this.getValue());
+ }
+ this.trigger("datetime_changed");
+ return;
+ }
var oldValue = this.getValue();
if (this.isValid()) {
this._setValueFromUi();
@@ -254,6 +260,7 @@ var DateWidget = Widget.extend({
*/
_onDateTimePickerHide: function () {
this.__isOpen = false;
+ this.changeDatetime();
if (this._onScroll) {
window.removeEventListener('scroll', this._onScroll, true);
}
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 5eff2fb4422..657ad2d6ebb 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -712,8 +712,8 @@
<t t-name="FieldMany2One">
<t t-if="widget.mode === 'readonly'">
- <a t-if="!widget.nodeOptions.no_open" t-att-tabindex="widget.attrs.tabindex" class="o_form_uri" href="#"/>
- <span t-if="widget.nodeOptions.no_open"/>
+ <a t-if="!widget.noOpen" t-att-tabindex="widget.attrs.tabindex" class="o_form_uri" href="#"/>
+ <span t-if="widget.noOpen"/>
</t>
<div t-if="widget.mode === 'edit'" class="o_field_widget o_field_many2one" aria-atomic="true">
<div class="o_input_dropdown">
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index c4869ce2e00..8e6c05b938f 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -529,6 +529,42 @@ QUnit.module('basic_fields', {
form.destroy();
});
+ QUnit.test('float field in list view no widget', async function (assert) {
+ assert.expect(5);
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<field name="qux" digits="[5,3]"/>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 2,
+ });
+
+ assert.doesNotHaveClass(form.$('.o_field_widget'), 'o_field_empty',
+ 'Float field should be considered set for value 0.');
+ assert.strictEqual(form.$('.o_field_widget').first().text(), '0.000',
+ 'The value should be displayed properly.');
+
+ await testUtils.form.clickEdit(form);
+ assert.strictEqual(form.$('input[name=qux]').val(), '0.000',
+ 'The value should be rendered with correct precision.');
+
+ await testUtils.fields.editInput(form.$('input[name=qux]'), '108.2458938598598');
+ assert.strictEqual(form.$('input[name=qux]').val(), '108.2458938598598',
+ 'The value should not be formated yet.');
+
+ await testUtils.fields.editInput(form.$('input[name=qux]'), '18.8958938598598');
+ await testUtils.form.clickSave(form);
+ assert.strictEqual(form.$('.o_field_widget').first().text(), '18.896',
+ 'The new value should be rounded properly.');
+
+ form.destroy();
+ });
+
QUnit.test('float field in form view', async function (assert) {
assert.expect(5);
@@ -3093,6 +3129,63 @@ QUnit.module('basic_fields', {
form.destroy();
});
+ QUnit.test('datetime fields do not trigger fieldChange before datetime completly picked', async function (assert) {
+ assert.expect(6);
+
+ this.data.partner.onchanges = {
+ datetime: function () {},
+ };
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form><field name="datetime"/></form>',
+ res_id: 1,
+ translateParameters: { // Avoid issues due to localization formats
+ date_format: '%m/%d/%Y',
+ time_format: '%H:%M:%S',
+ },
+ session: {
+ getTZOffset: function () {
+ return 120;
+ },
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange') {
+ assert.step('onchange');
+ }
+ return this._super.apply(this, arguments);
+ },
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+
+ testUtils.dom.openDatepicker(form.$('.o_datepicker'));
+ assert.containsOnce($('body'), '.bootstrap-datetimepicker-widget');
+
+ // select a date and time
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first());
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)'));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)'));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(3));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day:contains(22)'));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .fa-clock-o'));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hour'));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .hour:contains(08)'));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-minute'));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .minute:contains(25)'));
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-second'));
+ assert.verifySteps([], "should not have done any onchange yet");
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget .second:contains(35)'));
+
+ assert.containsNone($('body'), '.bootstrap-datetimepicker-widget');
+ assert.strictEqual(form.$('.o_datepicker_input').val(), "04/22/2017 08:25:35");
+ assert.verifySteps(['onchange'], "should have done only one onchange");
+
+ form.destroy();
+ });
QUnit.test('datetime field not visible in form view should not capture the focus on keyboard navigation', async function (assert) {
assert.expect(1);
@@ -3215,6 +3308,45 @@ QUnit.module('basic_fields', {
list.destroy();
});
+ QUnit.test('multi edition of datetime field in list view: edit date in input', async function (assert) {
+ assert.expect(4);
+
+ var list = await createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="datetime"/>' +
+ '</tree>',
+ translateParameters: { // Avoid issues due to localization formats
+ date_format: '%m/%d/%Y',
+ time_format: '%H:%M:%S',
+ },
+ session: {
+ getTZOffset: function () {
+ return 120;
+ },
+ },
+ });
+
+ // select two records and edit them
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+
+ await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell'));
+ assert.containsOnce(list, 'input.o_datepicker_input');
+ list.$('.o_datepicker_input').val("10/02/2019 09:00:00");
+ await testUtils.dom.triggerEvents(list.$('.o_datepicker_input'), ['change']);
+
+ assert.containsOnce(document.body, '.modal');
+ await testUtils.dom.click($('.modal .modal-footer .btn-primary'));
+
+ assert.strictEqual(list.$('.o_data_row:first .o_data_cell').text(), "10/02/2019 09:00:00");
+ assert.strictEqual(list.$('.o_data_row:nth(1) .o_data_cell').text(), "10/02/2019 09:00:00");
+
+ list.destroy();
+ });
+
QUnit.test('datetime field remove value', async function (assert) {
assert.expect(4);
diff --git a/addons/web/static/tests/fields/field_utils_tests.js b/addons/web/static/tests/fields/field_utils_tests.js
index c4281b38c87..e0b9564d13c 100644
--- a/addons/web/static/tests/fields/field_utils_tests.js
+++ b/addons/web/static/tests/fields/field_utils_tests.js
@@ -353,5 +353,39 @@ QUnit.test('parse datetime', function (assert) {
core._t.database.parameters = originalParameters;
});
+QUnit.test('parse date without separator', function (assert) {
+ assert.expect(8);
+
+ var originalParameters = _.clone(core._t.database.parameters);
+
+ _.extend(core._t.database.parameters, {date_format: '%d.%m/%Y'});
+ var dateFormat = "DD.MM/YYYY";
+
+ assert.throws(function () {fieldUtils.parse.date("1197")}, /is not a correct/, "Wrongly formated dates should be invalid");
+ assert.throws(function () {fieldUtils.parse.date("0131")}, /is not a correct/, "Wrongly formated dates should be invalid");
+ assert.throws(function () {fieldUtils.parse.date("970131")}, /is not a correct/, "Wrongly formated dates should be invalid");
+ assert.equal(fieldUtils.parse.date("3101").format(dateFormat), "31.01/" + moment.utc().year());
+ assert.equal(fieldUtils.parse.date("31.01").format(dateFormat), "31.01/" + moment.utc().year());
+ assert.equal(fieldUtils.parse.date("310197").format(dateFormat), "31.01/1997");
+ assert.equal(fieldUtils.parse.date("310117").format(dateFormat), "31.01/2017");
+ assert.equal(fieldUtils.parse.date("31011985").format(dateFormat), "31.01/1985");
+
+ core._t.database.parameters = originalParameters;
+});
+
+QUnit.test('parse datetime without separator', function (assert) {
+ assert.expect(3);
+
+ var originalParameters = _.clone(core._t.database.parameters);
+
+ _.extend(core._t.database.parameters, {date_format: '%d.%m/%Y', time_format: '%H:%M/%S'});
+ var dateTimeFormat = "DD.MM/YYYY HH:mm/ss";
+
+ assert.equal(fieldUtils.parse.datetime("3101198508").format(dateTimeFormat), "31.01/1985 08:00/00");
+ assert.equal(fieldUtils.parse.datetime("310119850833").format(dateTimeFormat), "31.01/1985 08:33/00");
+ assert.equal(fieldUtils.parse.datetime("31/01/1985 08").format(dateTimeFormat), "31.01/1985 08:00/00");
+
+ core._t.database.parameters = originalParameters;
+});
});
});
diff --git a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
index b32f8478ccf..e905e486f6f 100644
--- a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
+++ b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
@@ -566,6 +566,81 @@ QUnit.module('fields', {}, function () {
form.destroy();
});
+ QUnit.test("many2one doesn't trigger field_change when being emptied", async function (assert) {
+ assert.expect(2);
+
+ const list = await createView({
+ arch: `
+ <tree editable="top">
+ <field name="trululu"/>
+ </tree>`,
+ data: this.data,
+ model: 'partner',
+ View: ListView,
+ });
+
+ // Select two records
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+
+ await testUtils.dom.click(list.$('.o_data_row:first() .o_data_cell:first()'));
+
+ const $input = list.$('.o_field_widget[name=trululu] input');
+
+ await testUtils.fields.editInput($input, "");
+ await testUtils.dom.triggerEvents($input, ['keyup']);
+
+ assert.containsNone(document.body, '.modal',
+ "No save should be triggered when removing value");
+
+ await testUtils.fields.many2one.clickOpenDropdown('trululu');
+ await testUtils.fields.many2one.clickHighlightedItem('trululu');
+
+ assert.containsOnce(document.body, '.modal',
+ "Saving should be triggered when selecting a value");
+ await testUtils.dom.click($('.modal .btn-primary'));
+
+ list.destroy();
+ });
+
+ QUnit.test("focus tracking on a many2one in a list", async function (assert) {
+ assert.expect(4);
+
+ const list = await createView({
+ arch: '<tree editable="top"><field name="trululu"/></tree>',
+ archs: {
+ 'partner,false,form': '<form string="Partners"><field name="foo"/></form>',
+ },
+ data: this.data,
+ model: 'partner',
+ View: ListView,
+ });
+
+ // Select two records
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+
+ await testUtils.dom.click(list.$('.o_data_row:first() .o_data_cell:first()'));
+
+ const input = list.$('.o_data_row:first() .o_data_cell:first() input')[0];
+
+ assert.strictEqual(document.activeElement, input, "Input should be focused when activated");
+
+ await testUtils.fields.many2one.clickOpenDropdown('trululu');
+ await testUtils.fields.many2one.clickItem('trululu', 'Create');
+
+ // At this point, if the focus is correctly registered by the m2o, there
+ // should be only one modal (the "Create" one) and none for saving changes.
+ assert.containsOnce(document.body, '.modal', "There should be only one modal");
+
+ await testUtils.dom.click($('.modal .btn:not(.btn-primary)'));
+
+ assert.strictEqual(document.activeElement, input, "Input should be focused after dialog closes");
+ assert.strictEqual(input.value, "", "Input should be empty after discard");
+
+ list.destroy();
+ });
+
QUnit.test('many2one fields with option "no_open"', async function (assert) {
assert.expect(3);
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 0cf3279aa54..df25691f375 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -190,7 +190,7 @@ QUnit.module('Views', {
});
QUnit.test('simple editable rendering', async function (assert) {
- assert.expect(12);
+ assert.expect(15);
var list = await createView({
View: ListView,
@@ -201,6 +201,7 @@ QUnit.module('Views', {
assert.containsN(list, 'th', 3, "should have 2 th");
assert.containsN(list, 'th', 3, "should have 3 th");
+ assert.containsN(list, '.o_list_record_selector input:enabled', 5);
assert.containsOnce(list, 'td:contains(yop)', "should contain yop");
assert.isVisible(list.$buttons.find('.o_list_button_add'),
@@ -218,6 +219,7 @@ QUnit.module('Views', {
"should have a visible save button");
assert.isVisible(list.$buttons.find('.o_list_button_discard'),
"should have a visible discard button");
+ assert.containsNone(list, '.o_list_record_selector input:enabled');
await testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
@@ -227,6 +229,8 @@ QUnit.module('Views', {
"should not have a visible save button");
assert.isNotVisible(list.$buttons.find('.o_list_button_discard'),
"should not have a visible discard button");
+ assert.containsN(list, '.o_list_record_selector input:enabled', 5);
+
list.destroy();
});
@@ -1114,6 +1118,49 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('head selector is toggled by the other selectors', async function (assert) {
+ assert.expect(6);
+
+ const list = await createView({
+ arch: '<tree><field name="foo"/><field name="bar"/></tree>',
+ data: this.data,
+ groupBy: ['bar'],
+ model: 'foo',
+ View: ListView,
+ });
+
+ assert.ok(!list.$('thead .o_list_record_selector input')[0].checked,
+ "Head selector should be unchecked");
+
+ await testUtils.dom.click(list.$('.o_group_header:first()'));
+ await testUtils.dom.click(list.$('thead .o_list_record_selector input'));
+
+ assert.containsN(list, 'tbody .o_list_record_selector input:checked',
+ 3, "All visible checkboxes should be checked");
+
+ await testUtils.dom.click(list.$('.o_group_header:last()'));
+
+ assert.ok(!list.$('thead .o_list_record_selector input')[0].checked,
+ "Head selector should be unchecked");
+
+ await testUtils.dom.click(list.$('tbody .o_list_record_selector input:last()'));
+
+ assert.ok(list.$('thead .o_list_record_selector input')[0].checked,
+ "Head selector should be checked");
+
+ await testUtils.dom.click(list.$('tbody .o_list_record_selector:first() input'));
+
+ assert.ok(!list.$('thead .o_list_record_selector input')[0].checked,
+ "Head selector should be unchecked");
+
+ await testUtils.dom.click(list.$('.o_group_header:first()'));
+
+ assert.ok(list.$('thead .o_list_record_selector input')[0].checked,
+ "Head selector should be checked");
+
+ list.destroy();
+ });
+
QUnit.test('selection is reset on reload', async function (assert) {
assert.expect(5);
@@ -2889,7 +2936,7 @@ QUnit.module('Views', {
});
QUnit.test('edition: create new line, then discard', async function (assert) {
- assert.expect(8);
+ assert.expect(11);
var list = await createView({
View: ListView,
@@ -2904,11 +2951,13 @@ QUnit.module('Views', {
"create button should be visible");
assert.strictEqual(list.$buttons.find('.o_list_button_discard:visible').length, 0,
"discard button should be hidden");
+ assert.containsN(list, '.o_list_record_selector input:enabled', 5);
await testUtils.dom.click(list.$buttons.find('.o_list_button_add'));
assert.strictEqual(list.$buttons.find('.o_list_button_add:visible').length, 0,
"create button should be hidden");
assert.strictEqual(list.$buttons.find('.o_list_button_discard:visible').length, 1,
"discard button should be visible");
+ assert.containsNone(list, '.o_list_record_selector input:enabled');
await testUtils.dom.click(list.$buttons.find('.o_list_button_discard'));
assert.containsN(list, 'tr.o_data_row', 4,
"should still have 4 records");
@@ -2916,6 +2965,8 @@ QUnit.module('Views', {
"create button should be visible again");
assert.strictEqual(list.$buttons.find('.o_list_button_discard:visible').length, 0,
"discard button should be hidden again");
+ assert.containsN(list, '.o_list_record_selector input:enabled', 5);
+
list.destroy();
});
@@ -4500,8 +4551,34 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('editable list view: single edition with selected records', async function (assert) {
+ assert.expect(2);
+
+ const list = await createView({
+ arch: `<tree editable="top"><field name="foo"/></tree>`,
+ data: this.data,
+ model: 'foo',
+ View: ListView,
+ });
+
+ // Select first record
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+
+ // Edit the second
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_data_cell:first()'));
+ await testUtils.fields.editInput(list.$('.o_data_row:eq(1) .o_data_cell:first() input'), "oui");
+ await testUtils.dom.click($('.o_list_button_save'));
+
+ assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell:first()').text(), "yop",
+ "First row should remain unchanged");
+ assert.strictEqual(list.$('.o_data_row:eq(1) .o_data_cell:first()').text(), "oui",
+ "Second row should have been updated");
+
+ list.destroy();
+ });
+
QUnit.test('editable list view: multi edition', async function (assert) {
- assert.expect(19);
+ assert.expect(24);
var list = await createView({
View: ListView,
@@ -4544,7 +4621,7 @@ QUnit.module('Views', {
assert.verifySteps(['default_get']);
await testUtils.fields.editInput(list.$('.o_selected_row .o_field_widget[name=int_field]'), 123);
- assert.containsNone($, '.modal', "the multi edition should not be triggered during creation");
+ assert.containsNone(document.body, '.modal', "the multi edition should not be triggered during creation");
await testUtils.dom.click($('.o_list_button_save'));
assert.verifySteps(['create', 'read']);
@@ -4552,15 +4629,241 @@ QUnit.module('Views', {
// edit a field
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
await testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'), 666);
- assert.containsOnce($, '.modal', "there should be an opened modal");
- assert.ok($('.modal').text().includes('2 valid'), "the number of records should be correctly displayed");
-
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
+ assert.containsOnce(document.body, '.modal', "modal appears when switching cells");
+ await testUtils.dom.click($('.modal .btn:contains(Cancel)'));
+ assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), 'yop10',
+ "changes have been discarded and row is back to readonly");
+ assert.strictEqual(document.activeElement, list.$('.o_data_row:eq(0) .o_data_cell:eq(1)')[0],
+ "focus should be given to the most recently edited cell after discard");
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
+ await testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'), 666);
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_data_cell:eq(0)'));
+ assert.ok($('.modal').text().includes('2 selected records'), "the number of records should be correctly displayed");
await testUtils.dom.click($('.modal .btn-primary'));
+ assert.containsNone(list, '.o_data_cell input.o_field_widget', "no field should be editable anymore");
assert.verifySteps(['write', 'read']);
assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), "yop666",
"the first row should be updated");
assert.strictEqual(list.$('.o_data_row:eq(1) .o_data_cell').text(), "blip666",
"the second row should be updated");
+ assert.containsNone(list, '.o_data_cell input.o_field_widget', "no field should be editable anymore");
+ assert.strictEqual(document.activeElement, list.$('.o_data_row:eq(0) .o_data_cell:eq(1)')[0],
+ "focus should be given to the most recently edited cell after confirm");
+
+ list.destroy();
+ });
+
+ QUnit.test('editable list view: multi edition and onchanges', async function (assert) {
+ assert.expect(10);
+
+ this.data.foo.onchanges = {
+ foo: function (obj) {
+ obj.int_field = obj.foo.length;
+ },
+ };
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="foo"/>' +
+ '<field name="int_field"/>' +
+ '</tree>',
+ mockRPC: function (route, args) {
+ assert.step(args.method || route);
+ if (args.method === 'write') {
+ args.args[1].int_field = args.args[1].foo.length;
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ // edit a single record to validate that onchanges work
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
+ await testUtils.fields.editInput(list.$('.o_field_widget[name=foo]'), 'hi');
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
+ await testUtils.dom.click(list.$('.o_list_button_discard'));
+
+ assert.containsOnce(document.body, '.modal'); // discard dialog
+ await testUtils.dom.click($('.modal .btn-primary'));
+
+ assert.verifySteps(['/web/dataset/search_read', 'onchange']);
+
+ // select two records
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+
+ // edit foo, first row
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
+ await testUtils.fields.editInput(list.$('.o_field_widget[name=foo]'), 'hello');
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
+
+ assert.containsOnce(document.body, '.modal'); // save dialog
+ await testUtils.dom.click($('.modal .btn-primary'));
+
+ assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), "hello5");
+ assert.strictEqual(list.$('.o_data_row:eq(1) .o_data_cell').text(), "hello5");
+
+ assert.verifySteps(['write', 'read'], "should not perform the onchange in multi edition");
+
+ list.destroy();
+ });
+
+ QUnit.test('editable list view: multi edition error and cancellation handling', async function (assert) {
+ assert.expect(12);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="foo" required="1"/>' +
+ '<field name="int_field"/>' +
+ '</tree>',
+ });
+
+ assert.containsN(list, '.o_list_record_selector input:enabled', 5);
+
+ // select two records
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+
+ // edit a line and cancel
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
+ assert.containsNone(list, '.o_list_record_selector input:enabled');
+ await testUtils.fields.editInput(list.$('.o_selected_row .o_field_widget[name=foo]'), "abc");
+ await testUtils.dom.click($('.modal .btn:contains("Cancel")'));
+ assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), 'yop10', "first cell should have discarded any change");
+ assert.containsN(list, '.o_list_record_selector input:enabled', 5);
+
+ // edit a line with an invalid format type
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
+ assert.containsNone(list, '.o_list_record_selector input:enabled');
+ await testUtils.fields.editInput(list.$('.o_selected_row .o_field_widget[name=int_field]'), "hahaha");
+ assert.containsOnce(document.body, '.modal', "there should be an opened modal");
+ await testUtils.dom.click($('.modal .btn-primary'));
+ assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), 'yop10', "changes should be discarded");
+ assert.containsN(list, '.o_list_record_selector input:enabled', 5);
+
+ // edit a line with an invalid value
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
+ assert.containsNone(list, '.o_list_record_selector input:enabled');
+ await testUtils.fields.editInput(list.$('.o_selected_row .o_field_widget[name=foo]'), "");
+ assert.containsOnce(document.body, '.modal', "there should be an opened modal");
+ await testUtils.dom.click($('.modal .btn-primary'));
+ assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), 'yop10', "changes should be discarded");
+ assert.containsN(list, '.o_list_record_selector input:enabled', 5);
+
+ list.destroy();
+ });
+
+ QUnit.test('editable list view: multi edition of many2one: set same value', async function (assert) {
+ assert.expect(4);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="foo"/>' +
+ '<field name="m2o"/>' +
+ '</tree>',
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ assert.deepEqual(args.args, [[1, 2, 3, 4], { m2o: 1 }],
+ "should force write value on all selected records");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(list.$('.o_list_many2one').text(), "Value 1Value 2Value 1Value 1");
+
+ // select all records (the first one has value 1 for m2o)
+ await testUtils.dom.click(list.$('thead .o_list_record_selector input'));
+
+ // set m2o to 1 in first record
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
+ await testUtils.fields.many2one.searchAndClickItem('m2o', {search: 'Value 1'});
+
+ assert.containsOnce(document.body, '.modal');
+
+ await testUtils.dom.click($('.modal .modal-footer .btn-primary'));
+
+ assert.strictEqual(list.$('.o_list_many2one').text(), "Value 1Value 1Value 1Value 1");
+
+ list.destroy();
+ });
+
+ QUnit.test('editable list view: clicking on "Discard changes" in multi edition', async function (assert) {
+ assert.expect(2);
+
+ const list = await createView({
+ arch: `
+ <tree editable="top">
+ <field name="foo"/>
+ </tree>`,
+ data: this.data,
+ model: 'foo',
+ View: ListView,
+ });
+
+ // select two records
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+
+ await testUtils.dom.click(list.$('.o_data_row:first() .o_data_cell:first()'));
+ list.$('.o_data_row:first() .o_data_cell:first() input').val("oof");
+
+ const $discardButton = list.$buttons.find('.o_list_button_discard');
+
+ // Simulates an actual click (event chain is: mousedown > change > blur > focus > mouseup > click)
+ await testUtils.dom.triggerEvents($discardButton, ['mousedown']);
+ await testUtils.dom.triggerEvents(list.$('.o_data_row:first() .o_data_cell:first() input'),
+ ['change', 'blur', 'focusout']);
+ await testUtils.dom.triggerEvents($discardButton, ['focus']);
+ $discardButton[0].dispatchEvent(new MouseEvent('mouseup'));
+ await testUtils.dom.click($discardButton);
+
+ assert.ok($('.modal').text().includes("Warning"), "Modal should ask to discard changes");
+ await testUtils.dom.click($('.modal .btn-primary'));
+
+ assert.strictEqual(list.$('.o_data_row:first() .o_data_cell:first()').text(), "yop");
+
+ list.destroy();
+ });
+
+ QUnit.test('editable list view (multi edition): mousedown on "Discard", but mouseup somewhere else', async function (assert) {
+ assert.expect(1);
+
+ const list = await createView({
+ arch: `
+ <tree editable="top">
+ <field name="foo"/>
+ </tree>`,
+ data: this.data,
+ model: 'foo',
+ View: ListView,
+ });
+
+ // select two records
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+
+ await testUtils.dom.click(list.$('.o_data_row:first() .o_data_cell:first()'));
+ list.$('.o_data_row:first() .o_data_cell:first() input').val("oof");
+
+ // Simulates a pseudo drag and drop
+ await testUtils.dom.triggerEvents(list.$buttons.find('.o_list_button_discard'), ['mousedown']);
+ await testUtils.dom.triggerEvents(list.$('.o_data_row:first() .o_data_cell:first() input'),
+ ['change', 'blur', 'focusout']);
+ await testUtils.dom.triggerEvents($(document.body), ['focus']);
+ window.dispatchEvent(new MouseEvent('mouseup'));
+ await testUtils.nextTick();
+
+ assert.ok($('.modal').text().includes("Confirmation"), "Modal should ask to save changes");
+ await testUtils.dom.click($('.modal .btn-primary'));
list.destroy();
});
@@ -4606,6 +4909,65 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('editable list view: many2one with readonly modifier', async function (assert) {
+ assert.expect(2);
+
+ const list = await createView({
+ arch:
+ `<tree editable="top">
+ <field name="m2o" readonly="1"/>
+ <field name="foo"/>
+ </tree>`,
+ data: this.data,
+ model: 'foo',
+ View: ListView,
+ });
+
+ // edit a field
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
+
+ assert.containsOnce(list, '.o_data_row:eq(0) .o_data_cell:eq(0) a[name="m2o"]');
+ assert.strictEqual(document.activeElement, list.$('.o_data_row:eq(0) .o_data_cell:eq(1) input')[0],
+ "focus should go to the char input");
+
+ list.destroy();
+ });
+
+ QUnit.test('editable list view: multi edition: edit and validate last row', async function (assert) {
+ assert.expect(3);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="foo"/>' +
+ '<field name="int_field"/>' +
+ '</tree>',
+ // in this test, we want to accurately mock what really happens, that is, input
+ // fields only trigger their changes on 'change' event, not on 'input'
+ fieldDebounce: 100000,
+ });
+
+ assert.containsN(list, '.o_data_row', 4);
+
+ // select all records
+ await testUtils.dom.click(list.$('.o_list_view thead .o_list_record_selector input'));
+
+ // edit last cell of last line
+ await testUtils.dom.click(list.$('.o_data_row:last .o_data_cell:last'));
+ testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'), '666');
+ await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row .o_data_cell:last input'), 'enter');
+
+ assert.containsOnce(document.body, '.modal');
+ await testUtils.dom.click($('.modal .btn-primary'));
+
+ assert.containsN(list, '.o_data_row', 4,
+ "should not create a new row as we were in multi edition");
+
+ list.destroy();
+ });
+
QUnit.test('list grouped by date:month', async function (assert) {
assert.expect(1);
@@ -6325,6 +6687,28 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('add and discard a line through keyboard navigation without crashing', async function (assert) {
+ assert.expect(2);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="bottom"><field name="foo" required="1"/></tree>',
+ groupBy: ['bar'],
+ });
+
+ await testUtils.dom.click(list.$('.o_group_header:first')); // open group
+ // Triggers ENTER on "Add a line" wrapper cell
+ await testUtils.fields.triggerKeydown(list.$('.o_group_field_row_add'), 'enter');
+ assert.containsN(list, 'tbody:nth(1) .o_data_row', 4, "new data row should be created");
+ await testUtils.dom.click(list.$buttons.find('.o_list_button_discard'));
+ // At this point, a crash manager should appear if no proper link targetting
+ assert.containsN(list, 'tbody:nth(1) .o_data_row', 3,"new data row should be discarded.");
+
+ list.destroy();
+ });
+
QUnit.test('editable grouped list with create="0"', async function (assert) {
assert.expect(1);
diff --git a/addons/web/static/tests/views/search_view_tests.js b/addons/web/static/tests/views/search_view_tests.js
index 88720e77ede..a94b0c4ce8e 100644
--- a/addons/web/static/tests/views/search_view_tests.js
+++ b/addons/web/static/tests/views/search_view_tests.js
@@ -604,6 +604,7 @@ QUnit.module('Search View', {
// DateTime case
$autocomplete = $('.o_searchview_input');
await stringToEvent($autocomplete, '07/15/1983 00:00:00');
+ await testUtils.fields.triggerKey('down', $autocomplete, 'down');
await testUtils.fields.triggerKey('up', $autocomplete, 'enter');
assert.equal($('.o_searchview_facet .o_facet_values').text().trim(), '07/15/1983 00:00:00',
diff --git a/addons/web/static/tests/widgets/domain_selector_tests.js b/addons/web/static/tests/widgets/domain_selector_tests.js
index 305315b72c6..8c246fd8399 100644
--- a/addons/web/static/tests/widgets/domain_selector_tests.js
+++ b/addons/web/static/tests/widgets/domain_selector_tests.js
@@ -174,6 +174,7 @@ QUnit.module('DomainSelector', {
await testUtils.dom.clickFirst($('.bootstrap-datetimepicker-widget :not(.today)[data-action="selectDay"]'));
assert.notEqual(domainSelector.$(".o_datepicker:visible input").val(), val,
"datepicker value should have changed");
+ await testUtils.dom.click($('.bootstrap-datetimepicker-widget a[data-action=close]'));
domainSelector.destroy();
});
diff --git a/addons/website/static/src/scss/website.scss b/addons/website/static/src/scss/website.scss
index ef2edf98b35..d0bfb83168e 100644
--- a/addons/website/static/src/scss/website.scss
+++ b/addons/website/static/src/scss/website.scss
@@ -341,9 +341,11 @@ table.table_desc tr td {
// Title
.s_title {
.s_title_boxed {
- display: inline-block;
- padding: $grid-gutter-width;
- border: 1px solid;
+ > * {
+ display: inline-block;
+ padding: $grid-gutter-width;
+ border: 1px solid;
+ }
}
.s_title_lines {
overflow: hidden;
diff --git a/addons/website_event_questions/report/report_event_question_view.xml b/addons/website_event_questions/report/report_event_question_view.xml
index f2982ac35b5..109d3414a2c 100644
--- a/addons/website_event_questions/report/report_event_question_view.xml
+++ b/addons/website_event_questions/report/report_event_question_view.xml
@@ -40,6 +40,9 @@
<filter string="Event" name="group_by_event" context="{'group_by': 'event_id'}" />
<filter string="Question" name="group_by_question" context="{'group_by': 'question_id'}"/>
<filter string="Answer" name="group_by_answer" context="{'group_by': 'answer_id'}" />
+ <filter name="exclude_cancelled"
+ string="Exclude cancelled registrations"
+ domain="[('attendee_id.state', '!=', 'cancel')]" />
</group>
</search>
</field>
diff --git a/addons/website_sale/data/mail_template_data.xml b/addons/website_sale/data/mail_template_data.xml
index 18a1bb70a51..c6314d9bf69 100644
--- a/addons/website_sale/data/mail_template_data.xml
+++ b/addons/website_sale/data/mail_template_data.xml
@@ -38,7 +38,7 @@
<h1 style="color:#A9A9A9;">THERE'S SOMETHING IN YOUR CART.</h1>
Would you like to complete your purchase?<br/><br/>
% if object.order_line:
- % for line in object.order_line:
+ % for line in object.website_order_line:
<hr/>
<table width="100%">
<tr>
diff --git a/addons/website_sale_wishlist/controllers/main.py b/addons/website_sale_wishlist/controllers/main.py
index 09bd5f90b17..b494aeb927d 100644
--- a/addons/website_sale_wishlist/controllers/main.py
+++ b/addons/website_sale_wishlist/controllers/main.py
@@ -55,5 +55,5 @@ class WebsiteSaleWishlist(WebsiteSale):
request.session.modified = True
wish.sudo().unlink()
else:
- wish.active = False
+ wish.unlink()
return True