summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-06-06 11:54:35 +0200
committerChristophe Simonis <chs@odoo.com>2019-06-06 11:54:35 +0200
commit71a50a2214748fec413c5a0f042d0e7172ae6ea9 (patch)
tree2e9ee11551350e23923390d9c53baf630b6ed834
parenta7621137cb0d75ec545152d5e1d413c9d3548e45 (diff)
parent409679866b2ee8352a8cb27460e7332563526924 (diff)
[MERGE] forward port branch saas-12.3 up to 409679866b2
-rw-r--r--SECURITY.md40
-rw-r--r--addons/account/models/account.py33
-rw-r--r--addons/account/models/account_bank_statement.py123
-rw-r--r--addons/account/models/account_invoice.py23
-rw-r--r--addons/account/models/account_journal_dashboard.py11
-rw-r--r--addons/account/models/account_move.py72
-rw-r--r--addons/account/models/chart_template.py6
-rw-r--r--addons/account/models/product.py1
-rw-r--r--addons/account/models/reconciliation_widget.py45
-rw-r--r--addons/account/report/account_invoice_report.py8
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_action.js9
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_model.js4
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_renderer.js10
-rw-r--r--addons/account/tests/test_account_customer_invoice.py18
-rw-r--r--addons/account/tests/test_invoice_taxes.py14
-rw-r--r--addons/account/tests/test_reconciliation.py77
-rw-r--r--addons/account/views/account_invoice_view.xml2
-rw-r--r--addons/account/views/account_move_views.xml1
-rw-r--r--addons/account/views/account_payment_view.xml3
-rw-r--r--addons/account/wizard/wizard_tax_adjustments.py1
-rw-r--r--addons/account_analytic_default/models/account_analytic_default.py18
-rw-r--r--addons/account_payment/controllers/payment.py12
-rw-r--r--addons/account_voucher/models/account_voucher.py2
-rw-r--r--addons/bus/controllers/main.py4
-rw-r--r--addons/calendar/models/calendar.py12
-rw-r--r--addons/calendar_sms/models/calendar.py2
-rw-r--r--addons/crm/models/crm_lead.py15
-rw-r--r--addons/event_sale/models/sale_order.py2
-rw-r--r--addons/gamification/models/res_users.py24
-rw-r--r--addons/google_account/models/google_service.py6
-rw-r--r--addons/hr/models/res_users.py4
-rw-r--r--addons/hr/views/res_users.xml2
-rw-r--r--addons/hr_attendance_presence/views/hr_employee.xml2
-rw-r--r--addons/hr_contract/models/hr_contract.py6
-rw-r--r--addons/hr_expense/models/hr_expense.py36
-rw-r--r--addons/hr_holidays/models/hr_employee.py4
-rw-r--r--addons/hr_holidays/models/hr_leave.py53
-rw-r--r--addons/hr_holidays/models/hr_leave_allocation.py5
-rw-r--r--addons/hr_holidays/tests/test_leave_requests.py2
-rw-r--r--addons/hr_holidays/views/hr_leave_views.xml2
-rw-r--r--addons/hr_maintenance/views/hr_views.xml2
-rw-r--r--addons/hr_org_chart/controllers/hr_org_chart.py2
-rw-r--r--addons/hw_escpos/escpos/escpos.py25
-rw-r--r--addons/hw_scale/controllers/main.py11
-rw-r--r--addons/iap/models/iap.py2
-rw-r--r--addons/iap/security/ir.model.access.csv2
-rw-r--r--addons/im_livechat/models/im_livechat_channel.py9
-rw-r--r--addons/im_livechat/tests/test_get_mail_channel.py8
-rw-r--r--addons/im_support/static/src/js/support_message.js5
-rw-r--r--addons/l10n_ae/__manifest__.py4
-rw-r--r--addons/l10n_ae/data/account_data.xml12
-rw-r--r--addons/l10n_ae/data/account_tax_report_data.xml421
-rw-r--r--addons/l10n_ae/data/account_tax_template_data.xml837
-rw-r--r--addons/l10n_ae/data/fiscal_templates_data.xml112
-rw-r--r--addons/l10n_ae/data/l10n_ae_chart_data.xml4
-rw-r--r--addons/l10n_ae/data/l10n_ae_data.xml14
-rw-r--r--addons/l10n_ae/views/report_invoice_templates.xml35
-rw-r--r--addons/l10n_be/data/account_tax_template_data.xml2
-rw-r--r--addons/l10n_be_edi/security/security.xml7
-rw-r--r--addons/l10n_ca/data/account_tax_data.xml1
-rw-r--r--addons/l10n_ch/views/res_bank_view.xml2
-rw-r--r--addons/l10n_fr_fec/wizard/account_fr_fec.py4
-rw-r--r--addons/l10n_hn/data/account_chart_template_data.xml2
-rw-r--r--addons/l10n_in/models/account_invoice.py4
-rw-r--r--addons/l10n_it/data/account.tax.group.csv1
-rw-r--r--addons/l10n_it/data/account_tax_template.xml68
-rw-r--r--addons/l10n_mn/__init__.py2
-rw-r--r--addons/l10n_mn/__manifest__.py29
-rw-r--r--addons/l10n_mn/data/account.account.tag.csv94
-rw-r--r--addons/l10n_mn/data/account.account.template.csv285
-rw-r--r--addons/l10n_mn/data/account.chart.template.csv2
-rw-r--r--addons/l10n_mn/data/account.tax.group.csv7
-rw-r--r--addons/l10n_mn/data/account_chart_template_configuration_data.xml8
-rw-r--r--addons/l10n_mn/data/account_chart_template_data.xml15
-rw-r--r--addons/l10n_mn/data/account_fiscal_position_template_data.xml399
-rw-r--r--addons/l10n_mn/data/account_tax_template_data.xml1476
-rw-r--r--addons/l10n_mn/static/description/icon.pngbin0 -> 19419 bytes
-rw-r--r--addons/l10n_mx/data/account_tag_data.xml22
-rw-r--r--addons/l10n_za/__init__.py0
-rw-r--r--addons/l10n_za/__manifest__.py26
-rw-r--r--addons/l10n_za/data/account.account.tag.csv17
-rw-r--r--addons/l10n_za/data/account.account.template.csv111
-rw-r--r--addons/l10n_za/data/account.tax.group.csv3
-rw-r--r--addons/l10n_za/data/account_chart_template_configure_data.xml8
-rw-r--r--addons/l10n_za/data/account_chart_template_data.xml15
-rw-r--r--addons/l10n_za/data/account_chart_template_post_data.xml17
-rw-r--r--addons/l10n_za/data/account_tax_report_data.xml248
-rw-r--r--addons/l10n_za/data/account_tax_template_data.xml558
-rw-r--r--addons/l10n_za/static/description/icon.pngbin0 -> 7782 bytes
-rw-r--r--addons/lunch/controllers/main.py18
-rw-r--r--addons/lunch/models/lunch_order.py6
-rw-r--r--addons/lunch/report/lunch_product_report.py11
-rw-r--r--addons/lunch/static/src/js/lunch_kanban_controller.js7
-rw-r--r--addons/lunch/static/tests/lunch_kanban_tests.js48
-rw-r--r--addons/lunch/views/res_config_settings.xml1
-rw-r--r--addons/lunch/wizard/lunch_line_wizard.py2
-rw-r--r--addons/mail/models/mail_activity.py4
-rw-r--r--addons/mail/models/mail_message.py26
-rw-r--r--addons/mail/static/src/js/chatter.js4
-rw-r--r--addons/mail/static/src/js/models/messages/message.js3
-rw-r--r--addons/mail/static/src/js/services/mail_manager.js4
-rw-r--r--addons/mail/static/src/js/services/mail_notification_manager.js3
-rw-r--r--addons/mail/static/src/js/services/mail_status_manager.js10
-rw-r--r--addons/mail/static/tests/chatter_tests.js2
-rw-r--r--addons/mail/static/tests/mail_status_manager_tests.js14
-rw-r--r--addons/mail/wizard/mail_compose_message.py5
-rw-r--r--addons/mass_mailing/models/mass_mailing.py2
-rw-r--r--addons/mass_mailing/wizard/test_mailing.py2
-rw-r--r--addons/mrp/models/mrp_production.py4
-rw-r--r--addons/mrp/models/stock_warehouse.py2
-rw-r--r--addons/mrp/tests/test_workorder_operation.py23
-rw-r--r--addons/mrp/wizard/change_production_qty.py2
-rw-r--r--addons/payment/controllers/portal.py13
-rw-r--r--addons/payment/models/payment_acquirer.py2
-rw-r--r--addons/payment/static/src/js/payment_processing.js11
-rw-r--r--addons/payment_authorize/models/authorize_request.py6
-rw-r--r--addons/payment_authorize/models/payment.py23
-rw-r--r--addons/point_of_sale/models/pos_order.py3
-rw-r--r--addons/point_of_sale/static/src/js/gui.js5
-rw-r--r--addons/point_of_sale/static/src/js/models.js9
-rw-r--r--addons/point_of_sale/static/src/xml/pos.xml2
-rw-r--r--addons/point_of_sale/views/point_of_sale_dashboard.xml1
-rw-r--r--addons/portal/controllers/mail.py2
-rw-r--r--addons/portal/models/mail_message.py4
-rw-r--r--addons/pos_sale/report/sale_report.py15
-rw-r--r--addons/product/models/product.py11
-rw-r--r--addons/product/models/product_template.py66
-rw-r--r--addons/product/tests/test_product_attribute_value_config.py15
-rw-r--r--addons/product/tests/test_variants.py73
-rw-r--r--addons/product_email_template/models/product.py4
-rw-r--r--addons/product_email_template/views/product_views.xml2
-rw-r--r--addons/purchase/models/account_invoice.py5
-rw-r--r--addons/purchase_stock/models/account_invoice.py43
-rw-r--r--addons/purchase_stock/models/purchase.py4
-rw-r--r--addons/purchase_stock/models/stock.py2
-rw-r--r--addons/purchase_stock/tests/test_stockvaluation.py148
-rw-r--r--addons/sale/controllers/variant.py13
-rw-r--r--addons/sale/models/account_invoice.py2
-rw-r--r--addons/sale/models/payment.py12
-rw-r--r--addons/sale/models/sale.py4
-rw-r--r--addons/sale/report/report_all_channels_sales.py2
-rw-r--r--addons/sale/static/src/js/variant_mixin.js156
-rw-r--r--addons/sale/views/res_config_settings_views.xml2
-rw-r--r--addons/sale/views/sale_views.xml1
-rw-r--r--addons/sale_coupon/tests/common.py9
-rw-r--r--addons/sale_product_configurator/static/src/js/product_configurator_modal.js6
-rw-r--r--addons/sale_product_configurator/static/src/js/product_configurator_renderer.js16
-rw-r--r--addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js13
-rw-r--r--addons/sale_stock/models/sale_order.py15
-rw-r--r--addons/sale_timesheet/models/sale_order.py4
-rw-r--r--addons/sales_team/tests/__init__.py1
-rw-r--r--addons/sales_team/tests/test_default_team.py48
-rw-r--r--addons/sms/models/mail_thread.py4
-rw-r--r--addons/sms/wizard/send_sms.py19
-rw-r--r--addons/snailmail/models/snailmail_letter.py64
-rw-r--r--addons/snailmail_account/wizard/account_invoice_send.py2
-rw-r--r--addons/stock/models/product.py4
-rw-r--r--addons/stock/models/stock_move.py5
-rw-r--r--addons/stock/models/stock_warehouse.py1
-rw-r--r--addons/stock/report/report_stock_forecast.py3
-rw-r--r--addons/stock/wizard/stock_picking_return.py2
-rw-r--r--addons/stock/wizard/stock_picking_return_views.xml2
-rw-r--r--addons/stock_account/models/__init__.py1
-rw-r--r--addons/stock_account/models/account_reconciliation_widget.py37
-rw-r--r--addons/stock_account/tests/test_stockvaluation.py6
-rw-r--r--addons/survey/views/assets.xml6
-rw-r--r--addons/test_mail/tests/test_mail_template.py81
-rw-r--r--addons/web/static/src/js/chrome/abstract_web_client.js6
-rw-r--r--addons/web/static/src/js/chrome/action_manager_act_window.js21
-rw-r--r--addons/web/static/src/js/core/ajax.js12
-rw-r--r--addons/web/static/src/js/tools/test_menus.js6
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_controller.js3
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_model.js4
-rw-r--r--addons/web/static/src/js/widgets/date_picker.js18
-rw-r--r--addons/web/static/src/scss/bootstrap_review.scss14
-rw-r--r--addons/web/static/src/scss/form_view_extra.scss2
-rw-r--r--addons/web/static/src/scss/kanban_view.scss1
-rw-r--r--addons/web/static/src/scss/list_view.scss4
-rw-r--r--addons/web/static/tests/chrome/action_manager_tests.js24
-rw-r--r--addons/web/static/tests/fields/basic_fields_tests.js19
-rw-r--r--addons/web/static/tests/views/calendar_tests.js41
-rw-r--r--addons/web/static/tests/views/form_tests.js66
-rw-r--r--addons/web/static/tests/views/list_tests.js42
-rw-r--r--addons/web_editor/static/lib/summernote/summernote.js19
-rw-r--r--addons/web_editor/static/src/js/backend/field_html.js2
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/plugin/bullet.js7
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/plugin/media.js6
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/plugin/text.js5
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/widgets/media.js1
-rw-r--r--addons/web_editor/static/tests/wysiwyg_tests.js32
-rw-r--r--addons/web_tour/static/src/js/tour_manager.js3
-rw-r--r--addons/website/models/res_users.py9
-rw-r--r--addons/website/models/website.py11
-rw-r--r--addons/website/static/src/js/content/snippets.animation.js4
-rw-r--r--addons/website/static/src/js/content/website_root.js31
-rw-r--r--addons/website/static/src/js/menu/seo.js62
-rw-r--r--addons/website/static/tests/tours/rte.js1
-rw-r--r--addons/website/tests/test_get_current_website.py7
-rw-r--r--addons/website/views/website_templates.xml23
-rw-r--r--addons/website_blog/views/snippets.xml3
-rw-r--r--addons/website_crm_partner_assign/tests/test_partner_assign.py15
-rw-r--r--addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml8
-rw-r--r--addons/website_customer/views/website_customer_templates.xml9
-rw-r--r--addons/website_event/controllers/main.py5
-rw-r--r--addons/website_event/views/event_templates.xml16
-rw-r--r--addons/website_forum/controllers/main.py6
-rw-r--r--addons/website_forum/models/forum.py5
-rw-r--r--addons/website_forum/models/res_users.py15
-rw-r--r--addons/website_forum/static/src/scss/website_forum.scss1
-rw-r--r--addons/website_forum/views/website_forum.xml115
-rw-r--r--addons/website_links/static/src/js/website_links_menu.js7
-rw-r--r--addons/website_mail/models/mail_message.py5
-rw-r--r--addons/website_profile/controllers/main.py60
-rw-r--r--addons/website_profile/models/res_users.py9
-rw-r--r--addons/website_profile/views/website_profile.xml17
-rw-r--r--addons/website_sale/models/sale_order.py38
-rw-r--r--addons/website_sale/static/src/js/website_sale.js42
-rw-r--r--addons/website_sale/tests/test_website_sale_cart_recovery.py109
-rw-r--r--addons/website_sale_coupon/tests/test_shop_sale_coupon.py11
-rw-r--r--addons/website_sale_delivery/controllers/main.py5
-rw-r--r--addons/website_sale_product_configurator/static/src/js/product_configurator_modal.js4
-rw-r--r--addons/website_sale_product_configurator/static/src/js/website_sale_options.js2
-rw-r--r--addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js4
-rw-r--r--addons/website_slides/controllers/main.py6
-rw-r--r--addons/website_slides/static/src/js/slides_course_join.js7
-rw-r--r--addons/website_slides/static/src/js/slides_slide_like.js7
-rw-r--r--addons/website_slides/views/website_slides_templates_course.xml9
-rw-r--r--addons/website_slides/views/website_slides_templates_homepage.xml3
-rw-r--r--doc/_extensions/autojsdoc/parser/parser.py2
-rw-r--r--doc/cla/corporate/acsone.md1
-rw-r--r--doc/cla/corporate/ecoservice.md21
-rw-r--r--doc/cla/corporate/savoirfairelinux.md24
-rw-r--r--doc/cla/corporate/vauxoo.md1
-rw-r--r--doc/cla/individual/0yik.md11
-rw-r--r--doc/cla/individual/AfroMonkey.md11
-rw-r--r--doc/cla/individual/gjanssens.md11
-rw-r--r--doc/howtos/backend/exercise-basic-action2
-rw-r--r--doc/howtos/backend/exercise-searchview2
-rw-r--r--odoo/addons/base/models/ir_mail_server.py12
-rw-r--r--odoo/addons/base/models/ir_translation.py4
-rw-r--r--odoo/addons/base/models/res_company.py14
-rw-r--r--odoo/addons/base/models/res_config.py2
-rw-r--r--odoo/addons/base/models/res_partner.py2
-rw-r--r--odoo/addons/base/models/res_users.py19
-rw-r--r--odoo/addons/base/static/src/js/res_config_settings.js6
-rw-r--r--odoo/addons/base/static/tests/base_settings_tests.js8
-rw-r--r--odoo/addons/test_convert/__init__.py2
-rw-r--r--odoo/addons/test_convert/ir.model.access.csv1
-rw-r--r--odoo/addons/test_convert/models.py17
-rw-r--r--odoo/addons/test_convert/tests/test_convert.py21
-rw-r--r--odoo/addons/test_new_api/tests/test_new_fields.py7
-rw-r--r--odoo/fields.py7
-rw-r--r--odoo/models.py45
-rw-r--r--odoo/service/server.py2
-rw-r--r--odoo/tests/common.py3
-rw-r--r--odoo/tools/convert.py9
-rw-r--r--odoo/tools/image.py5
-rw-r--r--odoo/tools/translate.py5
258 files changed, 7196 insertions, 995 deletions
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 00000000000..98eb36db26e
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,40 @@
+# Security Policy
+
+## Supported Versions
+
+| Version | Supported |
+| ------- | ------------------ |
+| 12.0 | :white_check_mark: |
+| 11.0 | :white_check_mark: |
+| 10.0 | :white_check_mark: |
+| <=9.0 | :x: |
+
+## Reporting a Vulnerability
+
+Please share privately the details of your security vulnerability by contacting our Security Team:
+[Contact Info](https://www.odoo.com/security-report)
+
+Make sure to include as much information as possible, with the detailed steps to reproduce the problem,
+the versions that are affected, the expected results and actual results, and any other information that
+might help us react faster and more efficiently.
+
+We tend to prefer _text-based descriptions_ accompanied with a proof-of-concept script/exploit, rather
+than screenshots and videos.
+
+Our [Responsible Disclosure](https://www.odoo.com/security-report) page gives an overview of the
+process, including:
+
+ - Our Incident Response Procedure (what will happen after you report an issue)
+ - Our Rules (what you can and cannot do while researching security issues)
+ - Guidelines with **DO REPORT** and **DO NOT REPORT** issues
+ (what kind of issues will be accepted/rejected)
+
+
+## Important note
+
+We receive a majority of security reports that have little to no impact on the security of Odoo or
+the Odoo Cloud, and we ultimately have to reject them. To avoid a disappointing experience when
+contacting us, please try to put together a proof-of-concept attack and take a critical look at
+what's really at risk.
+If the proposed attack scenario turns out unrealistic, your report will probably be rejected.
+Also be sure to review our list of [non-qualifying issues](https://www.odoo.com/security-report#what).
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 0931f8ebd3d..6be56cd4795 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -1067,18 +1067,20 @@ class AccountTax(models.Model):
@api.model
def default_get(self, vals):
- rslt = super(AccountTax, self).default_get(vals)
+ # company_id is added so that we are sure to fetch a default value from it to use in repartition lines, below
+ rslt = super(AccountTax, self).default_get(vals + ['company_id'])
+ company_id = rslt.get('company_id')
if 'refund_repartition_line_ids' in vals:
rslt['refund_repartition_line_ids'] = [
- (0, 0, { 'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': rslt.get('company_id')}),
- (0, 0, { 'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': rslt.get('company_id')}),
+ (0, 0, { 'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': company_id}),
+ (0, 0, { 'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': company_id}),
]
if 'invoice_repartition_line_ids' in vals:
rslt['invoice_repartition_line_ids'] = [
- (0, 0, { 'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': rslt.get('company_id')}),
- (0, 0, { 'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': rslt.get('company_id')}),
+ (0, 0, { 'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': company_id}),
+ (0, 0, { 'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': company_id}),
]
return rslt
@@ -1174,7 +1176,8 @@ class AccountTax(models.Model):
str(invoice_tax_val['account_id']) + '-' + \
str(invoice_tax_val['account_analytic_id']) + '-' + \
str(invoice_tax_val.get('analytic_tag_ids', [])) + '-' + \
- str(invoice_tax_val.get('tax_ids') or [])
+ str(invoice_tax_val.get('tax_ids') or []) + '-' + \
+ str(invoice_tax_val.get('tag_ids') or [])
def _compute_amount(self, base_amount, price_unit, quantity=1.0, product=None, partner=None):
""" Returns the amount of a single tax. base_amount is the actual amount on which the tax is applied, which is
@@ -1235,6 +1238,10 @@ class AccountTax(models.Model):
all_taxes += tax
return all_taxes
+ def get_tax_tags(self, is_refund, repartition_type):
+ rep_lines = self.mapped(is_refund and 'refund_repartition_line_ids' or 'invoice_repartition_line_ids')
+ return rep_lines.filtered(lambda x: x.repartition_type == repartition_type).mapped('tag_ids')
+
@api.multi
def compute_all(self, price_unit, currency=None, quantity=1.0, product=None, partner=None, is_refund=False):
""" Returns all information required to apply taxes (in self + their children in case of a tax group).
@@ -1392,12 +1399,23 @@ class AccountTax(models.Model):
if tax.price_include and not total_included_checkpoints.get(i):
cumulated_tax_included_amount += tax_amount
+ # If the tax affects the base of subsequent taxes, its tax move lines must
+ # receive the base tags and tag_ids of these taxes, so that the tax report computes
+ # the right total
+ subsequent_taxes = self.env['account.tax']
+ subsequent_tags = self.env['account.account.tag']
+ if tax.include_base_amount:
+ subsequent_taxes = taxes[i+1:]
+ subsequent_tags = subsequent_taxes.get_tax_tags(is_refund, 'base')
+
+ # Compute the tax lines
tax_repartition_lines = is_refund and tax.refund_repartition_line_ids or tax.invoice_repartition_line_ids
repartition_lines_to_treat = len(tax_repartition_lines)
total_amount = 0
for repartition_line in tax_repartition_lines.filtered(lambda x: x.repartition_type == 'tax'):
# In case some rounding error occurs, we compensate for it on the last line
line_amount = round(sign * tax_amount * repartition_line.factor if repartition_lines_to_treat != 1 else tax_amount - total_amount, prec)
+
taxes_vals.append({
'id': tax.id,
'name': partner and tax.with_context(lang=partner.lang).name or tax.name,
@@ -1409,7 +1427,8 @@ class AccountTax(models.Model):
'price_include': tax.price_include,
'tax_exigibility': tax.tax_exigibility,
'tax_repartition_line_id': repartition_line.id,
- 'tag_ids': [(6, False, repartition_line.tag_ids.ids)],
+ 'tag_ids': [(6, False, (repartition_line.tag_ids + subsequent_tags).ids)],
+ 'tax_ids': [(6, False, subsequent_taxes.ids)]
})
total_amount += line_amount
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 8cea4c9bd92..7537227fdae 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -446,7 +446,6 @@ class AccountBankStatementLine(models.Model):
""" Prepare the dict of values to balance the move.
:param recordset move: the account.move to link the move line
- :param dict move: a dict of vals of a account.move which will be created later
:param float amount: the amount of transaction that wasn't already reconciled
"""
company_currency = self.journal_id.company_id.currency_id
@@ -454,23 +453,19 @@ class AccountBankStatementLine(models.Model):
st_line_currency = self.currency_id or statement_currency
amount_currency = False
st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
- if isinstance(move, dict):
- amount_sum = sum(x[2].get('amount_currency', 0) for x in move['line_ids'])
- else:
- amount_sum = sum(x.amount_currency for x in move.line_ids)
# We have several use case here to compare the currency and amount currency of counterpart line to balance the move:
if st_line_currency != company_currency and st_line_currency == statement_currency:
# company in currency A, statement in currency B and transaction in currency B
# counterpart line must have currency B and correct amount is inverse of already existing lines
- amount_currency = -amount_sum
+ amount_currency = -sum([x.amount_currency for x in move.line_ids])
elif st_line_currency != company_currency and statement_currency == company_currency:
# company in currency A, statement in currency A and transaction in currency B
# counterpart line must have currency B and correct amount is inverse of already existing lines
- amount_currency = -amount_sum
+ amount_currency = -sum([x.amount_currency for x in move.line_ids])
elif st_line_currency != company_currency and st_line_currency != statement_currency:
# company in currency A, statement in currency B and transaction in currency C
# counterpart line must have currency B and use rate between B and C to compute correct amount
- amount_currency = -amount_sum/st_line_currency_rate
+ amount_currency = -sum([x.amount_currency for x in move.line_ids])/st_line_currency_rate
elif st_line_currency == company_currency and statement_currency != company_currency:
# company in currency A, statement in currency B and transaction in currency A
# counterpart line must have currency B and amount is computed using the rate between A and B
@@ -479,8 +474,9 @@ class AccountBankStatementLine(models.Model):
# last case is company in currency A, statement in currency A and transaction in currency A
# and in this case counterpart line does not need any second currency nor amount_currency
- aml_dict = {
+ return {
'name': self.name,
+ 'move_id': move.id,
'partner_id': self.partner_id and self.partner_id.id or False,
'account_id': amount >= 0 \
and self.statement_id.journal_id.default_credit_account_id.id \
@@ -491,71 +487,22 @@ class AccountBankStatementLine(models.Model):
'currency_id': statement_currency != company_currency and statement_currency.id or (st_line_currency != company_currency and st_line_currency.id or False),
'amount_currency': amount_currency,
}
- if isinstance(move, self.env['account.move'].__class__):
- aml_dict['move_id'] = move.id
- return aml_dict
- @api.multi
def fast_counterpart_creation(self):
"""This function is called when confirming a bank statement and will allow to automatically process lines without
going in the bank reconciliation widget. By setting an account_id on bank statement lines, it will create a journal
entry using that account to counterpart the bank account
"""
- payment_list = []
- move_list = []
- account_type_receivable = self.env.ref('account.data_account_type_receivable')
- already_done_stmt_line_ids = [a['statement_line_id'][0] for a in self.env['account.move.line'].read_group([('statement_line_id', 'in', self.ids)], ['statement_line_id'], ['statement_line_id'])]
- managed_st_line = []
for st_line in self:
# Technical functionality to automatically reconcile by creating a new move line
- if st_line.account_id and not st_line.id in already_done_stmt_line_ids:
- managed_st_line.append(st_line.id)
- # Create payment vals
- total = st_line.amount
- payment_methods = (total > 0) and st_line.journal_id.inbound_payment_method_ids or st_line.journal_id.outbound_payment_method_ids
- currency = st_line.journal_id.currency_id or st_line.company_id.currency_id
- partner_type = 'customer' if st_line.account_id.user_type_id == account_type_receivable else 'supplier'
- payment_list.append({
- 'payment_method_id': payment_methods and payment_methods[0].id or False,
- 'payment_type': total > 0 and 'inbound' or 'outbound',
- 'partner_id': st_line.partner_id.id,
- 'partner_type': partner_type,
- 'journal_id': st_line.statement_id.journal_id.id,
- 'payment_date': st_line.date,
- 'state': 'reconciled',
- 'currency_id': currency.id,
- 'amount': abs(total),
- 'communication': st_line._get_communication(payment_methods[0] if payment_methods else False),
- 'name': st_line.statement_id.name or _("Bank Statement %s") % st_line.date,
- })
-
- # Create move and move line vals
- move_vals = st_line._prepare_reconciliation_move(st_line.statement_id.name)
- aml_dict = {
+ if st_line.account_id and not st_line.journal_entry_ids.ids:
+ vals = {
'name': st_line.name,
'debit': st_line.amount < 0 and -st_line.amount or 0.0,
'credit': st_line.amount > 0 and st_line.amount or 0.0,
'account_id': st_line.account_id.id,
- 'partner_id': st_line.partner_id.id,
- 'statement_line_id': st_line.id,
}
- st_line._prepare_move_line_for_currency(aml_dict, st_line.date or fields.Date.context_today())
- move_vals['line_ids'] = [(0, 0, aml_dict)]
- balance_line = self._prepare_reconciliation_move_line(move_vals, st_line.amount)
- move_vals['line_ids'].append((0, 0, balance_line))
- move_list.append(move_vals)
-
- # Creates
- payment_ids = self.env['account.payment'].create(payment_list)
- for payment_id, move_vals in zip(payment_ids, move_list):
- for line in move_vals['line_ids']:
- line[2]['payment_id'] = payment_id.id
- move_ids = self.env['account.move'].create(move_list)
- move_ids.post()
-
- for move, st_line, payment in zip(move_ids, self.browse(managed_st_line), payment_ids):
- st_line.write({'move_name': move.name})
- payment.write({'payment_reference': move.name})
+ st_line.process_reconciliation(new_aml_dicts=[vals])
def _get_communication(self, payment_method_id):
return self.name or ''
@@ -683,6 +630,8 @@ class AccountBankStatementLine(models.Model):
# Create move line(s). Either matching an existing journal entry (eg. invoice), in which
# case we reconcile the existing and the new move lines together, or being a write-off.
if counterpart_aml_dicts or new_aml_dicts:
+ st_line_currency = self.currency_id or statement_currency
+ st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
# Create the move
self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1
@@ -705,12 +654,32 @@ class AccountBankStatementLine(models.Model):
# Complete dicts to create both counterpart move lines and write-offs
to_create = (counterpart_aml_dicts + new_aml_dicts)
+ company = self.company_id
date = self.date or fields.Date.today()
for aml_dict in to_create:
aml_dict['move_id'] = move.id
aml_dict['partner_id'] = self.partner_id.id
aml_dict['statement_line_id'] = self.id
- self._prepare_move_line_for_currency(aml_dict, date)
+ if st_line_currency.id != company_currency.id:
+ aml_dict['amount_currency'] = aml_dict['debit'] - aml_dict['credit']
+ aml_dict['currency_id'] = st_line_currency.id
+ if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
+ # Statement is in company currency but the transaction is in foreign currency
+ aml_dict['debit'] = company_currency.round(aml_dict['debit'] / st_line_currency_rate)
+ aml_dict['credit'] = company_currency.round(aml_dict['credit'] / st_line_currency_rate)
+ elif self.currency_id and st_line_currency_rate:
+ # Statement is in foreign currency and the transaction is in another one
+ aml_dict['debit'] = statement_currency._convert(aml_dict['debit'] / st_line_currency_rate, company_currency, company, date)
+ aml_dict['credit'] = statement_currency._convert(aml_dict['credit'] / st_line_currency_rate, company_currency, company, date)
+ else:
+ # Statement is in foreign currency and no extra currency is given for the transaction
+ aml_dict['debit'] = st_line_currency._convert(aml_dict['debit'], company_currency, company, date)
+ aml_dict['credit'] = st_line_currency._convert(aml_dict['credit'], company_currency, company, date)
+ elif statement_currency.id != company_currency.id:
+ # Statement is in foreign currency but the transaction is in company currency
+ prorata_factor = (aml_dict['debit'] - aml_dict['credit']) / self.amount_currency
+ aml_dict['amount_currency'] = prorata_factor * self.amount
+ aml_dict['currency_id'] = statement_currency.id
# Create write-offs
for aml_dict in new_aml_dicts:
@@ -760,36 +729,6 @@ class AccountBankStatementLine(models.Model):
counterpart_moves.assert_balanced()
return counterpart_moves
- @api.multi
- def _prepare_move_line_for_currency(self, aml_dict, date):
- self.ensure_one()
- company_currency = self.journal_id.company_id.currency_id
- statement_currency = self.journal_id.currency_id or company_currency
- st_line_currency = self.currency_id or statement_currency
- st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
- company = self.company_id
-
- if st_line_currency.id != company_currency.id:
- aml_dict['amount_currency'] = aml_dict['debit'] - aml_dict['credit']
- aml_dict['currency_id'] = st_line_currency.id
- if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
- # Statement is in company currency but the transaction is in foreign currency
- aml_dict['debit'] = company_currency.round(aml_dict['debit'] / st_line_currency_rate)
- aml_dict['credit'] = company_currency.round(aml_dict['credit'] / st_line_currency_rate)
- elif self.currency_id and st_line_currency_rate:
- # Statement is in foreign currency and the transaction is in another one
- aml_dict['debit'] = statement_currency._convert(aml_dict['debit'] / st_line_currency_rate, company_currency, company, date)
- aml_dict['credit'] = statement_currency._convert(aml_dict['credit'] / st_line_currency_rate, company_currency, company, date)
- else:
- # Statement is in foreign currency and no extra currency is given for the transaction
- aml_dict['debit'] = st_line_currency._convert(aml_dict['debit'], company_currency, company, date)
- aml_dict['credit'] = st_line_currency._convert(aml_dict['credit'], company_currency, company, date)
- elif statement_currency.id != company_currency.id:
- # Statement is in foreign currency but the transaction is in company currency
- prorata_factor = (aml_dict['debit'] - aml_dict['credit']) / self.amount_currency
- aml_dict['amount_currency'] = prorata_factor * self.amount
- aml_dict['currency_id'] = statement_currency.id
-
def _check_invoice_state(self, invoice):
if invoice.state == 'in_payment' and all([payment.state == 'reconciled' for payment in invoice.mapped('payment_move_line_ids.payment_id')]):
invoice.write({'state': 'paid'})
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 47c0cb42b27..8770b232808 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -879,7 +879,7 @@ class AccountInvoice(models.Model):
domain = {}
company_id = self.company_id.id
p = self.partner_id if not company_id else self.partner_id.with_context(force_company=company_id)
- type = self.type
+ type = self.type or self.env.context.get('type', 'out_invoice')
if p:
rec_account = p.property_account_receivable_id
pay_account = p.property_account_payable_id
@@ -1081,7 +1081,7 @@ class AccountInvoice(models.Model):
else:
return self.env.ref('account.invoice_form').id
- def _prepare_tax_line_vals(self, line, tax, tax_ids):
+ def _prepare_tax_line_vals(self, line, tax):
''' Prepare values to create an account.invoice.tax line.
:param line: An account.invoice.line record.
:param tax: Tax values outputted by compute_all() in account.tax.
@@ -1099,8 +1099,9 @@ class AccountInvoice(models.Model):
'account_analytic_id': tax['analytic'] and line.account_analytic_id.id or False,
'account_id': tax['account_id'] or line.account_id.id,
'analytic_tag_ids': tax['analytic'] and line.analytic_tag_ids.ids or False,
- 'tax_ids': tax_ids and [(6, None, tax_ids)] or False,
'tax_repartition_line_id': tax.get('tax_repartition_line_id'), # For base amount, we let this field empty
+ 'tag_ids': tax['tag_ids'],
+ 'tax_ids': tax['tax_ids'],
}
# If the taxes generate moves on the same financial account as the invoice line,
@@ -1130,7 +1131,6 @@ class AccountInvoice(models.Model):
price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = line.invoice_line_tax_ids._origin.compute_all(price_unit, self.currency_id, line.quantity, line.product_id, self.partner_id, is_refund=self.type in ('in_refund', 'out_refund'))['taxes']
- affecting_base_tax_ids = []
for tax_vals in taxes:
# Retrieve the tax record (not in tax_vals when dealing with group of taxes).
if tax_map.get(tax_vals['id']):
@@ -1138,7 +1138,7 @@ class AccountInvoice(models.Model):
else:
tax = tax_map[tax_vals['id']] = self.env['account.tax'].browse(tax_vals['id'])
- val = self._prepare_tax_line_vals(line, tax_vals, affecting_base_tax_ids)
+ val = self._prepare_tax_line_vals(line, tax_vals)
key = tax.get_grouping_key(val)
if key not in tax_grouped:
@@ -1148,11 +1148,6 @@ class AccountInvoice(models.Model):
for field in default_tax_group_fields:
tax_grouped[key][field] += round_curr(val.get(field)) or 0
- if tax.amount_type == "group":
- affecting_base_tax_ids += tax.children_tax_ids.filtered(lambda t: is_tax_affecting_base_amount(t)).ids
- elif is_tax_affecting_base_amount(tax):
- affecting_base_tax_ids.append(tax.id)
-
return tax_grouped
@api.multi
@@ -1268,7 +1263,6 @@ class AccountInvoice(models.Model):
def tax_line_move_line_get(self):
self.ensure_one()
-
res = []
# loop the invoice.tax.line in reversal sequence
for tax_line in sorted(self.tax_line_ids, key=lambda x: -x.sequence):
@@ -1288,7 +1282,8 @@ class AccountInvoice(models.Model):
'invoice_id': self.id,
'tax_ids': tax_line.tax_ids and [(6, 0, tax_line.tax_ids.ids)] or False, # We don't pass an empty recordset here, as it would reset the tax_exibility of the line, due to the condition in account.move.line's create
'tax_repartition_line_id': tax_line.tax_repartition_line_id.id,
- 'tag_ids': [(6, 0, tax_line.tax_repartition_line_id.tag_ids.ids)],
+ 'tag_ids': [(6, 0, tax_line.tag_ids.ids)],
+ 'tax_base_amount': tax_line.base,
})
return res
@@ -2146,8 +2141,8 @@ class AccountInvoiceTax(models.Model):
company_id = fields.Many2one('res.company', string='Company', related='account_id.company_id', store=True, readonly=True)
currency_id = fields.Many2one('res.currency', related='invoice_id.currency_id', store=True, readonly=True)
base = fields.Monetary(string='Base')
- tax_ids = fields.Many2many('account.tax', string='Affecting Base Taxes',
- help='Taxes affecting the tax base amount applied before this one.')
+ tax_ids = fields.Many2many('account.tax', string='Affecting Base Taxes', help='Taxes whose base amount needs has to be affected by this tax line.')
+ tag_ids = fields.Many2many(string="Tags", comodel_name='account.account.tag', help="The taxes that will be applied on the move line generated for this tax entry")
@api.depends('amount', 'amount_rounding')
def _compute_amount_total(self):
diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py
index cf1f40f7a94..08b119405b4 100644
--- a/addons/account/models/account_journal_dashboard.py
+++ b/addons/account/models/account_journal_dashboard.py
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta
from babel.dates import format_datetime, format_date
from odoo import models, api, _, fields
+from odoo.osv import expression
from odoo.release import version
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF, safe_eval
from odoo.tools.misc import formatLang, format_date as odoo_format_date
@@ -427,10 +428,16 @@ class account_journal(models.Model):
action_name = 'action_view_bank_statement_tree'
elif self.type == 'sale':
action_name = 'action_invoice_tree1'
- self = self.with_context(use_domain=[('journal_id', '=', self.id)])
+ use_domain = expression.AND(
+ [self.env.context.get('use_domain', []), [('journal_id', '=', self.id)]]
+ )
+ self = self.with_context(use_domain=use_domain)
elif self.type == 'purchase':
action_name = 'action_vendor_bill_template'
- self = self.with_context(use_domain=[('journal_id', '=', self.id)])
+ use_domain = expression.AND(
+ [self.env.context.get('use_domain', []), [('journal_id', '=', self.id)]]
+ )
+ self = self.with_context(use_domain=use_domain)
else:
action_name = 'action_move_journal_line'
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 8fb43c81d3d..19e3ea5dec4 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -87,14 +87,12 @@ class AccountMove(models.Model):
if line.account_id.user_type_id.type in ('receivable', 'payable'):
amount = abs(line.debit - line.credit)
total_amount += amount
+ for partial_line in (line.matched_debit_ids + line.matched_credit_ids):
+ total_reconciled += partial_line.amount
precision_currency = move.currency_id or move.company_id.currency_id
if float_is_zero(total_amount, precision_rounding=precision_currency.rounding):
move.matched_percentage = 1.0
else:
- for line in move.line_ids:
- if line.account_id.user_type_id.type in ('receivable', 'payable'):
- for partial_line in (line.matched_debit_ids + line.matched_credit_ids):
- total_reconciled += partial_line.amount
move.matched_percentage = total_reconciled / total_amount
@api.multi
@@ -290,6 +288,7 @@ class AccountMove(models.Model):
'company_currency_id': self.company_id.currency_id.id,
'tax_repartition_line_id': line_vals['tax_repartition_line_id'],
'tag_ids': line_vals['tag_ids'],
+ 'tax_base_amount': line_vals['base'],
}
# N.B. currency_id/amount_currency are not set because if we have two lines with the same tax
# and different currencies, we have no idea which currency set on this line.
@@ -580,7 +579,7 @@ class AccountMoveLine(models.Model):
else:
date = partial_line.credit_move_id.date if partial_line.debit_move_id == line else partial_line.debit_move_id.date
rate = line.currency_id.with_context(date=date).rate
- amount_residual_currency += sign_partial_line * line.currency_id.round(partial_line.amount * rate)
+ amount_residual_currency += sign_partial_line * partial_line.amount * rate
#computing the `reconciled` field.
reconciled = False
@@ -609,15 +608,6 @@ class AccountMoveLine(models.Model):
currency = self.env['account.journal'].browse(context['default_journal_id']).currency_id
return currency
- @api.depends('move_id.line_ids', 'move_id.line_ids.tax_line_id', 'move_id.line_ids.debit', 'move_id.line_ids.credit')
- def _compute_tax_base_amount(self):
- for move_line in self:
- if move_line.tax_line_id:
- base_lines = move_line.move_id.line_ids.filtered(lambda line: move_line.tax_line_id in line.tax_ids)
- move_line.tax_base_amount = abs(sum(base_lines.mapped('balance')))
- else:
- move_line.tax_base_amount = 0
-
@api.depends('move_id')
def _compute_parent_state(self):
for record in self.filtered('move_id'):
@@ -641,7 +631,7 @@ class AccountMoveLine(models.Model):
help="The residual amount on a journal item expressed in the company currency.")
amount_residual_currency = fields.Monetary(compute='_amount_residual', string='Residual Amount in Currency', store=True,
help="The residual amount on a journal item expressed in its currency (possibly not the company currency).")
- tax_base_amount = fields.Monetary(string="Base Amount", compute='_compute_tax_base_amount', currency_field='company_currency_id', store=True)
+ tax_base_amount = fields.Monetary(string="Base Amount", currency_field='company_currency_id')
account_id = fields.Many2one('account.account', string='Account', required=True, index=True,
ondelete="cascade", domain=[('deprecated', '=', False)], default=lambda self: self._context.get('account_id', False))
move_id = fields.Many2one('account.move', string='Journal Entry', ondelete="cascade",
@@ -699,21 +689,23 @@ class AccountMoveLine(models.Model):
if {'debit', 'credit', 'partner_id', 'account_id'}.isdisjoint(fields):
return rec
#compute the default credit/debit of the next line in case of a manual entry
- AccountMove = self.env['account.move']
+ move = self.env['account.move'].new({'line_ids': self._context['line_ids']})
balance = 0
- for line in AccountMove.resolve_2many_commands(
- 'line_ids', self._context['line_ids'], fields=['credit', 'debit']):
- balance += line.get('debit', 0) - line.get('credit', 0)
- if len(self._context['line_ids']) > 1:
- lines = AccountMove.resolve_2many_commands('line_ids', self._context['line_ids'][-2:], fields=['partner_id', 'account_id'])
- if lines[0].get('partner_id', False) == lines[1].get('partner_id', False):
- rec.update({'partner_id': lines[0].get('partner_id', False)})
- if lines[0].get('account_id', False) == lines[1].get('account_id', False):
- rec.update({'account_id': lines[0].get('account_id', False)})
+ for line in move.line_ids:
+ balance += line.debit - line.credit
if balance < 0:
rec.update({'debit': -balance})
if balance > 0:
rec.update({'credit': balance})
+
+ # Set the partner/account to the same value as the two last lines if they are equal
+ if len(move.line_ids) > 1:
+ last_lines = move.line_ids[-2:]
+ if last_lines[0].account_id == last_lines[1].account_id:
+ rec['account_id'] = last_lines[0].account_id.id
+ if last_lines[0].partner_id == last_lines[1].partner_id:
+ rec['partner_id'] = last_lines[0].partner_id.id
+
return rec
@api.multi
@@ -821,21 +813,22 @@ class AccountMoveLine(models.Model):
In case of full reconciliation, all moves belonging to the reconciliation will belong to the same account_full_reconcile object.
"""
# Get first all aml involved
- todo = self.env['account.partial.reconcile'].search_read(['|', ('debit_move_id', 'in', self.ids), ('credit_move_id', 'in', self.ids)], ['debit_move_id', 'credit_move_id'])
- amls = set(self.ids)
+ part_recs = self.env['account.partial.reconcile'].search(['|', ('debit_move_id', 'in', self.ids), ('credit_move_id', 'in', self.ids)])
+ amls = self
+ todo = set(part_recs)
seen = set()
while todo:
- aml_ids = [rec['debit_move_id'][0] for rec in todo if rec['debit_move_id']] + [rec['credit_move_id'][0] for rec in todo if rec['credit_move_id']]
- amls |= set(aml_ids)
- seen |= set([rec['id'] for rec in todo])
- todo = self.env['account.partial.reconcile'].search_read(['&', '|', ('credit_move_id', 'in', aml_ids), ('debit_move_id', 'in', aml_ids), '!', ('id', 'in', list(seen))], ['debit_move_id', 'credit_move_id'])
-
- partial_rec_ids = list(seen)
+ partial_rec = todo.pop()
+ seen.add(partial_rec)
+ for aml in [partial_rec.debit_move_id, partial_rec.credit_move_id]:
+ if aml not in amls:
+ amls += aml
+ for x in aml.matched_debit_ids | aml.matched_credit_ids:
+ if x not in seen:
+ todo.add(x)
+ partial_rec_ids = [x.id for x in seen]
if not amls:
return
- else:
- amls = self.browse(list(amls))
-
# If we have multiple currency, we can only base ourselve on debit-credit to see if it is fully reconciled
currency = set([a.currency_id for a in amls if a.currency_id.id != False])
multiple_currency = False
@@ -998,7 +991,10 @@ class AccountMoveLine(models.Model):
debit_moves = debit_moves.sorted(key=lambda a: (a.date_maturity or a.date, a.currency_id))
credit_moves = credit_moves.sorted(key=lambda a: (a.date_maturity or a.date, a.currency_id))
# Compute on which field reconciliation should be based upon:
- field = self[0].account_id.currency_id and 'amount_residual_currency' or 'amount_residual'
+ if self[0].account_id.currency_id and self[0].account_id.currency_id != self[0].account_id.company_id.currency_id:
+ field = 'amount_residual_currency'
+ else:
+ field = 'amount_residual'
#if all lines share the same currency, use amount_residual_currency to avoid currency rounding error
if self[0].currency_id and all([x.amount_currency and x.currency_id == self[0].currency_id for x in self]):
field = 'amount_residual_currency'
@@ -1712,6 +1708,7 @@ class AccountPartialReconcile(models.Model):
'move_id': newly_created_move.id,
'partner_id': line.partner_id.id,
'tax_repartition_line_id': line.tax_repartition_line_id.id,
+ 'tax_base_amount': line.tax_base_amount,
'tag_ids': [(6, 0, line.tag_ids.ids)],
})
if line.account_id.reconcile:
@@ -1737,6 +1734,7 @@ class AccountPartialReconcile(models.Model):
'amount_currency': self.amount_currency and line.currency_id.round(line.amount_currency * amount / line.balance) or 0.0,
'partner_id': line.partner_id.id,
'tax_repartition_line_id': line.tax_repartition_line_id.id,
+ 'tax_base_amount': line.tax_base_amount,
'tag_ids': [(6, 0, line.tag_ids.ids)],
})
self.env['account.move.line'].with_context(check_move_validity=False).create({
diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py
index 53b36274de1..6409ae13445 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -894,7 +894,9 @@ class AccountTaxTemplate(models.Model):
'account_dict': dictionary containing a to-do list with all the accounts to assign on new taxes
}
"""
- ChartTemplate = self.env['account.chart.template']
+ # default_company_id is needed in context to allow creation of default
+ # repartition lines on taxes
+ ChartTemplate = self.env['account.chart.template'].with_context(default_company_id=company.id)
todo_dict = {'account.tax': {}, 'account.tax.repartition.line': {}}
tax_template_to_tax = {}
@@ -926,7 +928,7 @@ class AccountTaxTemplate(models.Model):
# We also have to delay the assignation of accounts to repartition lines
all_tax_rep_lines = tax.invoice_repartition_line_ids + tax.refund_repartition_line_ids
all_template_rep_lines = template.invoice_repartition_line_ids + template.refund_repartition_line_ids
- for i in range(0, len(all_tax_rep_lines)):
+ for i in range(0, len(all_template_rep_lines)):
# We assume template and tax repartition lines are in the same order
template_account = all_template_rep_lines[i].account_id
if template_account:
diff --git a/addons/account/models/product.py b/addons/account/models/product.py
index 655b457bbf0..f9b046c652a 100644
--- a/addons/account/models/product.py
+++ b/addons/account/models/product.py
@@ -81,4 +81,5 @@ class ProductProduct(models.Model):
'analytic_tag_ids': line.get('analytic_tag_ids', False),
'tax_repartition_line_id': line.get('tax_repartition_line_id'),
'tag_ids': line.get('tag_ids'),
+ 'tax_base_amount': line.get('tax_base_amount'),
}
diff --git a/addons/account/models/reconciliation_widget.py b/addons/account/models/reconciliation_widget.py
index 0d6577fe34c..f10bacfa3f3 100644
--- a/addons/account/models/reconciliation_widget.py
+++ b/addons/account/models/reconciliation_widget.py
@@ -83,28 +83,43 @@ class AccountReconciliation(models.AbstractModel):
@api.model
def _get_bank_statement_line_partners(self, st_lines):
+ params = []
+
+ # Add the res.partner.ban's IR rules. In case partners are not shared between companies,
+ # identical bank accounts may exist in a company we don't have access to.
+ ir_rules_query = self.env['res.partner.bank']._where_calc([])
+ self.env['res.partner.bank']._apply_ir_rules(ir_rules_query, 'read')
+ from_clause, where_clause, where_clause_params = ir_rules_query.get_sql()
+ if where_clause:
+ where_bank = ('AND %s' % where_clause).replace('res_partner_bank', 'bank')
+ params += where_clause_params
+ else:
+ where_bank = ''
+
+ # Add the res.partner's IR rules. In case partners are not shared between companies,
+ # identical partners may exist in a company we don't have access to.
+ ir_rules_query = self.env['res.partner']._where_calc([])
+ self.env['res.partner']._apply_ir_rules(ir_rules_query, 'read')
+ from_clause, where_clause, where_clause_params = ir_rules_query.get_sql()
+ if where_clause:
+ where_partner = ('AND %s' % where_clause).replace('res_partner', 'p3')
+ params += where_clause_params
+ else:
+ where_partner = ''
+
query = '''
SELECT
st_line.id AS id,
COALESCE(p1.id,p2.id,p3.id) AS partner_id
FROM account_bank_statement_line st_line
- LEFT JOIN res_partner_bank bank ON bank.id = st_line.bank_account_id OR bank.acc_number = st_line.account_number
- LEFT JOIN res_partner p1 ON st_line.partner_id=p1.id
- LEFT JOIN res_partner p2 ON bank.partner_id=p2.id
- LEFT JOIN res_partner p3 ON p3.name ILIKE st_line.partner_name
- WHERE st_line.id IN %s
'''
- params = [tuple(st_lines.ids)]
+ query += 'LEFT JOIN res_partner_bank bank ON bank.id = st_line.bank_account_id OR bank.acc_number = st_line.account_number %s\n' % (where_bank)
+ query += 'LEFT JOIN res_partner p1 ON st_line.partner_id=p1.id \n'
+ query += 'LEFT JOIN res_partner p2 ON bank.partner_id=p2.id \n'
+ query += 'LEFT JOIN res_partner p3 ON p3.name ILIKE st_line.partner_name %s\n' % (where_partner)
+ query += 'WHERE st_line.id IN %s'
- # Add the res.partner's IR rules to the WHERE clause. In case partners are not shared
- # between companies, identical partners may exist in a company we don't have access to.
- ir_rules_query = self.env['res.partner']._where_calc([])
- self.env['res.partner']._apply_ir_rules(ir_rules_query, 'read')
- from_clause, where_clause, where_clause_params = ir_rules_query.get_sql()
- where_p3 = (" AND %s" % where_clause).replace('res_partner', 'p3') if where_clause else ''
- if where_p3:
- query += where_p3
- params += where_clause_params
+ params += [tuple(st_lines.ids)]
self._cr.execute(query, params)
diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py
index 594a49d831e..b94bd711c63 100644
--- a/addons/account/report/account_invoice_report.py
+++ b/addons/account/report/account_invoice_report.py
@@ -112,13 +112,13 @@ class AccountInvoiceReport(models.Model):
1 AS nbr,
ai.id AS invoice_id, ai.type, ai.state, pt.categ_id, ai.date_due, ai.account_id, ail.account_id AS account_line_id,
ai.partner_bank_id,
- SUM ((invoice_type.sign_qty * ail.quantity) / u.factor * u2.factor) AS product_qty,
+ SUM ((invoice_type.sign_qty * ail.quantity) / COALESCE(u.factor,1) * COALESCE(u2.factor,1)) AS product_qty,
SUM(ail.price_subtotal_signed * invoice_type.sign) AS price_total,
- (ai.amount_total * invoice_type.sign) / (SELECT count(*) FROM account_invoice_line l where invoice_id = ai.id) *
+ ai.amount_total_signed / (SELECT count(*) FROM account_invoice_line l where invoice_id = ai.id) *
count(*) * invoice_type.sign AS amount_total,
SUM(ABS(ail.price_subtotal_signed)) / CASE
- WHEN SUM(ail.quantity / u.factor * u2.factor) <> 0::numeric
- THEN SUM(ail.quantity / u.factor * u2.factor)
+ WHEN SUM(ail.quantity / COALESCE(u.factor,1) * COALESCE(u2.factor,1)) <> 0::numeric
+ THEN SUM(ail.quantity / COALESCE(u.factor,1) * COALESCE(u2.factor,1))
ELSE 1::numeric
END AS price_average,
ai.residual_company_signed / (SELECT count(*) FROM account_invoice_line l where invoice_id = ai.id) *
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_action.js b/addons/account/static/src/js/reconciliation/reconciliation_action.js
index 67d5de9de08..5f27b0970f6 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_action.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_action.js
@@ -346,9 +346,12 @@ var StatementAction = AbstractAction.extend({
'context': self.model.getContext(),
});
_.each(result.handles, function (handle) {
- self._getWidget(handle).destroy();
- var index = _.findIndex(self.widgets, function (widget) {return widget.handle===handle;});
- self.widgets.splice(index, 1);
+ var widget = self._getWidget(handle);
+ if (widget) {
+ widget.destroy();
+ var index = _.findIndex(self.widgets, function (widget) {return widget.handle===handle;});
+ self.widgets.splice(index, 1);
+ }
});
// Get number of widget and if less than constant and if there are more to laod, load until constant
if (self.widgets.length < self.model.defaultDisplayQty
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index c28790796d2..d4dce5581ca 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -623,13 +623,13 @@ var StatementModel = BasicModel.extend({
prop = this._formatQuickCreate(line);
line.reconciliation_proposition.push(prop);
}
- if (!line.reconciliation_proposition.slice(0,-1).every(function(prop) {return prop.to_check == values.to_check;})) {
+ if ('to_check' in values && !line.reconciliation_proposition.slice(0,-1).every(function(prop) {return prop.to_check == values.to_check;})) {
new CrashManager().show_warning({data: {
exception_type: _t("Incorrect Operation"),
message: _t("You cannot mix items with and without the 'To Check' checkbox ticked.")
}});
// FIXME: model should not be tied to the DOM !
- $('.create_to_check input').click();
+ $('.create_to_check input:visible').prop('checked', !values.to_check).change();
return Promise.resolve();
}
_.each(values, function (value, fieldName) {
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
index b59b99bc881..53cd0b76598 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -938,10 +938,12 @@ var ManualLineRenderer = LineRenderer.extend({
* @override
*/
_renderCreate: function (state) {
- var parentPromise = this._super(state);
- this.$('.create .create_journal_id').show();
- this.$('.create .create_date').removeClass('d-none');
- this.$('.create .create_journal_id .o_input').addClass('o_required_modifier');
+ var self = this;
+ var parentPromise = this._super(state).then(function() {
+ self.$('.create .create_journal_id').show();
+ self.$('.create .create_date').removeClass('d-none');
+ self.$('.create .create_journal_id .o_input').addClass('o_required_modifier');
+ });
return parentPromise;
},
diff --git a/addons/account/tests/test_account_customer_invoice.py b/addons/account/tests/test_account_customer_invoice.py
index 5334151eebb..1a84c82cda2 100644
--- a/addons/account/tests/test_account_customer_invoice.py
+++ b/addons/account/tests/test_account_customer_invoice.py
@@ -306,30 +306,30 @@ class TestAccountCustomerInvoice(AccountTestUsers):
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 2)
- self.assertEquals(dashboard_data['sum_draft'], '$ 68.42')
+ self.assertIn('68.42', dashboard_data['sum_draft'])
self.assertEquals(dashboard_data['number_waiting'], 0)
- self.assertEquals(dashboard_data['sum_waiting'], '$ 0.00')
+ self.assertIn('0.00', dashboard_data['sum_waiting'])
# Check Both
invoice.action_invoice_open()
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 1)
- self.assertEquals(dashboard_data['sum_draft'], '$ -13.30')
+ self.assertIn('-13.30', dashboard_data['sum_draft'])
self.assertEquals(dashboard_data['number_waiting'], 1)
- self.assertEquals(dashboard_data['sum_waiting'], '$ 81.72')
+ self.assertIn('81.72', dashboard_data['sum_waiting'])
# Check waiting payment
refund.action_invoice_open()
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 0)
- self.assertEquals(dashboard_data['sum_draft'], '$ 0.00')
+ self.assertIn('0.00', dashboard_data['sum_draft'])
self.assertEquals(dashboard_data['number_waiting'], 2)
- self.assertEquals(dashboard_data['sum_waiting'], '$ 68.42')
+ self.assertIn('68.42', dashboard_data['sum_waiting'])
# Check partial
receivable_account = refund.move_id.line_ids.mapped('account_id').filtered(lambda a: a.internal_type == 'receivable')
@@ -353,12 +353,12 @@ class TestAccountCustomerInvoice(AccountTestUsers):
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 0)
- self.assertEquals(dashboard_data['sum_draft'], '$ 0.00')
+ self.assertIn('0.00', dashboard_data['sum_draft'])
self.assertEquals(dashboard_data['number_waiting'], 2)
- self.assertEquals(dashboard_data['sum_waiting'], '$ 78.42')
+ self.assertIn('78.42', dashboard_data['sum_waiting'])
with patch('odoo.fields.Date.today', patched_today):
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_late'], 2)
- self.assertEquals(dashboard_data['sum_late'], '$ 78.42')
+ self.assertIn('78.42', dashboard_data['sum_late'])
diff --git a/addons/account/tests/test_invoice_taxes.py b/addons/account/tests/test_invoice_taxes.py
index 4b0d8380f72..b6759271579 100644
--- a/addons/account/tests/test_invoice_taxes.py
+++ b/addons/account/tests/test_invoice_taxes.py
@@ -100,8 +100,8 @@ class TestInvoiceTaxes(AccountingTestCase):
Expected:
Tax | Taxes | Base | Amount
--------------------------------------------
- 21% incl | / | 100 | 21
- 12% | 21% incl | 121 | 14.52
+ 21% incl | 12% | 100 | 21
+ 12% | / | 121 | 14.52
12% | / | 100 | 12
'''
invoice = self._create_invoice([
@@ -110,9 +110,8 @@ class TestInvoiceTaxes(AccountingTestCase):
])
invoice.action_invoice_open()
self.assertRecordValues(invoice.tax_line_ids.sorted(lambda x: x.amount), [
- {'name': self.percent_tax_2.name, 'base': 100, 'amount': 12, 'tax_ids': []},
- {'name': self.percent_tax_2.name, 'base': 121, 'amount': 14.52, 'tax_ids': [self.percent_tax_1_incl.id]},
- {'name': self.percent_tax_1_incl.name, 'base': 100, 'amount': 21, 'tax_ids': []},
+ {'name': self.percent_tax_1_incl.name, 'base': 100, 'amount': 21, 'tax_ids': [self.percent_tax_2.id]},
+ {'name': self.percent_tax_2.name, 'base': 221, 'amount': 26.52, 'tax_ids': []},
])
def test_group_of_taxes(self):
@@ -135,9 +134,8 @@ class TestInvoiceTaxes(AccountingTestCase):
])
invoice.action_invoice_open()
self.assertRecordValues(invoice.tax_line_ids.sorted(lambda x: x.amount), [
- {'name': self.percent_tax_2.name, 'base': 100, 'amount': 12, 'tax_ids': []},
- {'name': self.percent_tax_2.name, 'base': 121, 'amount': 14.52, 'tax_ids': [self.percent_tax_1_incl.id]},
- {'name': self.percent_tax_1_incl.name, 'base': 100, 'amount': 21, 'tax_ids': []},
+ {'name': self.percent_tax_1_incl.name, 'base': 100, 'amount': 21, 'tax_ids': [self.percent_tax_2.id]},
+ {'name': self.percent_tax_2.name, 'base': 221, 'amount': 26.52, 'tax_ids': []},
])
def _create_tax_tag(self, tag_name):
diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py
index 62d414fdf54..0a35310f8a7 100644
--- a/addons/account/tests/test_reconciliation.py
+++ b/addons/account/tests/test_reconciliation.py
@@ -91,6 +91,11 @@ class TestReconciliation(AccountingTestCase):
'code': 'CABA',
'type': 'general',
})
+ self.general_journal = self.env['account.journal'].create({
+ 'name': 'general',
+ 'code': 'GENE',
+ 'type': 'general',
+ })
# Tax Cash Basis
self.tax_cash_basis = self.env['account.tax'].create({
@@ -1304,6 +1309,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 16.67,
'move_id': purchase_move.id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 83.33,
})
purchase_move.post()
@@ -1422,6 +1428,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 5,
'move_id': purchase_move.id,
'tax_repartition_line_id': tax_cash_basis10percent.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 50,
})
AccountMoveLine.create({
'name': 'expenseTaxed 20%',
@@ -1436,6 +1443,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 16.67,
'move_id': purchase_move.id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 83.33,
})
purchase_move.post()
@@ -1646,6 +1654,7 @@ class TestReconciliationExec(TestReconciliation):
'currency_id': self.currency_usd_id,
'amount_currency': 848.16,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 106841.65,
})
purchase_payable_line0 = aml_obj.create({
'name': 'Payable',
@@ -1816,6 +1825,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 17094.66,
'move_id': purchase_move.id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 106841.65,
'currency_id': self.currency_usd_id,
'amount_currency': 848.16,
})
@@ -1975,6 +1985,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 5,
'move_id': purchase_move.id,
'tax_repartition_line_id': tax_cash_basis10percent.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 50,
})
AccountMoveLine.create({
'name': 'expenseTaxed 20%',
@@ -1989,6 +2000,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 20,
'move_id': purchase_move.id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 100,
})
purchase_move.post()
@@ -2056,6 +2068,71 @@ class TestReconciliationExec(TestReconciliation):
self.assertTrue(pay_receivable_line1.reconciled)
self.assertEqual(pay_receivable_line1.matched_debit_ids, move_caba1.tax_cash_basis_rec_id)
+ def test_reconciliation_with_currency(self):
+ #reconciliation on an account having a foreign currency being
+ #the same as the company one
+ account_rcv = self.account_rcv
+ account_rcv.currency_id = self.currency_euro_id
+ aml_obj = self.env['account.move.line'].with_context(
+ check_move_validity=False)
+ general_move1 = self.env['account.move'].create({
+ 'name': 'general1',
+ 'journal_id': self.general_journal.id,
+ })
+ aml_obj.create({
+ 'name': 'debit1',
+ 'account_id': account_rcv.id,
+ 'debit': 11,
+ 'move_id': general_move1.id,
+ })
+ aml_obj.create({
+ 'name': 'credit1',
+ 'account_id': self.account_rsa.id,
+ 'credit': 11,
+ 'move_id': general_move1.id,
+ })
+ general_move1.post()
+ general_move2 = self.env['account.move'].create({
+ 'name': 'general2',
+ 'journal_id': self.general_journal.id,
+ })
+ aml_obj.create({
+ 'name': 'credit2',
+ 'account_id': account_rcv.id,
+ 'credit': 10,
+ 'move_id': general_move2.id,
+ })
+ aml_obj.create({
+ 'name': 'debit2',
+ 'account_id': self.account_rsa.id,
+ 'debit': 10,
+ 'move_id': general_move2.id,
+ })
+ general_move2.post()
+ general_move3 = self.env['account.move'].create({
+ 'name': 'general3',
+ 'journal_id': self.general_journal.id,
+ })
+ aml_obj.create({
+ 'name': 'credit3',
+ 'account_id': account_rcv.id,
+ 'credit': 1,
+ 'move_id': general_move3.id,
+ })
+ aml_obj.create({
+ 'name': 'debit3',
+ 'account_id': self.account_rsa.id,
+ 'debit': 1,
+ 'move_id': general_move3.id,
+ })
+ general_move3.post()
+ to_reconcile = ((general_move1 + general_move2 + general_move3)
+ .mapped('line_ids')
+ .filtered(lambda l: l.account_id.id == account_rcv.id))
+ to_reconcile.reconcile()
+ for aml in to_reconcile:
+ self.assertEqual(aml.amount_residual, 0.0)
+
def test_reconciliation_process_move_lines_with_mixed_currencies(self):
# Delete any old rate - to make sure that we use the ones we need.
old_rates = self.env['res.currency.rate'].search(
diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml
index b965b67f488..a55aafc795e 100644
--- a/addons/account/views/account_invoice_view.xml
+++ b/addons/account/views/account_invoice_view.xml
@@ -384,6 +384,7 @@
<field name="amount_total" invisible="1"/>
<field name="currency_id" invisible="1" force_save="1"/>
<field name="tax_repartition_line_id" invisible="1"/> <!--Needed in order for the onchange computing the taxes to keep the repartition lines-->
+ <field name="tag_ids" invisible="1"/> <!--Needed in order for the onchange computing the taxes to keep the tags-->
<field name="base" invisible="1"/>
</tree>
</field>
@@ -655,6 +656,7 @@
<field name="amount_total"/>
<field name="currency_id" invisible="1" force_save="1"/>
<field name="tax_repartition_line_id" invisible="1"/> <!--Needed in order for the onchange computing the taxes to keep the repartition lines-->
+ <field name="tag_ids" invisible="1"/> <!--Needed in order for the onchange computing the taxes to keep the tags-->
<field name="base" invisible="1"/>
</tree>
</field>
diff --git a/addons/account/views/account_move_views.xml b/addons/account/views/account_move_views.xml
index 0eadb5cdcf8..aa2e1a35123 100644
--- a/addons/account/views/account_move_views.xml
+++ b/addons/account/views/account_move_views.xml
@@ -357,6 +357,7 @@
<field name="recompute_tax_line" invisible="1" readonly="1"/>
<field name="tax_line_grouping_key" invisible="1" readonly="1"/>
<field name="tax_repartition_line_id" invisible="1"/> <!--Not to be removed, we need it so that the call to new() takes it into account when autocompleting the lines of a tax-->
+ <field name="tax_base_amount" invisible="1"/> <!--Not to be removed, we need it so that the call to new() takes it into account when autocompleting the lines of a tax-->
<field name="tag_ids" invisible="1"/>
</tree>
</field>
diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml
index 772a29affff..a3e175bdf20 100644
--- a/addons/account/views/account_payment_view.xml
+++ b/addons/account/views/account_payment_view.xml
@@ -99,7 +99,8 @@
<filter string="Payment Method" name="paymentmethod" domain="[]" context="{'group_by': 'payment_method_id'}"/>
<filter string="State" name="state" domain="[]" context="{'group_by': 'state'}"/>
<filter string="Currency" name="currency" domain="[]" context="{'group_by': 'currency_id'}" groups="base.group_multi_currency"/>
- <filter string="Company" name="company" domain="[]" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
+ <!-- company_id is not stored (cdca5d4751a979f6b694f02d88160843f81a77f5). But we can't break structure-->
+ <filter string="Company" name="company" domain="[]" context="{'group_by': 'company_id'}" groups="base.group_multi_company" invisible="1" />
<separator/>
<filter string="Late Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
diff --git a/addons/account/wizard/wizard_tax_adjustments.py b/addons/account/wizard/wizard_tax_adjustments.py
index a394c240ffe..4dd21030802 100644
--- a/addons/account/wizard/wizard_tax_adjustments.py
+++ b/addons/account/wizard/wizard_tax_adjustments.py
@@ -37,6 +37,7 @@ class TaxAdjustments(models.TransientModel):
'credit': not is_debit and abs(self.amount) or 0,
'account_id': is_debit and self.debit_account_id.id or self.credit_account_id.id,
'tax_repartition_line_id': repartition_line.id,
+ 'tax_base_amount': tax_vals['base'],
'tag_ids': [(6, False, repartition_line.tag_ids.ids)],
}))
diff --git a/addons/account_analytic_default/models/account_analytic_default.py b/addons/account_analytic_default/models/account_analytic_default.py
index cfbcbbcafc8..208be7e3ab8 100644
--- a/addons/account_analytic_default/models/account_analytic_default.py
+++ b/addons/account_analytic_default/models/account_analytic_default.py
@@ -70,13 +70,27 @@ class AccountInvoiceLine(models.Model):
@api.onchange('product_id', 'account_id')
def _onchange_product_id_account_id(self):
- rec = self.env['account.analytic.default'].account_get(product_id=self.product_id.id, partner_id=self.invoice_id.commercial_partner_id.id, account_id=self.account_id.id, user_id=self.env.uid, date=self.invoice_id.date_due, company_id=self.invoice_id.company_id.id)
+ rec = self.env['account.analytic.default'].account_get(
+ product_id=self.product_id.id,
+ partner_id=self.invoice_id.commercial_partner_id.id,
+ account_id=self.account_id.id,
+ user_id=self.invoice_id.user_id.id or self.env.uid,
+ date=self.invoice_id.date_due,
+ company_id=self.invoice_id.company_id.id,
+ )
self.account_analytic_id = rec.analytic_id.id
self.analytic_tag_ids = rec.analytic_tag_ids.ids
def _set_additional_fields(self):
if not self.account_analytic_id or not self.analytic_tag_ids:
- rec = self.env['account.analytic.default'].account_get(product_id=self.product_id.id, partner_id=self.invoice_id.commercial_partner_id.id, account_id=self.account_id.id, user_id=self.env.uid, date=self.invoice_id.date_due, company_id=self.invoice_id.company_id.id)
+ rec = self.env['account.analytic.default'].account_get(
+ product_id=self.product_id.id,
+ partner_id=self.invoice_id.commercial_partner_id.id,
+ account_id=self.account_id.id,
+ user_id=self.invoice_id.user_id.id or self.env.uid,
+ date=self.invoice_id.date_due,
+ company_id=self.invoice_id.company_id.id,
+ )
if rec:
if not self.account_analytic_id:
self.account_analytic_id = rec.analytic_id.id
diff --git a/addons/account_payment/controllers/payment.py b/addons/account_payment/controllers/payment.py
index 8ac01bd398d..c3922258c6f 100644
--- a/addons/account_payment/controllers/payment.py
+++ b/addons/account_payment/controllers/payment.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from werkzeug.urls import url_encode
+
from odoo import http, _
from odoo.addons.portal.controllers.portal import _build_url_w_params
from odoo.addons.payment.controllers.portal import PaymentProcessing
@@ -17,8 +19,6 @@ class PaymentPortal(http.Controller):
:return html: form containing all values related to the acquirer to
redirect customers to the acquirer website """
- success_url = kwargs.get('success_url', '/my')
-
invoice_sudo = request.env['account.invoice'].sudo().browse(invoice_id)
if not invoice_sudo:
return False
@@ -30,6 +30,10 @@ class PaymentPortal(http.Controller):
if request.env.user._is_public():
save_token = False # we avoid to create a token for the public user
+
+ success_url = kwargs.get(
+ 'success_url', "%s?%s" % (invoice_sudo.access_url, url_encode({'access_token': access_token}) if access_token else '')
+ )
vals = {
'acquirer_id': acquirer_id,
'return_url': success_url,
@@ -54,7 +58,6 @@ class PaymentPortal(http.Controller):
def invoice_pay_token(self, invoice_id, pm_id=None, **kwargs):
""" Use a token to perform a s2s transaction """
error_url = kwargs.get('error_url', '/my')
- success_url = kwargs.get('success_url', '/my')
access_token = kwargs.get('access_token')
params = {}
if access_token:
@@ -65,6 +68,9 @@ class PaymentPortal(http.Controller):
params['error'] = 'pay_invoice_invalid_doc'
return request.redirect(_build_url_w_params(error_url, params))
+ success_url = kwargs.get(
+ 'success_url', "%s?%s" % (invoice_sudo.access_url, url_encode({'access_token': access_token}) if access_token else '')
+ )
try:
token = request.env['payment.token'].sudo().browse(int(pm_id))
except (ValueError, TypeError):
diff --git a/addons/account_voucher/models/account_voucher.py b/addons/account_voucher/models/account_voucher.py
index 7a760249a5d..4fa88c8de43 100644
--- a/addons/account_voucher/models/account_voucher.py
+++ b/addons/account_voucher/models/account_voucher.py
@@ -37,7 +37,7 @@ class AccountVoucher(models.Model):
('purchase', 'Purchase')
], string='Type', readonly=True, states={'draft': [('readonly', False)]}, oldname="type")
name = fields.Char('Payment Memo',
- readonly=True, states={'draft': [('readonly', False)]}, default='')
+ readonly=True, states={'draft': [('readonly', False)]}, default='',copy=False)
date = fields.Date("Bill Date", readonly=True,
index=True, states={'draft': [('readonly', False)]},
copy=False, default=fields.Date.context_today)
diff --git a/addons/bus/controllers/main.py b/addons/bus/controllers/main.py
index 6ff4d7ac8e0..54bb005d5fc 100644
--- a/addons/bus/controllers/main.py
+++ b/addons/bus/controllers/main.py
@@ -38,3 +38,7 @@ class BusController(Controller):
if request.registry.in_test_mode():
raise exceptions.UserError(_("bus.Bus not available in test mode"))
return self._poll(request.db, channels, last, options)
+
+ @route('/longpolling/im_status', type="json", auth="user")
+ def im_status(self, partner_ids):
+ return request.env['res.partner'].search_read([['id', 'in', partner_ids]], ['id', 'im_status'])
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index 16a95ffdbd6..d32b9178818 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -245,7 +245,7 @@ class AlarmManager(models.AbstractModel):
_name = 'calendar.alarm_manager'
_description = 'Event Alarm Manager'
- def get_next_potential_limit_alarm(self, alarm_type, seconds=None, partner_id=None):
+ def _get_next_potential_limit_alarm(self, alarm_type, seconds=None, partner_id=None):
result = {}
delta_request = """
SELECT
@@ -325,6 +325,12 @@ class AlarmManager(models.AbstractModel):
'rrule': rule
}
+ # determine accessible events
+ events = self.env['calendar.event'].browse(result)
+ result = {
+ key: result[key]
+ for key in set(events._filter_access_rules('read').ids)
+ }
return result
def do_check_alarm_for_one_date(self, one_date, event, event_maxdelta, in_the_next_X_seconds, alarm_type, after=False, missing=False):
@@ -376,7 +382,7 @@ class AlarmManager(models.AbstractModel):
cron_interval = cron.interval_number * interval_to_second[cron.interval_type]
- all_meetings = self.get_next_potential_limit_alarm('email', seconds=cron_interval)
+ all_meetings = self._get_next_potential_limit_alarm('email', seconds=cron_interval)
for meeting in self.env['calendar.event'].browse(all_meetings):
max_delta = all_meetings[meeting.id]['max_duration']
@@ -406,7 +412,7 @@ class AlarmManager(models.AbstractModel):
if not partner:
return []
- all_meetings = self.get_next_potential_limit_alarm('notification', partner_id=partner.id)
+ all_meetings = self._get_next_potential_limit_alarm('notification', partner_id=partner.id)
time_limit = 3600 * 24 # return alarms of the next 24 hours
for event_id in all_meetings:
max_delta = all_meetings[event_id]['max_duration']
diff --git a/addons/calendar_sms/models/calendar.py b/addons/calendar_sms/models/calendar.py
index 1f87174e150..65cd1a8cad5 100644
--- a/addons/calendar_sms/models/calendar.py
+++ b/addons/calendar_sms/models/calendar.py
@@ -52,7 +52,7 @@ class AlarmManager(models.AbstractModel):
}
cron_interval = cron.interval_number * interval_to_second[cron.interval_type]
- events_data = self.get_next_potential_limit_alarm('sms', seconds=cron_interval)
+ events_data = self._get_next_potential_limit_alarm('sms', seconds=cron_interval)
for event in self.env['calendar.event'].browse(events_data):
max_delta = events_data[event.id]['max_duration']
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index 29685f8e2b1..7ffab80372a 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -54,7 +54,10 @@ class Lead(models.Model):
_primary_email = 'email_from'
def _default_probability(self):
- stage_id = self._default_stage_id()
+ if 'default_stage_id' in self._context:
+ stage_id = self._context.get('default_stage_id')
+ else:
+ stage_id = self._default_stage_id()
if stage_id:
return self.env['crm.stage'].browse(stage_id).probability
return 10
@@ -305,9 +308,10 @@ class Lead(models.Model):
if vals.get('user_id') and 'date_open' not in vals:
vals['date_open'] = fields.Datetime.now()
- if context.get('default_partner_id') and not vals.get('email_from'):
- partner = self.env['res.partner'].browse(context['default_partner_id'])
- vals['email_from'] = partner.email
+ partner_id = vals.get('partner_id') or context.get('default_partner_id')
+ onchange_values = self._onchange_partner_id_values(partner_id)
+ onchange_values.update(vals) # we don't want to overwrite any existing key
+ vals = onchange_values
return super(Lead, self.with_context(context)).create(vals)
@@ -316,7 +320,8 @@ class Lead(models.Model):
# stage change: update date_last_stage_update
if 'stage_id' in vals:
vals['date_last_stage_update'] = fields.Datetime.now()
- if vals.get('user_id') and 'date_open' not in vals:
+ # Only write the 'date_open' if no salesperson was assigned.
+ if vals.get('user_id') and 'date_open' not in vals and not self.mapped('user_id'):
vals['date_open'] = fields.Datetime.now()
# stage change with new stage: update probability and date_closed
if vals.get('stage_id') and 'probability' not in vals:
diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py
index 5a759e8a68d..d705ab57881 100644
--- a/addons/event_sale/models/sale_order.py
+++ b/addons/event_sale/models/sale_order.py
@@ -81,7 +81,7 @@ class SaleOrderLine(models.Model):
@api.onchange('event_ticket_id')
def _onchange_event_ticket_id(self):
company = self.event_id.company_id or self.env.company
- currency = company.currency_id
+ currency = company.currency_id or self.env.company.currency_id
self.price_unit = currency._convert(
self.event_ticket_id.price, self.order_id.currency_id, self.order_id.company_id, self.order_id.date_order or fields.Date.today())
diff --git a/addons/gamification/models/res_users.py b/addons/gamification/models/res_users.py
index 6a188f2003a..daa9d4bc1e1 100644
--- a/addons/gamification/models/res_users.py
+++ b/addons/gamification/models/res_users.py
@@ -3,6 +3,9 @@
from odoo import api, fields, models
+import logging
+
+_logger = logging.getLogger(__name__)
class Users(models.Model):
_inherit = 'res.users'
@@ -18,26 +21,9 @@ class Users(models.Model):
@api.depends('karma')
def _compute_karma_position(self):
- where_query = self._where_calc([])
- self._apply_ir_rules(where_query, 'read')
- from_clause, where_clause, where_clause_params = where_query.get_sql()
-
- query = """
- SELECT sub.id, sub.karma_position
- FROM (
- SELECT id, row_number() OVER (ORDER BY res_users.karma DESC) AS karma_position
- FROM {from_clause}
- WHERE {where_clause}
- ) sub
- WHERE sub.id IN %s
- """.format(from_clause=from_clause, where_clause=where_clause)
-
- self.env.cr.execute(query, where_clause_params + [tuple(self.ids)])
-
- position_map = {item['id']: item['karma_position'] for item in self.env.cr.dictfetchall()}
-
+ _logger.warning("The field karma_position from res.users is deprecated. Don't use it anymore.")
for user in self:
- user.karma_position = position_map.get(user.id, 0)
+ user.karma_position = 0
@api.multi
@api.depends('badge_ids')
diff --git a/addons/google_account/models/google_service.py b/addons/google_account/models/google_service.py
index 5292a490515..fb9213f96b2 100644
--- a/addons/google_account/models/google_service.py
+++ b/addons/google_account/models/google_service.py
@@ -189,12 +189,12 @@ class GoogleService(models.TransientModel):
status = error.response.status_code
response = ""
else:
- req = json.loads(error.request.body)
+ req = json.loads(error.request.body or 'null')
res = error.response.json()
_logger.exception("Error while requesting Google Services\nRequest:\n%s\nResponse:\n%s", pformat(req), pformat(res))
if error.response.status_code in (400, 401, 410):
- raise UserError(_("Error while requesting Google Services: %s" % res['error']['message']))
- raise self.env['res.config.settings'].get_config_warning(_("Something went wrong with your request to google"))
+ raise UserError(_("Error while requesting Google Services: %s") % res['error']['message'])
+ raise self.env['res.config.settings'].get_config_warning(_("Something went wrong with your request to google: %s") % res['error']['message'])
return (status, response, ask_time)
# TODO : remove me, it is only used in google calendar. Make google_calendar use the constants
diff --git a/addons/hr/models/res_users.py b/addons/hr/models/res_users.py
index 65561a52480..547ef54d7df 100644
--- a/addons/hr/models/res_users.py
+++ b/addons/hr/models/res_users.py
@@ -15,7 +15,7 @@ class User(models.Model):
job_title = fields.Char(related='employee_id.job_title', readonly=False)
work_phone = fields.Char(related='employee_id.work_phone', readonly=False)
mobile_phone = fields.Char(related='employee_id.mobile_phone', readonly=False)
- phone = fields.Char(related='employee_id.phone', readonly=False, related_sudo=False)
+ employee_phone = fields.Char(related='employee_id.phone', readonly=False, related_sudo=False)
work_email = fields.Char(related='employee_id.work_email', readonly=False, related_sudo=False)
category_ids = fields.Many2many(related='employee_id.category_ids', string="Employee Tags", readonly=False, related_sudo=False)
department_id = fields.Many2one(related='employee_id.department_id', readonly=False, related_sudo=False)
@@ -99,7 +99,7 @@ class User(models.Model):
'parent_id',
'passport_id',
'permit_no',
- 'phone',
+ 'employee_phone',
'pin',
'place_of_birth',
'spouse_birthdate',
diff --git a/addons/hr/views/res_users.xml b/addons/hr/views/res_users.xml
index d544fa568d0..6838d961adc 100644
--- a/addons/hr/views/res_users.xml
+++ b/addons/hr/views/res_users.xml
@@ -98,7 +98,7 @@
It should be different from the work address.
</div>
<field name="km_home_work"/>
- <field name="phone"/>
+ <field name="employee_phone"/>
<field name="employee_bank_account_id"/>
</group>
<group string="Citizenship">
diff --git a/addons/hr_attendance_presence/views/hr_employee.xml b/addons/hr_attendance_presence/views/hr_employee.xml
index c49871522a2..a645e594b20 100644
--- a/addons/hr_attendance_presence/views/hr_employee.xml
+++ b/addons/hr_attendance_presence/views/hr_employee.xml
@@ -4,7 +4,7 @@
<record id="hr_employee_view_kanban" model="ir.ui.view">
<field name="name">hr.employee</field>
<field name="model">hr.employee</field>
- <field name="inherit_id" ref="hr_presence.hr_employee_view_kanban"/>
+ <field name="inherit_id" ref="hr_presence.hr_employee_view_kanban_inherit"/>
<field name="priority">20</field>
<field name="arch" type="xml">
<xpath expr="//span[@name='presence_absent']" position="attributes">
diff --git a/addons/hr_contract/models/hr_contract.py b/addons/hr_contract/models/hr_contract.py
index 366624ab8f4..f8662c1f55f 100644
--- a/addons/hr_contract/models/hr_contract.py
+++ b/addons/hr_contract/models/hr_contract.py
@@ -60,12 +60,12 @@ class Contract(models.Model):
@api.constrains('employee_id', 'state', 'date_start', 'date_end')
def _check_current_contract(self):
- """ Two contracts in state [incoming | pending | open | close] cannot overlap """
- for contract in self.filtered(lambda c: c.state not in ['draft', 'cancel']):
+ """ Two contracts in state [incoming | pending | open] cannot overlap """
+ for contract in self.filtered(lambda c: c.state not in ['draft', 'cancel', 'close']):
domain = [
('id', '!=', contract.id),
('employee_id', '=', contract.employee_id.id),
- ('state', 'in', ['incoming', 'pending', 'open', 'close']),
+ ('state', 'in', ['incoming', 'pending', 'open']),
]
if not contract.date_end:
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index 5ece94762a4..5c865fc053e 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -151,14 +151,14 @@ class HrExpense(models.Model):
@api.model
def get_empty_list_help(self, help_message):
- if help_message and "oe_view_nocontent_smiling_face" not in help_message:
+ if help_message and "o_view_nocontent_smiling_face" not in help_message:
use_mailgateway = self.env['ir.config_parameter'].sudo().get_param('hr_expense.use_mailgateway')
alias_record = use_mailgateway and self.env.ref('hr_expense.mail_alias_expense') or False
if alias_record and alias_record.alias_domain and alias_record.alias_name:
link = "<a id='o_mail_test' href='mailto:%(email)s?subject=Lunch%%20with%%20customer%%3A%%20%%2412.32'>%(email)s</a>" % {
'email': '%s@%s' % (alias_record.alias_name, alias_record.alias_domain)
}
- return '<p class="oe_view_nocontent_smiling_face">%s</p><p class="oe_view_nocontent_alias">%s</p>' % (
+ return '<p class="o_view_nocontent_smiling_face">%s</p><p class="oe_view_nocontent_alias">%s</p>' % (
_('Add a new expense,'),
_('or send receipts by email to %s.') % (link),)
return super(HrExpense, self).get_empty_list_help(help_message)
@@ -212,6 +212,25 @@ class HrExpense(models.Model):
# ----------------------------------------
@api.multi
+ def _prepare_move_values(self):
+ """
+ This function prepares move values related to an expense
+ """
+ self.ensure_one()
+ journal = self.sheet_id.bank_journal_id if self.payment_mode == 'company_account' else self.sheet_id.journal_id
+ account_date = self.sheet_id.accounting_date or self.date
+ move_values = {
+ 'journal_id': journal.id,
+ 'company_id': self.env.company.id,
+ 'date': account_date,
+ 'ref': self.sheet_id.name,
+ # force the name to the default value, to avoid an eventual 'default_name' in the context
+ # to set it to '' which cause no number to be given to the account.move when posted.
+ 'name': '/',
+ }
+ return move_values
+
+ @api.multi
def _get_account_move_by_sheet(self):
""" Return a mapping between the expense sheet of current expense and its account move
:returns dict where key is a sheet id, and value is an account move record
@@ -219,18 +238,8 @@ class HrExpense(models.Model):
move_grouped_by_sheet = {}
for expense in self:
# create the move that will contain the accounting entries
- account_date = expense.sheet_id.accounting_date or expense.date
if expense.sheet_id.id not in move_grouped_by_sheet:
- journal = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id
- move = self.env['account.move'].create({
- 'journal_id': journal.id,
- 'company_id': self.env.company.id,
- 'date': account_date,
- 'ref': expense.sheet_id.name,
- # force the name to the default value, to avoid an eventual 'default_name' in the context
- # to set it to '' which cause no number to be given to the account.move when posted.
- 'name': '/',
- })
+ move = self.env['account.move'].create(expense._prepare_move_values())
move_grouped_by_sheet[expense.sheet_id.id] = move
else:
move = move_grouped_by_sheet[expense.sheet_id.id]
@@ -325,6 +334,7 @@ class HrExpense(models.Model):
'amount_currency': amount_currency if different_currency else 0.0,
'account_id': tax['account_id'] or move_line_src['account_id'],
'tax_repartition_line_id': tax['tax_repartition_line_id'],
+ 'tax_base_amount': tax['base'],
'expense_id': expense.id,
'partner_id': partner_id,
'currency_id': expense.currency_id.id if different_currency else False,
diff --git a/addons/hr_holidays/models/hr_employee.py b/addons/hr_holidays/models/hr_employee.py
index 459cf0a642d..3ff7b0ebcf6 100644
--- a/addons/hr_holidays/models/hr_employee.py
+++ b/addons/hr_holidays/models/hr_employee.py
@@ -153,8 +153,8 @@ class HrEmployeeBase(models.AbstractModel):
hr_vals['manager_id'] = values['parent_id']
if values.get('department_id') is not None:
hr_vals['department_id'] = values['department_id']
- holidays = self.env['hr.leave'].search(['|', ('state', 'in', ['draft', 'confirm']), ('date_from', '>', today_date), ('employee_id', 'in', self.ids)])
+ holidays = self.env['hr.leave'].sudo().search(['|', ('state', 'in', ['draft', 'confirm']), ('date_from', '>', today_date), ('employee_id', 'in', self.ids)])
holidays.write(hr_vals)
- allocations = self.env['hr.leave.allocation'].search([('state', 'in', ['draft', 'confirm']), ('employee_id', 'in', self.ids)])
+ allocations = self.env['hr.leave.allocation'].sudo().search([('state', 'in', ['draft', 'confirm']), ('employee_id', 'in', self.ids)])
allocations.write(hr_vals)
return res
diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py
index add502f82fb..ec3ee75f609 100644
--- a/addons/hr_holidays/models/hr_leave.py
+++ b/addons/hr_holidays/models/hr_leave.py
@@ -13,6 +13,7 @@ from odoo import api, fields, models, tools, SUPERUSER_ID
from odoo.addons.resource.models.resource import float_to_time, HOURS_PER_DAY
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools import float_compare
+from odoo.tools.float_utils import float_round
from odoo.tools.translate import _
_logger = logging.getLogger(__name__)
@@ -146,6 +147,8 @@ class HolidaysRequest(models.Model):
number_of_hours_display = fields.Float(
'Duration in hours', compute='_compute_number_of_hours_display', copy=False, readonly=True,
help='Number of hours of the time off request according to your working schedule. Used for interface.')
+ duration_display = fields.Char('Requested (Days/Hours)', compute='_compute_duration_display',
+ help="Field allowing to see the leave request duration in days or hours depending on the leave_type_request_unit") # details
# details
meeting_id = fields.Many2one('calendar.event', string='Meeting')
parent_id = fields.Many2one('hr.leave', string='Parent', copy=False)
@@ -355,11 +358,17 @@ class HolidaysRequest(models.Model):
self.mode_company_id = False
self.department_id = False
+ @api.multi
+ def _sync_employee_details(self):
+ for holiday in self:
+ holiday.manager_id = holiday.employee_id.parent_id.id
+ if holiday.employee_id:
+ holiday.department_id = holiday.employee_id.department_id
+
@api.onchange('employee_id')
def _onchange_employee_id(self):
- self.manager_id = self.employee_id.parent_id.id
- if self.employee_id:
- self.department_id = self.employee_id.department_id
+ self._sync_employee_details()
+ self.holiday_status_id = False
@api.onchange('date_from', 'date_to', 'employee_id')
def _onchange_leave_dates(self):
@@ -386,6 +395,16 @@ class HolidaysRequest(models.Model):
holiday.number_of_hours_display = 0
@api.multi
+ @api.depends('number_of_hours_display', 'number_of_days_display')
+ def _compute_duration_display(self):
+ for leave in self:
+ leave.duration_display = '%g %s' % (
+ (float_round(leave.number_of_hours_display, precision_digits=2)
+ if leave.leave_type_request_unit == 'hour'
+ else float_round(leave.number_of_days_display, precision_digits=2)),
+ _('hour(s)') if leave.leave_type_request_unit == 'hour' else _('day(s)'))
+
+ @api.multi
@api.depends('state', 'employee_id', 'department_id')
def _compute_can_reset(self):
for holiday in self:
@@ -413,7 +432,7 @@ class HolidaysRequest(models.Model):
def _check_date(self):
for holiday in self:
domain = [
- ('date_from', '<=', holiday.date_to),
+ ('date_from', '<', holiday.date_to),
('date_to', '>', holiday.date_from),
('employee_id', '=', holiday.employee_id.id),
('id', '!=', holiday.id),
@@ -456,7 +475,10 @@ class HolidaysRequest(models.Model):
res = []
for leave in self:
if self.env.context.get('short_name'):
- res.append((leave.id, _("%s : %.2f day(s)") % (leave.name or leave.holiday_status_id.name, leave.number_of_days)))
+ if leave.leave_type_request_unit == 'hour':
+ res.append((leave.id, _("%s : %.2f hour(s)") % (leave.name or leave.holiday_status_id.name, leave.number_of_hours_display)))
+ else:
+ res.append((leave.id, _("%s : %.2f day(s)") % (leave.name or leave.holiday_status_id.name, leave.number_of_days)))
else:
if leave.holiday_type == 'company':
target = leave.mode_company_id.name
@@ -466,11 +488,18 @@ class HolidaysRequest(models.Model):
target = leave.category_id.name
else:
target = leave.employee_id.name
- res.append(
- (leave.id,
- _("%s on %s: %.2f day(s)") %
- (target, leave.holiday_status_id.name, leave.number_of_days))
- )
+ if leave.leave_type_request_unit == 'hour':
+ res.append(
+ (leave.id,
+ _("%s on %s : %.2f hour(s)") %
+ (target, leave.holiday_status_id.name, leave.number_of_hours_display))
+ )
+ else:
+ res.append(
+ (leave.id,
+ _("%s on %s: %.2f day(s)") %
+ (target, leave.holiday_status_id.name, leave.number_of_days))
+ )
return res
@api.multi
@@ -511,7 +540,7 @@ class HolidaysRequest(models.Model):
if holiday.validation_type == 'hr':
holiday.message_subscribe(partner_ids=(holiday.employee_id.parent_id.user_id.partner_id | holiday.employee_id.leave_manager_id.partner_id).ids)
if employee_id:
- holiday._onchange_employee_id()
+ holiday._sync_employee_details()
if 'number_of_days' not in values and ('date_from' in values or 'date_to' in values):
holiday._onchange_leave_dates()
if leave_type.validation_type == 'no_validation':
@@ -550,7 +579,7 @@ class HolidaysRequest(models.Model):
for holiday in self:
if employee_id:
holiday.add_follower(employee_id)
- holiday._onchange_employee_id()
+ self._sync_employee_details()
if 'number_of_days' not in values and ('date_from' in values or 'date_to' in values):
holiday._onchange_leave_dates()
return result
diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py
index 91aa37b2e2d..c400d47aedf 100644
--- a/addons/hr_holidays/models/hr_leave_allocation.py
+++ b/addons/hr_holidays/models/hr_leave_allocation.py
@@ -218,7 +218,10 @@ class HolidaysAllocation(models.Model):
@api.depends('number_of_days', 'employee_id')
def _compute_number_of_hours_display(self):
for allocation in self:
- allocation.number_of_hours_display = allocation.number_of_days * (allocation.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY)
+ if allocation.parent_id and allocation.parent_id.type_request_unit == "hour":
+ allocation.number_of_hours_display = allocation.number_of_days * HOURS_PER_DAY
+ else:
+ allocation.number_of_hours_display = allocation.number_of_days * (allocation.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY)
@api.multi
@api.depends('number_of_hours_display', 'number_of_days_display')
diff --git a/addons/hr_holidays/tests/test_leave_requests.py b/addons/hr_holidays/tests/test_leave_requests.py
index 737e904b438..eacd2a396e0 100644
--- a/addons/hr_holidays/tests/test_leave_requests.py
+++ b/addons/hr_holidays/tests/test_leave_requests.py
@@ -158,9 +158,9 @@ class TestLeaveRequests(TestHrHolidaysBase):
self.employee_hrmanager.write({'department_id': self.hr_dept.id})
self.assertFalse(self.env['hr.leave'].search([('employee_id', 'in', self.hr_dept.member_ids.ids)]))
leave_form = Form(self.env['hr.leave'].sudo(self.user_hrmanager), view='hr_holidays.hr_leave_view_form_manager')
- leave_form.holiday_status_id = self.holidays_type_1
leave_form.holiday_type = 'department'
leave_form.department_id = self.hr_dept
+ leave_form.holiday_status_id = self.holidays_type_1
leave = leave_form.save()
leave.action_approve()
member_ids = self.hr_dept.member_ids.ids
diff --git a/addons/hr_holidays/views/hr_leave_views.xml b/addons/hr_holidays/views/hr_leave_views.xml
index 1208b7f4ab6..8ff0691879e 100644
--- a/addons/hr_holidays/views/hr_leave_views.xml
+++ b/addons/hr_holidays/views/hr_leave_views.xml
@@ -387,7 +387,7 @@
<field name="name"/>
<field name="date_from"/>
<field name="date_to"/>
- <field name="number_of_days" string="Number of Days" sum="Total time off"/>
+ <field name="duration_display"/>
<field name="state"/>
<field name="payslip_status" widget="toggle_button" options='{"active": "Reported in last payslips", "inactive": "To Report in Payslip"}' groups="hr_holidays.group_hr_holidays_manager"/>
<field name="category_id" invisible="1"/>
diff --git a/addons/hr_maintenance/views/hr_views.xml b/addons/hr_maintenance/views/hr_views.xml
index 070328c9464..826ddcf2d44 100644
--- a/addons/hr_maintenance/views/hr_views.xml
+++ b/addons/hr_maintenance/views/hr_views.xml
@@ -7,7 +7,7 @@
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="%(maintenance.hr_equipment_action)d"
- context="{'search_default_my':1}"
+ context="{'search_default_employee_id': id}"
groups="maintenance.group_equipment_manager"
class="o_stat_button"
icon="fa-cubes"
diff --git a/addons/hr_org_chart/controllers/hr_org_chart.py b/addons/hr_org_chart/controllers/hr_org_chart.py
index d9aeb0ce5fa..fb389f6bb51 100644
--- a/addons/hr_org_chart/controllers/hr_org_chart.py
+++ b/addons/hr_org_chart/controllers/hr_org_chart.py
@@ -56,7 +56,7 @@ class HrOrgChartController(http.Controller):
return {}
# compute employee data for org chart
- ancestors, current = request.env['hr.employee.public'], employee
+ ancestors, current = request.env['hr.employee.public'].sudo(), employee.sudo()
while current.parent_id and len(ancestors) < self._managers_level+1:
ancestors += current.parent_id
current = current.parent_id
diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py
index a92b0d44eb1..d9f40d24816 100644
--- a/addons/hw_escpos/escpos/escpos.py
+++ b/addons/hw_escpos/escpos/escpos.py
@@ -893,14 +893,23 @@ class Escpos:
self._raw(PAPER_FULL_CUT)
- def cashdraw(self, pin):
- """ Send pulse to kick the cash drawer """
- if pin == 2:
- self._raw(CD_KICK_2)
- elif pin == 5:
- self._raw(CD_KICK_5)
- else:
- raise CashDrawerError()
+ def cashdraw(self, pin, tries=5):
+ """ Send pulse to kick the cash drawer
+
+ With some printers the drawer will not open after one pulse, for this reason we will check
+ the drawer status up to 'tries' times and send a new pulse if the drawer is still closed. If the
+ drawer status is open, we will stop sending pulses.
+ """
+ for i in range(tries):
+ if pin == 2:
+ self._raw(CD_KICK_2)
+ elif pin == 5:
+ self._raw(CD_KICK_5)
+ else:
+ raise CashDrawerError()
+
+ if i != tries - 1 and not self.get_printer_status()['printer']['drawer_pin_high']:
+ break
def hw(self, hw):
diff --git a/addons/hw_scale/controllers/main.py b/addons/hw_scale/controllers/main.py
index bb98a8521d0..1ebb6e39af2 100644
--- a/addons/hw_scale/controllers/main.py
+++ b/addons/hw_scale/controllers/main.py
@@ -8,7 +8,7 @@ import time
from collections import namedtuple
from os import listdir
-from threading import Thread, Lock
+from threading import Event, Lock, Thread
from odoo import http
@@ -134,6 +134,7 @@ class Scale(Thread):
self.path_to_scale = ''
self.protocol = None
self.disabled = False
+ self.weight_request_event = Event()
def lockedstart(self):
with self.lock:
@@ -261,8 +262,8 @@ class Scale(Thread):
return None
def get_weight(self):
- self.repeats = 5
self.disabled = False
+ self.weight_request_event.set()
self.lockedstart()
return self.weight
@@ -361,11 +362,13 @@ class Scale(Thread):
self.device = self.get_device()
if not self.device:
# retry later to support "plug and play"
- time.sleep(10)
+ self.weight_request_event.clear()
+ self.weight_request_event.wait(10)
else:
self.disabled = self.protocol.disable
else:
- time.sleep(10)
+ self.weight_request_event.clear()
+ self.weight_request_event.wait(10)
scale_thread = None
diff --git a/addons/iap/models/iap.py b/addons/iap/models/iap.py
index 998eb6850b2..7dd9b6c206b 100644
--- a/addons/iap/models/iap.py
+++ b/addons/iap/models/iap.py
@@ -162,7 +162,7 @@ class IapAccount(models.Model):
@api.model
def get(self, service_name, force_create=True):
- account = self.search([('service_name', '=', service_name), ('company_id', 'in', [self.env.company.id, False])])
+ account = self.search([('service_name', '=', service_name), ('company_id', 'in', [self.env.company.id, False])], limit=1, order='company_id desc, id desc')
if not account and force_create:
account = self.create({'service_name': service_name})
# Since the account did not exist yet, we will encounter a NoCreditError,
diff --git a/addons/iap/security/ir.model.access.csv b/addons/iap/security/ir.model.access.csv
index 7e423f3c57a..80fd4e2212f 100644
--- a/addons/iap/security/ir.model.access.csv
+++ b/addons/iap/security/ir.model.access.csv
@@ -1,3 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_client_iap_account_manager,iap.account.manager,model_iap_account,base.group_system,1,1,1,0
+access_client_iap_account_manager,iap.account.manager,model_iap_account,base.group_system,1,1,1,1
access_client_iap_account_user,iap.account.user,model_iap_account,base.group_user,1,0,1,0
diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py
index 9ef4df884f7..93770819191 100644
--- a/addons/im_livechat/models/im_livechat_channel.py
+++ b/addons/im_livechat/models/im_livechat_channel.py
@@ -162,8 +162,11 @@ class ImLivechatChannel(models.Model):
operator_partner_id = operator.partner_id.id
# partner to add to the mail.channel
channel_partner_to_add = [(4, operator_partner_id)]
- if self.env.user and self.env.user.active: # valid session user (not public)
- channel_partner_to_add.append((4, self.env.user.partner_id.id))
+ visitor_user = False
+ if user_id:
+ visitor_user = self.env['res.users'].browse(user_id)
+ if visitor_user and visitor_user.active: # valid session user (not public)
+ channel_partner_to_add.append((4, visitor_user.partner_id.id))
# create the session, and add the link with the given channel
mail_channel = self.env["mail.channel"].with_context(mail_create_nosubscribe=False).sudo().create({
'channel_partner_ids': channel_partner_to_add,
@@ -172,7 +175,7 @@ class ImLivechatChannel(models.Model):
'anonymous_name': False if user_id else anonymous_name,
'country_id': country_id,
'channel_type': 'livechat',
- 'name': ', '.join([self.env['res.users'].browse(user_id).name if user_id else anonymous_name, operator.livechat_username if operator.livechat_username else operator.name]),
+ 'name': ', '.join([visitor_user.name if visitor_user else anonymous_name, operator.livechat_username if operator.livechat_username else operator.name]),
'public': 'private',
'email_send': False,
})
diff --git a/addons/im_livechat/tests/test_get_mail_channel.py b/addons/im_livechat/tests/test_get_mail_channel.py
index ac3d5807333..2c0106ea37b 100644
--- a/addons/im_livechat/tests/test_get_mail_channel.py
+++ b/addons/im_livechat/tests/test_get_mail_channel.py
@@ -24,6 +24,11 @@ class TestGetMailChannel(TransactionCase):
'login': 'georges'
}])
+ self.visitor_user = self.env['res.users'].create({
+ 'name': 'Rajesh',
+ 'login': 'rajesh',
+ })
+
self.livechat_channel = self.env['im_livechat.channel'].create({
'name': 'The channel',
'user_ids': (6, 0, self.operators.ids)
@@ -46,6 +51,9 @@ class TestGetMailChannel(TransactionCase):
channel_operator_ids = [channel_operator[0] for channel_operator in channel_operators]
self.assertTrue(all(partner_id in channel_operator_ids for partner_id in self.operators.mapped('partner_id').ids))
+ visitor_user_channel = self.livechat_channel._get_mail_channel('Visitor', user_id=self.visitor_user.id)
+ self.assertEqual(visitor_user_channel['correspondent_name'], self.visitor_user.name, "Chat title should be correct")
+
def _get_mail_channels(self):
mail_channels = []
diff --git a/addons/im_support/static/src/js/support_message.js b/addons/im_support/static/src/js/support_message.js
index 7a4df85ca3a..92c85842aad 100644
--- a/addons/im_support/static/src/js/support_message.js
+++ b/addons/im_support/static/src/js/support_message.js
@@ -14,7 +14,10 @@ var SupportMessage = Message.extend({
*/
init: function () {
this._super.apply(this, arguments);
- if (this._serverAuthorID !== this.call('mail_service', 'getOdoobotID')) {
+ if (
+ !this._serverAuthorID ||
+ this._serverAuthorID[0] !== this.call('mail_service', 'getOdoobotID')[0]
+ ) {
if (!this._serverAuthorID[0]) {
// the author is the client
this._serverAuthorID = [session.partner_id, session.name];
diff --git a/addons/l10n_ae/__manifest__.py b/addons/l10n_ae/__manifest__.py
index d9542262df7..8f9f25a7b5f 100644
--- a/addons/l10n_ae/__manifest__.py
+++ b/addons/l10n_ae/__manifest__.py
@@ -14,11 +14,15 @@ United Arab Emirates accounting chart and localization.
""",
'depends': ['base', 'account'],
'data': [
+ 'data/l10n_ae_data.xml',
'data/account_data.xml',
'data/l10n_ae_chart_data.xml',
'data/account.account.template.csv',
'data/l10n_ae_chart_post_data.xml',
+ 'data/account_tax_report_data.xml',
'data/account_tax_template_data.xml',
+ 'data/fiscal_templates_data.xml',
'data/account_chart_template_data.xml',
+ 'views/report_invoice_templates.xml',
],
}
diff --git a/addons/l10n_ae/data/account_data.xml b/addons/l10n_ae/data/account_data.xml
index 48fcd567909..df9792faae5 100644
--- a/addons/l10n_ae/data/account_data.xml
+++ b/addons/l10n_ae/data/account_data.xml
@@ -1,9 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
- <data noupdate="1">
+ <data noupdate="0">
<!-- Account Tax Group -->
- <record id="tax_group_15" model="account.tax.group">
- <field name="name">Tax 15%</field>
+ <record id="ae_tax_group_5" model="account.tax.group">
+ <field name="name">Tax 5%</field>
+ </record>
+ <record id="ae_tax_group_0" model="account.tax.group">
+ <field name="name">Tax 0%</field>
+ </record>
+ <record id="ae_tax_group_exempted" model="account.tax.group">
+ <field name="name">Tax Exempted</field>
</record>
</data>
</odoo>
diff --git a/addons/l10n_ae/data/account_tax_report_data.xml b/addons/l10n_ae/data/account_tax_report_data.xml
new file mode 100644
index 00000000000..1f89d63f8a2
--- /dev/null
+++ b/addons/l10n_ae/data/account_tax_report_data.xml
@@ -0,0 +1,421 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <record id="tax_report_line_base_all_sales" model="account.tax.report.line">
+ <field name="name">VAT on Sales and all other Outputs (Base)</field>
+ <field name="sequence" eval="1"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_base" model="account.tax.report.line">
+ <field name="name">1. Standard Rated supplies (Base)</field>
+ <field name="parent_id" ref="tax_report_line_base_all_sales"/>
+ <field name="sequence" eval="1"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_base_abu_dhabi" model="account.tax.report.line">
+ <field name="name">a. Abu Dhabi</field>
+ <field name="tag_name">a. Abu Dhabi</field>
+ <field name="code">STD_RATE_SUPP_BASE_AB</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_base"/>
+ <field name="sequence" eval="1"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_base_dubai" model="account.tax.report.line">
+ <field name="name">b. Dubai</field>
+ <field name="tag_name">b. Dubai</field>
+ <field name="code">STD_RATE_SUPP_BASE_DB</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_base"/>
+ <field name="sequence" eval="2"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_base_sharjah" model="account.tax.report.line">
+ <field name="name">c. Sharjah</field>
+ <field name="tag_name">c. Sharjah</field>
+ <field name="code">STD_RATE_SUPP_BASE_SJ</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_base"/>
+ <field name="sequence" eval="3"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_base_ajman" model="account.tax.report.line">
+ <field name="name">d. Ajman</field>
+ <field name="tag_name">d. Ajman</field>
+ <field name="code">STD_RATE_SUPP_BASE_AJ</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_base"/>
+ <field name="sequence" eval="4"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_base_umm_al_quwain" model="account.tax.report.line">
+ <field name="name">e. Umm Al Quwain</field>
+ <field name="tag_name">e. Umm Al Quwain</field>
+ <field name="code">STD_RATE_SUPP_BASE_UM</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_base"/>
+ <field name="sequence" eval="5"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_base_ras_al_khaima" model="account.tax.report.line">
+ <field name="name">f. Ras Al-Khaima</field>
+ <field name="tag_name">f. Ras Al-Khaima</field>
+ <field name="code">STD_RATE_SUPP_BASE_RA</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_base"/>
+ <field name="sequence" eval="6"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_base_fujairah" model="account.tax.report.line">
+ <field name="name">g. Fujairah</field>
+ <field name="tag_name">g. Fujairah</field>
+ <field name="code">STD_RATE_SUPP_BASE_FU</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_base"/>
+ <field name="sequence" eval="7"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_base_subtotal" model="account.tax.report.line">
+ <field name="name">Sub Total</field>
+ <field name="formula">STD_RATE_SUPP_BASE_AB + STD_RATE_SUPP_BASE_DB + STD_RATE_SUPP_BASE_SJ + STD_RATE_SUPP_BASE_AJ + STD_RATE_SUPP_BASE_UM + STD_RATE_SUPP_BASE_RA + STD_RATE_SUPP_BASE_FU</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_base"/>
+ <field name="sequence" eval="8"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_tax_refund_tourist_base" model="account.tax.report.line">
+ <field name="name">2. Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme</field>
+ <field name="tag_name">2. Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme</field>
+ <field name="code">TAX_REF_TOUR_SCHEME_BASE</field>
+ <field name="parent_id" ref="tax_report_line_base_all_sales"/>
+ <field name="sequence" eval="2"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_supplies_reverse_charge_base" model="account.tax.report.line">
+ <field name="name">3. Supplies subject to reverse charge provisions</field>
+ <field name="tag_name">3. Supplies subject to reverse charge provisions</field>
+ <field name="code">REVERSE_CHARGE_PRO_BASE</field>
+ <field name="parent_id" ref="tax_report_line_base_all_sales"/>
+ <field name="sequence" eval="3"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_zero_rated_supplies_base" model="account.tax.report.line">
+ <field name="name">4. Zero rated supplies</field>
+ <field name="tag_name">4. Zero rated supplies</field>
+ <field name="code">ZERO_RATE_SUPP_BASE</field>
+ <field name="parent_id" ref="tax_report_line_base_all_sales"/>
+ <field name="sequence" eval="4"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_exempt_supplies_base" model="account.tax.report.line">
+ <field name="name">5. Exempt supplies</field>
+ <field name="tag_name">5. Exempt supplies</field>
+ <field name="code">EXAMPT_SUPP_BASE</field>
+ <field name="parent_id" ref="tax_report_line_base_all_sales"/>
+ <field name="sequence" eval="5"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_supplies_out_of_scope_base" model="account.tax.report.line">
+ <field name="name">6. Out of scope</field>
+ <field name="code">OUT_OF_SCOPE_BASE_0</field>
+ <field name="parent_id" ref="tax_report_line_base_all_sales"/>
+ <field name="sequence" eval="6"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_import_uae_base" model="account.tax.report.line">
+ <field name="name">7. Goods imported into the UAE</field>
+ <field name="tag_name">7. Goods imported into the UAE</field>
+ <field name="code">GOODS_IMPORT_IN_UAE_BASE</field>
+ <field name="parent_id" ref="tax_report_line_base_all_sales"/>
+ <field name="sequence" eval="7"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_adjustment_import_uae_base" model="account.tax.report.line">
+ <field name="name">8. Adjustments to goods imported into the UAE</field>
+ <field name="code">ADJUST_GOODS_IMPORT_IN_UAE_BASE</field>
+ <field name="parent_id" ref="tax_report_line_base_all_sales"/>
+ <field name="sequence" eval="8"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_base_all_sales_total" model="account.tax.report.line">
+ <field name="name">9. Total</field>
+ <field name="formula">ADJUST_GOODS_IMPORT_IN_UAE_BASE + GOODS_IMPORT_IN_UAE_BASE + OUT_OF_SCOPE_BASE_0 + EXAMPT_SUPP_BASE + ZERO_RATE_SUPP_BASE + REVERSE_CHARGE_PRO_BASE + TAX_REF_TOUR_SCHEME_BASE + (STD_RATE_SUPP_BASE_AB + STD_RATE_SUPP_BASE_DB + STD_RATE_SUPP_BASE_SJ + STD_RATE_SUPP_BASE_AJ + STD_RATE_SUPP_BASE_UM + STD_RATE_SUPP_BASE_RA + STD_RATE_SUPP_BASE_FU)</field>
+ <field name="parent_id" ref="tax_report_line_base_all_sales"/>
+ <field name="sequence" eval="9"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_base_all_expense" model="account.tax.report.line">
+ <field name="name">VAT on Expenses and all other Inputs (Base)</field>
+ <field name="sequence" eval="2"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_expense_base" model="account.tax.report.line">
+ <field name="name">10. Standard rated expenses</field>
+ <field name="tag_name">10. Standard rated expenses</field>
+ <field name="code">STD_RATE_EXPENSES_BASE</field>
+ <field name="parent_id" ref="tax_report_line_base_all_expense"/>
+ <field name="sequence" eval="1"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_expense_supplies_reverse_base" model="account.tax.report.line">
+ <field name="name">11. Supplies subject to the reverse charge provisions</field>
+ <field name="tag_name">11. Supplies subject to the reverse charge provisions</field>
+ <field name="code">SUPP_REV_CHARGE_PRO_BASE</field>
+ <field name="parent_id" ref="tax_report_line_base_all_expense"/>
+ <field name="sequence" eval="2"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_expense_out_of_scope" model="account.tax.report.line">
+ <field name="name">12. Out of scope</field>
+ <field name="code">OUT_OF_SCOPE_1_BASE</field>
+ <field name="parent_id" ref="tax_report_line_base_all_expense"/>
+ <field name="sequence" eval="3"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_base_all_expense_total" model="account.tax.report.line">
+ <field name="name">13. Totals</field>
+ <field name="formula">OUT_OF_SCOPE_1_BASE + SUPP_REV_CHARGE_PRO_BASE + STD_RATE_EXPENSES_BASE</field>
+ <field name="parent_id" ref="tax_report_line_base_all_expense"/>
+ <field name="sequence" eval="4"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_vat_all_sales" model="account.tax.report.line">
+ <field name="name">VAT on Sales and all other Outputs (Tax)</field>
+ <field name="sequence" eval="3"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_vat" model="account.tax.report.line">
+ <field name="name">1. Standard Rated supplies (Tax)</field>
+ <field name="sequence" eval="1"/>
+ <field name="parent_id" ref="tax_report_line_vat_all_sales"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_vat_abu_dhabi" model="account.tax.report.line">
+ <field name="name">a. Abu Dhabi</field>
+ <field name="tag_name">a. Abu Dhabi</field>
+ <field name="code">STD_RATE_SUPP_TAX_AB</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_vat"/>
+ <field name="sequence" eval="1"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_vat_dubai" model="account.tax.report.line">
+ <field name="name">b. Dubai</field>
+ <field name="tag_name">b. Dubai</field>
+ <field name="code">STD_RATE_SUPP_TAX_DB</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_vat"/>
+ <field name="sequence" eval="2"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_vat_sharjah" model="account.tax.report.line">
+ <field name="name">c. Sharjah</field>
+ <field name="tag_name">c. Sharjah</field>
+ <field name="code">STD_RATE_SUPP_TAX_SJ</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_vat"/>
+ <field name="sequence" eval="3"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_vat_ajman" model="account.tax.report.line">
+ <field name="name">d. Ajman</field>
+ <field name="tag_name">d. Ajman</field>
+ <field name="code">STD_RATE_SUPP_TAX_AJ</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_vat"/>
+ <field name="sequence" eval="4"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_vat_umm_al_quwain" model="account.tax.report.line">
+ <field name="name">e. Umm Al Quwain</field>
+ <field name="tag_name">e. Umm Al Quwain</field>
+ <field name="code">STD_RATE_SUPP_TAX_UM</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_vat"/>
+ <field name="sequence" eval="5"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_vat_ras_al_khaima" model="account.tax.report.line">
+ <field name="name">f. Ras Al-Khaima</field>
+ <field name="tag_name">f. Ras Al-Khaima</field>
+ <field name="code">STD_RATE_SUPP_TAX_RA</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_vat"/>
+ <field name="sequence" eval="6"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_vat_fujairah" model="account.tax.report.line">
+ <field name="name">g. Fujairah</field>
+ <field name="tag_name">g. Fujairah</field>
+ <field name="code">STD_RATE_SUPP_TAX_FU</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_vat"/>
+ <field name="sequence" eval="7"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_supplies_vat_subtotal" model="account.tax.report.line">
+ <field name="name">Sub Total</field>
+ <field name="formula">STD_RATE_SUPP_TAX_AB + STD_RATE_SUPP_TAX_DB + STD_RATE_SUPP_TAX_SJ + STD_RATE_SUPP_TAX_AJ + STD_RATE_SUPP_TAX_UM + STD_RATE_SUPP_TAX_RA + STD_RATE_SUPP_TAX_FU</field>
+ <field name="parent_id" ref="tax_report_line_standard_rated_supplies_vat"/>
+ <field name="sequence" eval="8"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_tax_refund_tourist_vat" model="account.tax.report.line">
+ <field name="name">2. Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme</field>
+ <field name="tag_name">2. Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme</field>
+ <field name="code">TAX_REF_TOUR_SCHEME_TAX</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_sales"/>
+ <field name="sequence" eval="2"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_supplies_reverse_charge_vat" model="account.tax.report.line">
+ <field name="name">3. Supplies subject to reverse charge provisions</field>
+ <field name="tag_name">3. Supplies subject to reverse charge provisions</field>
+ <field name="code">REVERSE_CHARGE_PRO_TAX</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_sales"/>
+ <field name="sequence" eval="3"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_zero_rated_supplies_vat" model="account.tax.report.line">
+ <field name="name">4. Zero rated supplies</field>
+ <field name="tag_name">4. Zero rated supplies</field>
+ <field name="code">ZERO_RATE_SUPP_TAX</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_sales"/>
+ <field name="sequence" eval="4"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_exempt_supplies_vat" model="account.tax.report.line">
+ <field name="name">5. Exempt supplies</field>
+ <field name="tag_name">5. Exempt supplies</field>
+ <field name="code">EXAMPT_SUPP_TAX</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_sales"/>
+ <field name="sequence" eval="5"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_supplies_out_of_scope_vat" model="account.tax.report.line">
+ <field name="name">6. Out of scope</field>
+ <field name="code">OUT_OF_SCOPE_TAX_0</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_sales"/>
+ <field name="sequence" eval="6"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_import_uae_vat" model="account.tax.report.line">
+ <field name="name">7. Goods imported into the UAE</field>
+ <field name="tag_name">7. Goods imported into the UAE</field>
+ <field name="code">GOODS_IMPORT_IN_UAE_TAX</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_sales"/>
+ <field name="sequence" eval="7"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_adjustment_import_uae_vat" model="account.tax.report.line">
+ <field name="name">8. Adjustments to goods imported into the UAE</field>
+ <field name="code">ADJUST_GOODS_IMPORT_IN_UAE_TAX</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_sales"/>
+ <field name="sequence" eval="8"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_vat_all_sales_total" model="account.tax.report.line">
+ <field name="name">9. Total</field>
+ <field name="formula">(STD_RATE_SUPP_TAX_AB + STD_RATE_SUPP_TAX_DB + STD_RATE_SUPP_TAX_SJ + STD_RATE_SUPP_TAX_AJ + STD_RATE_SUPP_TAX_UM + STD_RATE_SUPP_TAX_RA + STD_RATE_SUPP_TAX_FU) + OUT_OF_SCOPE_TAX_0 + ADJUST_GOODS_IMPORT_IN_UAE_TAX + GOODS_IMPORT_IN_UAE_TAX + EXAMPT_SUPP_TAX + ZERO_RATE_SUPP_TAX + REVERSE_CHARGE_PRO_TAX + TAX_REF_TOUR_SCHEME_TAX</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_sales"/>
+ <field name="sequence" eval="9"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_vat_all_expense" model="account.tax.report.line">
+ <field name="name">VAT on Expenses and all other Inputs (Tax)</field>
+ <field name="sequence" eval="4"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_standard_rated_expense_vat" model="account.tax.report.line">
+ <field name="name">10. Standard rated expenses</field>
+ <field name="tag_name">10. Standard rated expenses</field>
+ <field name="code">STD_RATE_EXPENSES_TAX</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_expense"/>
+ <field name="sequence" eval="1"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_expense_supplies_reverse_vat" model="account.tax.report.line">
+ <field name="name">11. Supplies subject to the reverse charge provisions</field>
+ <field name="tag_name">11. Supplies subject to the reverse charge provisions</field>
+ <field name="code">SUPP_REV_CHARGE_PRO_TAX</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_expense"/>
+ <field name="sequence" eval="2"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_expense_out_of_scope_vat" model="account.tax.report.line">
+ <field name="name">12. Out of scope</field>
+ <field name="code">OUT_OF_SCOPE_1_TAX</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_expense"/>
+ <field name="sequence" eval="3"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_vat_all_expense_total" model="account.tax.report.line">
+ <field name="name">13. Totals</field>
+ <field name="formula">OUT_OF_SCOPE_1_TAX + SUPP_REV_CHARGE_PRO_TAX + STD_RATE_EXPENSES_TAX</field>
+ <field name="parent_id" ref="tax_report_line_vat_all_expense"/>
+ <field name="sequence" eval="4"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_net_vat_due" model="account.tax.report.line">
+ <field name="name">Net VAT Due</field>
+ <field name="sequence" eval="5"/>
+ <field name="formula">((STD_RATE_SUPP_TAX_AB + STD_RATE_SUPP_TAX_DB + STD_RATE_SUPP_TAX_SJ + STD_RATE_SUPP_TAX_AJ + STD_RATE_SUPP_TAX_UM + STD_RATE_SUPP_TAX_RA + STD_RATE_SUPP_TAX_FU) + OUT_OF_SCOPE_TAX_0 + ADJUST_GOODS_IMPORT_IN_UAE_TAX + GOODS_IMPORT_IN_UAE_TAX + EXAMPT_SUPP_TAX + ZERO_RATE_SUPP_TAX + REVERSE_CHARGE_PRO_TAX + TAX_REF_TOUR_SCHEME_TAX) - (OUT_OF_SCOPE_1_TAX + SUPP_REV_CHARGE_PRO_TAX + STD_RATE_EXPENSES_TAX)</field>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_total_value_due_tax_period" model="account.tax.report.line">
+ <field name="name">14. Total value of due tax for the period</field>
+ <field name="formula">(STD_RATE_SUPP_TAX_AB + STD_RATE_SUPP_TAX_DB + STD_RATE_SUPP_TAX_SJ + STD_RATE_SUPP_TAX_AJ + STD_RATE_SUPP_TAX_UM + STD_RATE_SUPP_TAX_RA + STD_RATE_SUPP_TAX_FU) + OUT_OF_SCOPE_TAX_0 + ADJUST_GOODS_IMPORT_IN_UAE_TAX + GOODS_IMPORT_IN_UAE_TAX + EXAMPT_SUPP_TAX + ZERO_RATE_SUPP_TAX + REVERSE_CHARGE_PRO_TAX + TAX_REF_TOUR_SCHEME_TAX</field>
+ <field name="parent_id" ref="tax_report_line_net_vat_due"/>
+ <field name="sequence" eval="1"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_total_value_recoverable_tax_period" model="account.tax.report.line">
+ <field name="name">15. Total value of recoverable tax for the period</field>
+ <field name="formula">OUT_OF_SCOPE_1_TAX + SUPP_REV_CHARGE_PRO_TAX + STD_RATE_EXPENSES_TAX</field>
+ <field name="parent_id" ref="tax_report_line_net_vat_due"/>
+ <field name="sequence" eval="2"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+ <record id="tax_report_line_net_vat_due_period" model="account.tax.report.line">
+ <field name="name">16. Net VAT due (or reclaimed) for the period</field>
+ <field name="formula">((STD_RATE_SUPP_TAX_AB + STD_RATE_SUPP_TAX_DB + STD_RATE_SUPP_TAX_SJ + STD_RATE_SUPP_TAX_AJ + STD_RATE_SUPP_TAX_UM + STD_RATE_SUPP_TAX_RA + STD_RATE_SUPP_TAX_FU) + OUT_OF_SCOPE_TAX_0 + ADJUST_GOODS_IMPORT_IN_UAE_TAX + GOODS_IMPORT_IN_UAE_TAX + EXAMPT_SUPP_TAX + ZERO_RATE_SUPP_TAX + REVERSE_CHARGE_PRO_TAX + TAX_REF_TOUR_SCHEME_TAX) - (OUT_OF_SCOPE_1_TAX + SUPP_REV_CHARGE_PRO_TAX + STD_RATE_EXPENSES_TAX)</field>
+ <field name="parent_id" ref="tax_report_line_net_vat_due"/>
+ <field name="sequence" eval="3"/>
+ <field name="country_id" ref="base.ae"/>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_ae/data/account_tax_template_data.xml b/addons/l10n_ae/data/account_tax_template_data.xml
index 198d573bc10..15ae5ae9869 100644
--- a/addons/l10n_ae/data/account_tax_template_data.xml
+++ b/addons/l10n_ae/data/account_tax_template_data.xml
@@ -1,52 +1,787 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
- <record id="sale_tax_template" model="account.tax.template">
+ <record id="uae_sale_tax_5_dubai" model="account.tax.template">
+ <field name="name">TVA 5% (Dubai)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">TVA 5%</field>
+ <field name="tax_group_id" ref="ae_tax_group_5"/>
<field name="chart_template_id" ref="uae_chart_template_standard"/>
- <field name="name">Tax 10%</field>
- <field name="amount">10</field>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_dubai')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_dubai')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_dubai')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_dubai')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_5_abu_dhabi" model="account.tax.template">
+ <field name="name">TVA 5% (Abu Dhabi)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">TVA 5%</field>
+ <field name="tax_group_id" ref="ae_tax_group_5"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_abu_dhabi')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_abu_dhabi')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_abu_dhabi')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_abu_dhabi')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_5_sharjah" model="account.tax.template">
+ <field name="name">TVA 5% (Sharjah)</field>
<field name="type_tax_use">sale</field>
- <field name="tax_group_id" ref="tax_group_15"/>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">TVA 5%</field>
+ <field name="tax_group_id" ref="ae_tax_group_5"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_sharjah')],
}),
-
(0,0, {
'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_sharjah')],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_sharjah')],
}),
-
(0,0, {
'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_sharjah')],
}),
]"/>
</record>
- <record id="purchase_tax_template" model="account.tax.template">
+ <record id="uae_sale_tax_5_ajman" model="account.tax.template">
+ <field name="name">TVA 5% (Ajman)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">TVA 5%</field>
+ <field name="tax_group_id" ref="ae_tax_group_5"/>
<field name="chart_template_id" ref="uae_chart_template_standard"/>
- <field name="name">Purchase Tax 10%</field>
- <field name="amount">10</field>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_ajman')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_ajman')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_ajman')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_ajman')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_5_umm_al_quwain" model="account.tax.template">
+ <field name="name">TVA 5% (Umm Al Quwain)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">TVA 5%</field>
+ <field name="tax_group_id" ref="ae_tax_group_5"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_umm_al_quwain')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_umm_al_quwain')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_umm_al_quwain')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_umm_al_quwain')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_5_ras_al_khaima" model="account.tax.template">
+ <field name="name">TVA 5% (Ras Al-Khaima)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">TVA 5%</field>
+ <field name="tax_group_id" ref="ae_tax_group_5"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_ras_al_khaima')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_ras_al_khaima')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_ras_al_khaima')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_ras_al_khaima')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_5_fujairah" model="account.tax.template">
+ <field name="name">TVA 5% (Fujairah)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">TVA 5%</field>
+ <field name="tax_group_id" ref="ae_tax_group_5"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_fujairah')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_fujairah')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_base_fujairah')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_fujairah')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_exempted" model="account.tax.template">
+ <field name="name">Exempted Tax</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">0</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Exempted</field>
+ <field name="tax_group_id" ref="ae_tax_group_exempted"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_exempt_supplies_base')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_exempt_supplies_base')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_0" model="account.tax.template">
+ <field name="name">TVA 0%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">0</field>
+ <field name="amount_type">percent</field>
+ <field name="description">TVA 0%</field>
+ <field name="tax_group_id" ref="ae_tax_group_0"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_zero_rated_supplies_base')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_zero_rated_supplies_base')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ }),
+ ]"/>
+ </record>
+ <record id="uae_export_tax" model="account.tax.template">
+ <field name="name">Export Tax 0%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">0</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Export Tax</field>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <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',
+ }),
+ ]"/>
+ <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',
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_reverse_charge_dubai" model="account.tax.template">
+ <field name="name">Reverse Charge Provision (Dubai)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Supplies subject to reverse charge provisions</field>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_dubai')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_dubai')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_dubai')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_dubai')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_reverse_charge_abu_dhabi" model="account.tax.template">
+ <field name="name">Reverse Charge Provision (Abi Dhabi)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Supplies subject to reverse charge provisions</field>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_abu_dhabi')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_abu_dhabi')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_abu_dhabi')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_abu_dhabi')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_reverse_charge_sharjah" model="account.tax.template">
+ <field name="name">Reverse Charge Provision (Sharjah)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Supplies subject to reverse charge provisions</field>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_sharjah')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_sharjah')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_sharjah')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_sharjah')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_reverse_charge_ajman" model="account.tax.template">
+ <field name="name">Reverse Charge Provision (Ajman)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Supplies subject to reverse charge provisions</field>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_ajman')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_ajman')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_ajman')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_ajman')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_reverse_charge_umm_al_quwain" model="account.tax.template">
+ <field name="name">Reverse Charge Provision (Umm Al Quwain)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Supplies subject to reverse charge provisions</field>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_umm_al_quwain')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_umm_al_quwain')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_umm_al_quwain')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_umm_al_quwain')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_reverse_charge_ras_al_khaima" model="account.tax.template">
+ <field name="name">Reverse Charge Provision (Ras Al-Khaima)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Supplies subject to reverse charge provisions</field>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_ras_al_khaima')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_ras_al_khaima')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_ras_al_khaima')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_ras_al_khaima')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_reverse_charge_fujairah" model="account.tax.template">
+ <field name="name">Reverse Charge Provision (Fujairah)</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Supplies subject to reverse charge provisions</field>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_fujairah')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_fujairah')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_base'),ref('tax_report_line_standard_rated_supplies_base_fujairah')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_supplies_reverse_charge_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3785'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_supplies_vat_fujairah')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_sale_tax_tourist_refund" model="account.tax.template">
+ <field name="name">Tourist Refund scheme 5%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount">5.0</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme</field>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_tax_refund_tourist_base')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_tax_refund_tourist_vat')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_tax_refund_tourist_base')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_tax_refund_tourist_vat')],
+ }),
+ ]"/>
+ </record>
+ <!-- purchase taxes -->
+ <record id="uae_purchase_tax_5" model="account.tax.template">
+ <field name="name">TVA 5%</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">TVA 5%</field>
+ <field name="tax_group_id" ref="ae_tax_group_5"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_expense_base')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_expense_vat')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_expense_base')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_expense_vat')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_purchase_tax_exempted" model="account.tax.template">
+ <field name="name">Exempted Tax</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="amount">0</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Exempted Tax</field>
+ <field name="tax_group_id" ref="ae_tax_group_exempted"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <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('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_exempt_supplies_vat')],
+ }),
+ ]"/>
+ <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('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_exempt_supplies_vat')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_purchase_tax_0" model="account.tax.template">
+ <field name="name">TVA 0%</field>
<field name="type_tax_use">purchase</field>
- <field name="tax_group_id" ref="tax_group_15"/>
+ <field name="amount">0</field>
+ <field name="amount_type">percent</field>
+ <field name="description">TVA 0%</field>
+ <field name="tax_group_id" ref="ae_tax_group_0"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
<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('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_zero_rated_supplies_vat')],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
@@ -54,11 +789,89 @@
'factor_percent': 100,
'repartition_type': 'base',
}),
-
(0,0, {
'factor_percent': 100,
'repartition_type': 'tax',
'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_zero_rated_supplies_vat')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_import_tax" model="account.tax.template">
+ <field name="name">Import Tax 5%</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Import Tax</field>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_import_uae_base')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'plus_report_line_ids': [ref('tax_report_line_import_uae_vat')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_import_uae_base')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'minus_report_line_ids': [ref('tax_report_line_import_uae_vat')],
+ }),
+ ]"/>
+ </record>
+ <record id="uae_purchase_tax_reverse_charge" model="account.tax.template">
+ <field name="name">Reverse Charge Provision</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="description">Supplies subject to reverse charge provisions</field>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('tax_report_line_expense_supplies_reverse_base'),ref('tax_report_line_standard_rated_expense_base')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_expense_supplies_reverse_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'minus_report_line_ids': [ref('tax_report_line_standard_rated_expense_vat')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('tax_report_line_expense_supplies_reverse_base'),ref('tax_report_line_standard_rated_expense_base')],
+ }),
+ (0,0, {
+ 'factor_percent': -100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_expense_supplies_reverse_vat')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('uae_account_3726'),
+ 'plus_report_line_ids': [ref('tax_report_line_standard_rated_expense_vat')],
}),
]"/>
</record>
diff --git a/addons/l10n_ae/data/fiscal_templates_data.xml b/addons/l10n_ae/data/fiscal_templates_data.xml
new file mode 100644
index 00000000000..14a68c1863e
--- /dev/null
+++ b/addons/l10n_ae/data/fiscal_templates_data.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record id="account_fiscal_position_dubai" model="account.fiscal.position.template">
+ <field name="name">Dubai</field>
+ <field name="auto_apply" eval="True"/>
+ <field name="sequence">16</field>
+ <field name="country_id" ref="base.ae"/>
+ <field name="state_ids" eval="[(6,0,[ref('base.state_ae_du')])]"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ </record>
+
+ <record id="account_fiscal_position_abu_dhabi" model="account.fiscal.position.template">
+ <field name="name">Abu Dhabi</field>
+ <field name="auto_apply" eval="True"/>
+ <field name="sequence">16</field>
+ <field name="country_id" ref="base.ae"/>
+ <field name="state_ids" eval="[(6,0,[ref('base.state_ae_az')])]"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ </record>
+ <record id="account_fiscal_position_abu_dhabi_01" model="account.fiscal.position.tax.template">
+ <field name="tax_src_id" ref="uae_sale_tax_5_dubai"/>
+ <field name="tax_dest_id" ref="uae_sale_tax_5_abu_dhabi"/>
+ <field name="position_id" ref="account_fiscal_position_abu_dhabi"/>
+ </record>
+
+ <record id="account_fiscal_position_sharjah" model="account.fiscal.position.template">
+ <field name="name">Sharjah</field>
+ <field name="auto_apply" eval="True"/>
+ <field name="sequence">16</field>
+ <field name="country_id" ref="base.ae"/>
+ <field name="state_ids" eval="[(6,0,[ref('base.state_ae_sh')])]"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ </record>
+ <record id="account_fiscal_position_sharjah_01" model="account.fiscal.position.tax.template">
+ <field name="tax_src_id" ref="uae_sale_tax_5_dubai"/>
+ <field name="tax_dest_id" ref="uae_sale_tax_5_sharjah"/>
+ <field name="position_id" ref="account_fiscal_position_sharjah"/>
+ </record>
+
+ <record id="account_fiscal_position_ajman" model="account.fiscal.position.template">
+ <field name="name">Ajman</field>
+ <field name="auto_apply" eval="True"/>
+ <field name="sequence">16</field>
+ <field name="country_id" ref="base.ae"/>
+ <field name="state_ids" eval="[(6,0,[ref('base.state_ae_aj')])]"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ </record>
+ <record id="account_fiscal_position_ajman_01" model="account.fiscal.position.tax.template">
+ <field name="tax_src_id" ref="uae_sale_tax_5_dubai"/>
+ <field name="tax_dest_id" ref="uae_sale_tax_5_ajman"/>
+ <field name="position_id" ref="account_fiscal_position_ajman"/>
+ </record>
+
+ <record id="account_fiscal_position_umm_al_quwain" model="account.fiscal.position.template">
+ <field name="name">Umm Al Quwain</field>
+ <field name="auto_apply" eval="True"/>
+ <field name="sequence">16</field>
+ <field name="country_id" ref="base.ae"/>
+ <field name="state_ids" eval="[(6,0,[ref('base.state_ae_uq')])]"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ </record>
+ <record id="account_fiscal_position_umm_al_quwain_01" model="account.fiscal.position.tax.template">
+ <field name="tax_src_id" ref="uae_sale_tax_5_dubai"/>
+ <field name="tax_dest_id" ref="uae_sale_tax_5_umm_al_quwain"/>
+ <field name="position_id" ref="account_fiscal_position_umm_al_quwain"/>
+ </record>
+
+ <record id="account_fiscal_position_ras_al_khaima" model="account.fiscal.position.template">
+ <field name="name">Ras Al-Khaima</field>
+ <field name="auto_apply" eval="True"/>
+ <field name="sequence">16</field>
+ <field name="country_id" ref="base.ae"/>
+ <field name="state_ids" eval="[(6,0,[ref('base.state_ae_rk')])]"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ </record>
+ <record id="account_fiscal_position_ras_al_khaima_01" model="account.fiscal.position.tax.template">
+ <field name="tax_src_id" ref="uae_sale_tax_5_dubai"/>
+ <field name="tax_dest_id" ref="uae_sale_tax_5_ras_al_khaima"/>
+ <field name="position_id" ref="account_fiscal_position_ras_al_khaima"/>
+ </record>
+
+ <record id="account_fiscal_position_fujairah" model="account.fiscal.position.template">
+ <field name="name">Fujairah</field>
+ <field name="auto_apply" eval="True"/>
+ <field name="sequence">16</field>
+ <field name="country_id" ref="base.ae"/>
+ <field name="state_ids" eval="[(6,0,[ref('base.state_ae_fu')])]"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ </record>
+ <record id="account_fiscal_position_fujairah_01" model="account.fiscal.position.tax.template">
+ <field name="tax_src_id" ref="uae_sale_tax_5_dubai"/>
+ <field name="tax_dest_id" ref="uae_sale_tax_5_fujairah"/>
+ <field name="position_id" ref="account_fiscal_position_fujairah"/>
+ </record>
+
+ <record id="account_fiscal_position_non_uae_countries" model="account.fiscal.position.template">
+ <field name="name">Non-UAE</field>
+ <field name="sequence">20</field>
+ <field name="auto_apply" eval="True"/>
+ <field name="chart_template_id" ref="uae_chart_template_standard"/>
+ </record>
+ <record id="acccount_fiscal_position_tax_non_uae_01" model="account.fiscal.position.tax.template">
+ <field name="tax_src_id" ref="uae_sale_tax_5_dubai"/>
+ <field name="tax_dest_id" ref="uae_sale_tax_0"/>
+ <field name="position_id" ref="account_fiscal_position_non_uae_countries"/>
+ </record>
+ <record id="acccount_fiscal_position_tax_non_uae_02" model="account.fiscal.position.tax.template">
+ <field name="tax_src_id" ref="uae_purchase_tax_5"/>
+ <field name="tax_dest_id" ref="uae_purchase_tax_0"/>
+ <field name="position_id" ref="account_fiscal_position_non_uae_countries"/>
+ </record>
+</odoo>
diff --git a/addons/l10n_ae/data/l10n_ae_chart_data.xml b/addons/l10n_ae/data/l10n_ae_chart_data.xml
index 0c8202a34f2..eb648164386 100644
--- a/addons/l10n_ae/data/l10n_ae_chart_data.xml
+++ b/addons/l10n_ae/data/l10n_ae_chart_data.xml
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
+ <menuitem id="account_reports_ae_statements_menu" name="UAE" parent="account.menu_finance_reports" sequence="0" groups="account.group_account_user"/>
+
<record id="uae_chart_template_standard" model="account.chart.template">
<field name="name">U.A.E Chart of Accounts - Standard</field>
<field name="code_digits">6</field>
@@ -7,6 +9,6 @@
<field name="cash_account_code_prefix">105</field>
<field name="transfer_account_code_prefix">100</field>
<field name="currency_id" ref="base.AED" />
- <field name="complete_tax_set" eval="False"/>
+ <field name="complete_tax_set" eval="True"/>
</record>
</odoo>
diff --git a/addons/l10n_ae/data/l10n_ae_data.xml b/addons/l10n_ae/data/l10n_ae_data.xml
new file mode 100644
index 00000000000..94692698ecd
--- /dev/null
+++ b/addons/l10n_ae/data/l10n_ae_data.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <!-- set VAT label to show on invoice report -->
+ <record id="base.ae" model="res.country">
+ <field name="vat_label">TRN</field>
+ </record>
+ <record id="base.AED" model="res.currency">
+ <field name="symbol">AED</field>
+ </record>
+ <record id="gcc_countries_group" model="res.country.group">
+ <field name="name">GCC VAT implementing States</field>
+ <field name="country_ids" eval="[(6,0,[ref('base.ae'),ref('base.sa'),ref('base.bh')])]"/>
+ </record>
+</odoo>
diff --git a/addons/l10n_ae/views/report_invoice_templates.xml b/addons/l10n_ae/views/report_invoice_templates.xml
new file mode 100644
index 00000000000..681fe038284
--- /dev/null
+++ b/addons/l10n_ae/views/report_invoice_templates.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <template id="report_invoice_document" inherit_id="account.report_invoice_document">
+ <xpath expr="//p[@name='payment_term']" position="after">
+ <p t-if="o.company_id.country_id.code == 'AE' and o.partner_id.country_id.code != 'AE' and o.env.ref('l10n_ae.gcc_countries_group') in o.partner_id.country_id.country_group_ids">
+ Supply between <b>United Arad Emirates</b> and <b><span t-field="o.partner_id.country_id.name"/></b>
+ </p>
+ </xpath>
+ <xpath expr="//h2/span" position="before">
+ <span t-if="o.company_id.country_id.code == 'AE' and o.type in ['out_invoice', 'out_refund']">Tax</span>
+ </xpath>
+
+ <xpath expr="//div[hasclass('clearfix')]" position="after">
+ <div t-if="o.company_id.country_id.code == 'AE' and o.currency_id != o.company_id.currency_id" id="aed_amounts" class="row clearfix ml-auto my-3 text-nowrap table">
+ <t t-set="aed_rate" t-value="o.env['res.currency']._get_conversion_rate(o.currency_id, o.company_id.currency_id, o.company_id, o.date_invoice or datetime.date.today())"/>
+ <div name="exchange_rate" class="col-auto">
+ <strong>Exchange Rate</strong>
+ <p class="m-0" t-esc="aed_rate" t-options='{"widget": "float", "precision": 5}'/>
+ </div>
+ <div name="aed_subtotal" class="col-auto">
+ <strong>Subtotal (AED)</strong>
+ <p class="m-0" t-esc="o.amount_untaxed_signed" t-options='{"widget": "monetary", "display_currency": o.company_currency_id}'/>
+ </div>
+ <div name="aed_vat_amount" class="col-auto">
+ <strong>Tax Amount (AED)</strong>
+ <p class="m-0" t-esc="o.currency_id._convert(o.amount_tax, o.company_id.currency_id, o.company_id, o.date_invoice or datetime.date.today())" t-options='{"widget": "monetary", "display_currency": o.company_currency_id}'/>
+ </div>
+ <div name="aed_total" class="col-auto">
+ <strong>Total (AED)</strong>
+ <p class="m-0" t-esc="o.amount_total_company_signed" t-options='{"widget": "monetary", "display_currency": o.company_currency_id}'/>
+ </div>
+ </div>
+ </xpath>
+ </template>
+</odoo>
diff --git a/addons/l10n_be/data/account_tax_template_data.xml b/addons/l10n_be/data/account_tax_template_data.xml
index 7d3101e66ac..98e17c58ae2 100644
--- a/addons/l10n_be/data/account_tax_template_data.xml
+++ b/addons/l10n_be/data/account_tax_template_data.xml
@@ -3325,7 +3325,7 @@
(0,0, {
'factor_percent': 100,
'repartition_type': 'tax',
- 'plus_report_line_ids': [ref('tax_report_line_62')],
+ 'minus_report_line_ids': [ref('tax_report_line_62')],
}),
]"/>
</record>
diff --git a/addons/l10n_be_edi/security/security.xml b/addons/l10n_be_edi/security/security.xml
new file mode 100644
index 00000000000..3fc21715a4c
--- /dev/null
+++ b/addons/l10n_be_edi/security/security.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <!-- Note for later versions, the group Employee Manager should become contract manager -->
+ <record id="hr.group_hr_manager" model="res.groups">
+ <field name="implied_ids" eval="[(4, ref('fleet.fleet_group_manager'))]"/>
+ </record>
+</odoo>
diff --git a/addons/l10n_ca/data/account_tax_data.xml b/addons/l10n_ca/data/account_tax_data.xml
index d2eae78804d..347afdf8525 100644
--- a/addons/l10n_ca/data/account_tax_data.xml
+++ b/addons/l10n_ca/data/account_tax_data.xml
@@ -84,7 +84,6 @@
<field name="amount">100</field>
<field name="amount_type">group</field>
<field name="children_tax_ids" eval="[(6,0,[ref('pst_bc_sale_en'), ref('gstpst_sale_bc_gst_en')])]"/>
- <field name="tag_ids" eval="[(6,0,[ref('tag_gstpst_bc_sale')])]"/>
<field name="tax_group_id" ref="tax_group_fix"/>
</record>
diff --git a/addons/l10n_ch/views/res_bank_view.xml b/addons/l10n_ch/views/res_bank_view.xml
index df3919186ab..90329ee4813 100644
--- a/addons/l10n_ch/views/res_bank_view.xml
+++ b/addons/l10n_ch/views/res_bank_view.xml
@@ -29,7 +29,7 @@
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form"/>
<field name="arch" type="xml">
- <xpath expr="//field[@name='bank_acc_number']" position="after">
+ <xpath expr="//field[@name='bank_account_id']" position="after">
<field name="l10n_ch_postal" attrs="{'invisible': [('type', 'not in', ['bank', 'cash'])]}"/>
</xpath>
</field>
diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py
index 6d8b0bc62f0..34ca956a2c4 100644
--- a/addons/l10n_fr_fec/wizard/account_fr_fec.py
+++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py
@@ -305,7 +305,7 @@ class AccountFrFec(models.TransientModel):
END
AS PieceRef,
TO_CHAR(am.date, 'YYYYMMDD') AS PieceDate,
- CASE WHEN aml.name IS NULL THEN '/' ELSE replace(replace(aml.name, '|', '/'), '\t', '') END AS EcritureLib,
+ CASE WHEN aml.name IS NULL OR aml.name = '' THEN '/' ELSE replace(replace(aml.name, '|', '/'), '\t', '') END AS EcritureLib,
replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '000000000000000D99') END, '.', ',') AS Debit,
replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '000000000000000D99') END, '.', ',') AS Credit,
CASE WHEN rec.name IS NULL THEN '' ELSE rec.name END AS EcritureLet,
@@ -388,7 +388,7 @@ class AccountFrFec(models.TransientModel):
rows_length = len(rows)
for i, row in enumerate(rows):
if not i == rows_length - 1:
- row.append(lineterminator)
+ row[-1] += lineterminator
writer.writerow(row)
fecvalue = fecfile.getvalue()
diff --git a/addons/l10n_hn/data/account_chart_template_data.xml b/addons/l10n_hn/data/account_chart_template_data.xml
index 4a7131526ce..bfeafbd48dd 100644
--- a/addons/l10n_hn/data/account_chart_template_data.xml
+++ b/addons/l10n_hn/data/account_chart_template_data.xml
@@ -10,8 +10,6 @@
<field name="description">ISV por Cobrar</field>
<field name="amount" eval="15"/>
<field name="amount_type">percent</field>
- <field name="account_id" ref="cta110301"/>
- <field name="refund_account_id" ref="cta110301"/>
<field name="type_tax_use">purchase</field>
<field name="price_include" eval="True"/>
<field name="tax_group_id" ref="tax_group_iva_15"/>
diff --git a/addons/l10n_in/models/account_invoice.py b/addons/l10n_in/models/account_invoice.py
index 6ef37d2c584..5b59933d0e9 100644
--- a/addons/l10n_in/models/account_invoice.py
+++ b/addons/l10n_in/models/account_invoice.py
@@ -70,8 +70,8 @@ class AccountInvoice(models.Model):
vals['quantity'] = invoice_tax_line.l10n_in_quantity
return res
- def _prepare_tax_line_vals(self, line, tax, tax_ids):
- vals = super(AccountInvoice, self)._prepare_tax_line_vals(line, tax, tax_ids)
+ def _prepare_tax_line_vals(self, line, tax):
+ vals = super(AccountInvoice, self)._prepare_tax_line_vals(line, tax)
vals['l10n_in_product_id'] = line.product_id.id
vals['l10n_in_uom_id'] = line.uom_id.id
vals['l10n_in_quantity'] = line.quantity
diff --git a/addons/l10n_it/data/account.tax.group.csv b/addons/l10n_it/data/account.tax.group.csv
index ec8fffb16bc..c8035a8f072 100644
--- a/addons/l10n_it/data/account.tax.group.csv
+++ b/addons/l10n_it/data/account.tax.group.csv
@@ -1,6 +1,7 @@
id,name
tax_group_iva_2,IVA 2%
tax_group_iva_4,IVA 4%
+tax_group_iva_5,IVA 5%
tax_group_iva_10,IVA 10%
tax_group_iva_12,IVA 12%
tax_group_iva_21,IVA 21%
diff --git a/addons/l10n_it/data/account_tax_template.xml b/addons/l10n_it/data/account_tax_template.xml
index 8c64ec29cb3..0adb0478db4 100644
--- a/addons/l10n_it/data/account_tax_template.xml
+++ b/addons/l10n_it/data/account_tax_template.xml
@@ -205,6 +205,74 @@
]"/>
</record>
+ <record id="5v" model="account.tax.template">
+ <field name="description">5v</field>
+ <field name="chart_template_id" ref="l10n_it_chart_template_generic"/>
+ <field name="name">Iva al 5% (debito)</field>
+ <field name="sequence">5</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="price_include">False</field>
+ <field name="tax_group_id" ref="tax_group_iva_5"/>
+ <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('2601'),
+ }),
+ ]"/>
+ <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('2601'),
+ }),
+ ]"/>
+ </record>
+
+ <record id="5a" model="account.tax.template">
+ <field name="description">5a</field>
+ <field name="chart_template_id" ref="l10n_it_chart_template_generic"/>
+ <field name="name">Iva al 5% (credito)</field>
+ <field name="sequence">6</field>
+ <field name="amount">5</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="price_include">False</field>
+ <field name="tax_group_id" ref="tax_group_iva_5"/>
+ <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('1601'),
+ }),
+ ]"/>
+ <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('1601'),
+ }),
+ ]"/>
+ </record>
+
<record id="10v" model="account.tax.template">
<field name="description">10v</field>
<field name="chart_template_id" ref="l10n_it_chart_template_generic"/>
diff --git a/addons/l10n_mn/__init__.py b/addons/l10n_mn/__init__.py
new file mode 100644
index 00000000000..67dee8c60db
--- /dev/null
+++ b/addons/l10n_mn/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
diff --git a/addons/l10n_mn/__manifest__.py b/addons/l10n_mn/__manifest__.py
new file mode 100644
index 00000000000..ba60ad4da0f
--- /dev/null
+++ b/addons/l10n_mn/__manifest__.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+{
+ "name" : "Mongolia - Accounting",
+ "version" : "1.0",
+ 'category': 'Localization',
+ "description": """
+This is the module to manage the accounting chart for Mongolia.
+===============================================================
+
+ * the Mongolia Official Chart of Accounts,
+ * the Tax Code Chart for Mongolia
+ * the main taxes used in Mongolia
+
+Financial requirement contributor: Baskhuu Lodoikhuu. BumanIT LLC
+""",
+ "depends": ['account'],
+ 'data': [
+ 'data/account_chart_template_data.xml',
+ 'data/account.account.tag.csv',
+ 'data/account.account.template.csv',
+ 'data/account.tax.group.csv',
+ 'data/account_tax_template_data.xml',
+ 'data/account_fiscal_position_template_data.xml',
+ 'data/account.chart.template.csv',
+ 'data/account_chart_template_configuration_data.xml',
+ ],
+}
diff --git a/addons/l10n_mn/data/account.account.tag.csv b/addons/l10n_mn/data/account.account.tag.csv
new file mode 100644
index 00000000000..b8667ffc12d
--- /dev/null
+++ b/addons/l10n_mn/data/account.account.tag.csv
@@ -0,0 +1,94 @@
+id,name,applicability
+account.account_tag_operating,CF: Үндсэн: Бусад,accounts
+account.account_tag_financing,CF: Хөрөнгө оруулалт: Бусад Урт хугацаат хөрөнгө,accounts
+account.account_tag_investing,CF: Санхүү: Ногдол ашиг,accounts
+account_tag_operating_product,CF: Үндсэн: Бараа үйлчилгээ,accounts
+account_tag_operating_loyalty,CF: Үндсэн: Эрхийн шимтгэл хураамж,accounts
+account_tag_operating_insurance,CF: Үндсэн: Нийгмийн Даатгал,accounts
+account_tag_operating_insurance2,CF: Үндсэн: Бусад Даатгал,accounts
+account_tag_operating_taxrefund,CF: Үндсэн: Татвар,accounts
+account_tag_operating_funding,CF: Үндсэн: Татаас санхүүжилт,accounts
+account_tag_operating_employee,CF: Үндсэн: Ажилчид,accounts
+account_tag_operating_cost,CF: Үндсэн: Ашиглалтын зардал,accounts
+account_tag_operating_transport,CF: Үндсэн: Түлш тээвэр сэлбэг,accounts
+account_tag_operating_interest,CF: Үндсэн: Зээлийн хүү,accounts
+account_tag_investing_fasset,CF: Хөрөнгө оруулалт: Үндсэн хөрөнгө,accounts
+account_tag_investing_iasset,CF: Хөрөнгө оруулалт: Биет бус хөрөнгө,accounts
+account_tag_investing_invest,CF: Хөрөнгө оруулалт: Хөрөнгө оруулалт,accounts
+account_tag_investing_loan,CF: Хөрөнгө оруулалт: Зээл урьдчилгаа,accounts
+account_tag_investing_interest,CF: Хөрөнгө оруулалт: Зээлийн хүү,accounts
+account_tag_investing_dividends,CF: Хөрөнгө оруулалт: Ногдол ашиг,accounts
+account_tag_financing_loan,"CF: Санхүү: Зээл, өрийн үнэт цаас",accounts
+account_tag_financing_rental,CF: Санхүү: Санхүүгийн түрээс,accounts
+account_tag_financing_stock,CF: Санхүү: Хувьцаа өмчийн бусад үнэт цаас,accounts
+account_tag_financing_donation,CF: Санхүү: Хандив,accounts
+account_tag_exchange,CF: Валютын ханшийн зөрүү,accounts
+vat_report_tag2,ТТ3а: НӨАТ-аас чөлөөлөгдөх борлуулалтын орлого (2),taxes
+vat_report_tag5,ТТ3a: Дотоодын зах зээлд борлуулсан барааны борлуулалтын орлого (5),taxes
+vat_report_tag6,ТТ3a: Бусад барааны борлуулалтын орлого (6),taxes
+vat_report_tag7,ТТ3a: Эрх борлуулсны орлого (7),taxes
+vat_report_tag8,ТТ3a: Татан буугдах үед үлдсэн барааны орлого (8),taxes
+vat_report_tag9,ТТ3a: Өрийн төлбөрт шилжүүлсэн барааны орлого (9),taxes
+vat_report_tag11,ТТ3a: Наториатын үйлчилгээний орлого (11),taxes
+vat_report_tag12,ТТ3a: Өрийн төлбөрт тооцсон ажил үйлчилгээний орлого (12),taxes
+vat_report_tag13,"ТТ3a: Цахилгаан, дулаан, ус, шуудан, холбооны үйлчилгээ үзүүлсний орлого (13)",taxes
+vat_report_tag14,"ТТ3a: Бараа түрээслүүлэх, эзэмшүүлэх, ашиглуулах үйлчилгээний орлого (14)",taxes
+vat_report_tag15,"ТТ3a: Байр түрээслүүлэх, эзэмшүүлэх, ашиглуулах үйлчилгээний орлого (15)",taxes
+vat_report_tag16,"ТТ3a: Үл хөдлөх, хөдлөх хөрөнгө түрээслүүлэх, эзэмшүүлэх ашиглуулах үйлчилгээий орлого (16)",taxes
+vat_report_tag17,"ТТ3a: Шинэ бүтээл, загвар, зохиогчийн эрх, барааны тэмдэг худалдсаны орлого (17)",taxes
+vat_report_tag18,"ТТ3a: Хонжворт сугалаа, төлбөрт таавар, бооцоот тоглоомын орлого (18)",taxes
+vat_report_tag19,ТТ3a: Зуучлалын үйлчилгээний орлого (19),taxes
+vat_report_tag20,"ТТ3a: Бусдаас авсан хүү, торгууль, алдангийн орлого (20)",taxes
+vat_report_tag21,ТТ3a: Хөрөнгийн үнэлгээний үйлчилгээний орлого (21),taxes
+vat_report_tag22,"ТТ3a: Төсвийн санхүүжилт, татаас, урамшууллын орлого (22)",taxes
+vat_report_tag23,"ТТ3a: Өмгөөлөл, хууль зүйн үйлчилгээний орлого (23)",taxes
+vat_report_tag24,"ТТ3a: Үсчин, гоо сайхан, засвар үйлчилгээ, угаалга, хими цэвэрлэгээний үйлчилгээний орлого (24)",taxes
+vat_report_tag25,ТТ3a: Хуулийн 13-т зааснаас бусад үйлчилгээний орлого (25),taxes
+vat_report_tag28,ТТ3a: Экспортонд гаргасан барааны борлуулалтын орлого (28),taxes
+vat_report_tag29,ТТ3a: Экспортолсон үйлчилгээний борлуулалтын орлого (29),taxes
+vat_report_tag33,"ТТ3а: Худалдан авсан нийт бараа, ажил үйлчилгээ (33)",taxes
+vat_report_tag35,"ТТ3a: НӨАТ-тэй авсан Импортын бараа, ажил үйлчилгээний худалдан авалт (35)",taxes
+vat_report_tag36,"ТТ3a: Дотоодын зах зээлээс НӨАТ-тэй авсан бараа, ажил, үйлчилгээ (36)",taxes
+vat_report_tag37,"ТТ3a: Суутган төлөгчөөр бүртгүүлэх үед НӨАТ-тэй авсан бараа, үйлчилгээ (37)",taxes
+vat_report_tag38,"ТТ3a: Мал аж ахуй, тариалан эрхлэгчээс НӨАТ-тэй авсан бараа, үйлчилгээ (38)",taxes
+vat_report_tag41,"ТТ3а: Суудлын автомашин, түүний эд анги сэлбэгт төлсөн НӨАТ (41)",taxes
+vat_report_tag42,"ТТ3а: Ажлын хэрэгцээнд зориулж авсан бараа, үйлчилгээнд төлсөн НӨАТ (42)",taxes
+vat_report_tag43,"ТТ3а: Үндсэн хөрөнгө бэлтгэхэд зориулж импортоор авсан бараа, үйлчилгээнд төлсөн НӨАТ (43)",taxes
+vat_report_tag44,"ТТ3а: Чөлөөлөгдөх үйлдвэрлэл, үйлчилгээнд зориулж авсан бараа, ажил, үйлчилгээнд төлсөн НӨАТ (44)",taxes
+vat_report_tag45,"ТТ3а: Хайгуулын ажил болон ашиглалтын өмнөх үйл ажиллагаанд зориулж импортоор авсан бараа, ажил, үйлчилгээнд төлсөн НӨАТ (45)",taxes
+vat_report_tag46,"ТТ3а: Импортоор оруулсан бусад хамааралгүй бараа, ажил, үйлчилгээнд төлсөн НӨАТ (46)",taxes
+vat_report_tag48,ТТ3а: Cанхүүгийн түрээсээр дотоодын зах зээлд борлуулсны орлого (48),taxes
+vat_report_tag49,"ТТ3а: Факторинг, форфайтинг хэлцлийн үйлчилгээний орлого (49)",taxes
+vat_report_tag51,ТТ3а: Санхүүгийн түрээсийн зүйлийг худалдан авахад төлсөн түрээсийн төлбөр (51),taxes
+vat_report_tag52,"ТТ3а: Факторинг, форфайтинг хэлцлийг авахад төлсөн төлбөр (52)",taxes
+vat_report_tag58,"ТТ3а: Борлуулалтын орлогын буцаалт, хөнгөлөлт (58)",accounts
+vat_report_tag59,"ТТ3а: Худалдан авалтын буцаалт, хөнгөлөлт (59)",accounts
+tax_report_tag2,ТТ02: Татвараас чөлөөлөгдөх орлого (2),accounts
+tax_report_tag5,ТТ02: Бусад орлогын дүн (5),accounts
+tax_report_tag7,ТТ02: Үндсэн ажил үйлчилгээний орлого (7),accounts
+tax_report_tag8,ТТ02: Туслах ажил үйлчилгээний орлого (8),accounts
+tax_report_tag9,"ТТ02: Хувьцаа, үнэт цаас борлуулсны орлого (9)",accounts
+tax_report_tag10,"ТТ02: Үнэ төлбөргүйгээр бусдаас авсан бараа, үйлчилгээ (10)",accounts
+tax_report_tag11,ТТ02: Биет бус хөрөнгө борлуулсны орлого (11),accounts
+tax_report_tag12,ТТ02: Техникийн зөвлөх үйлчилгээний орлого (12),accounts
+tax_report_tag13,"ТТ02: Гэрээний хүү, торгууль, хохирлын нөхөн төлбөрийн орлого (13)",accounts
+tax_report_tag14,ТТ02: Гадаад валютын ханшийн зөрүүгийн бодит орлого (14),accounts
+tax_report_tag15,ТТ02: Хөдлөх болон үл хөдлөх эд хөрөнгийн түрээсийн орлого (15),accounts
+tax_report_tag16,ТТ02: Хөдлөх эд хөрөнгө борлуулсны орлого (16),accounts
+tax_report_tag17,ТТ02: Албан татвар ногдох бусад орлого (17),accounts
+tax_report_tag22,ТТ02: Татвар ногдох орлогоос хасагдахгүй зардал (22),accounts
+tax_report_tag23,ТТ02: Татвар ногдох орлогыг бууруулах (23),accounts
+tax_report_tag25,ТТ02: Сайн дурын даатгалын хураамжийн хэтрэлт (25),accounts
+tax_report_tag27,ТТ02: Өмнөх жилийн татварын алдагдлаас тайлант хугацаанд шилжүүлсэн дүн (27),accounts
+tax_report_tag30,ТТ02: Татварын хөнгөлөлт (30),accounts
+tax_report_tag32,"ТТ02: Эротик хэвлэл, бичлэг, тоглолт борлуулалтын орлого (32)",accounts
+tax_report_tag34,"ТТ02: Таавар, бооцоот тоглоом, хонжворт сугалааны орлого (34)",accounts
+tax_report_tag35,"ТТ02: Таавар, бооцоот тоглоом, хонжворт сугалааны орлого олоход зарцуулсан зардал (35)",accounts
+tax_report_tag36,ТТ02: Хонжворт олгосон мөнгө болон барааны үнэ (36),accounts
+tax_report_tag39,ТТ02: Хүүгийн орлого (39),accounts
+tax_report_tag41,"ТТ02: Гадаад улсад олсон ногдол ашиг, хүүгийн орлого (41)",accounts
+tax_report_tag43,ТТ02: Гадаад улсад олсон орлого (43),accounts
+tax_report_tag46,ТТ02: Ногдол ашгийн орлого (46),accounts
+tax_report_tag48,ТТ02: Эрхийн шимтгэлийн орлого (48),accounts
+tax_report_tag50,ТТ02: Эрх борлуулсны орлого (50),accounts
+tax_report_tag52,ТТ02: Үл хөдлөх хөрөнгө борлуулсны орлого (52),accounts
diff --git a/addons/l10n_mn/data/account.account.template.csv b/addons/l10n_mn/data/account.account.template.csv
new file mode 100644
index 00000000000..f64558c3e6b
--- /dev/null
+++ b/addons/l10n_mn/data/account.account.template.csv
@@ -0,0 +1,285 @@
+id,code,name,user_type_id/id,tag_ids/id,reconcile,chart_template_id:id
+account_template_1001_0101,10010101,Бэлэн мөнгө,account.data_account_type_liquidity,,0,mn_chart_1
+account_template_1003_0101,10030101,Жижиг мөнгөн сан,account.data_account_type_liquidity,,0,mn_chart_1
+account_template_1101_0101,11010101,Банкинд байгаа мөнгө,account.data_account_type_liquidity,,0,mn_chart_1
+account_template_1103_0101,11030101,Хугацаагүй хадгаламж,account.data_account_type_liquidity,,0,mn_chart_1
+account_template_1104_0101,11040101,Банкны төлбөрийн темринал,account.data_account_type_liquidity,,0,mn_chart_1
+account_template_1109_0101,11090101,Замд яваа мөнгө,account.data_account_type_current_assets,,1,mn_chart_1
+account_template_1110_0101,11100101,Бусад мөнгө түүнтэй адилтгах хөрөнгө,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1201_0101,12010101,Худалдааны авлага ААН,account.data_account_type_receivable,account_tag_operating_product,1,mn_chart_1
+account_template_1201_0201,12010201,Худалдааны авлага Хувь хүн,account.data_account_type_receivable,account_tag_operating_product,1,mn_chart_1
+account_template_1201_0301,12010301,Худалдааны авлага Холбоотой тал,account.data_account_type_receivable,account_tag_operating_product,1,mn_chart_1
+account_template_1202_0101,12020101,Авлагын бичиг,account.data_account_type_current_assets,account_tag_operating_product,0,mn_chart_1
+account_template_1203_0101,12030101,Найдваргүй авлагын хасагдуулга,account.data_account_type_current_assets,account_tag_operating_product,0,mn_chart_1
+account_template_1204_0101,12040101,Урьдчилж төлсөн ААНОАТатвар,account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1204_0201,12040201,Урьдчилж төлсөн ХХОАТатвар,account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1204_0301,12040301,Урьдчилж төлсөн НӨАТатвар,account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1204_0401,12040401,Урьдчилж төлсөн Гаалийн албан татвар,account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1204_0501,12040501,Урьдчилж төлсөн Үл хөдлөх хөрөнгийн албан татвар,account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1204_0601,12040601,Урьдчилж төлсөн Газрын төлбөр,account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1204_0701,12040701,Урьдчилж төлсөн АТӨЯХАТатвар,account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1204_0801,12040801,Урьдчилж төлсөн Агаарын бохирдлын татвар,account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1204_0901,12040901,Урьдчилж төлсөн Онцгой албан татвар,account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1204_1001,12041001,Урьдчилж төлсөн Байгалийн нөөц ашиглалтын төлбөр,account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1204_9901,12049901,Урьдчилж төлсөн Бусад татвар,account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1205_0101,12050101,"Урьдчилж төлсөн ЭМ, НДШ",account.data_account_type_current_assets,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_1205_0201,12050201,Урьдчилж төлсөн Сайн дурын даатгалын хураамж,account.data_account_type_current_assets,account_tag_operating_insurance2,0,mn_chart_1
+account_template_1206_0101,12060101,"Цалингийн урьдчилгаа, богино хугацаат зээл",account.data_account_type_receivable,account_tag_operating_employee,1,mn_chart_1
+account_template_1206_0201,12060201,"Төлбөр, дутагдал",account.data_account_type_receivable,account_tag_operating_employee,1,mn_chart_1
+account_template_1206_0301,12060301,Худалдааны бус авлага ААН,account.data_account_type_receivable,account.account_tag_operating,1,mn_chart_1
+account_template_1206_0401,12060401,Худалдааны бус авлага Хувь хүн,account.data_account_type_receivable,account.account_tag_operating,1,mn_chart_1
+account_template_1206_0501,12060501,Худалдааны бус авлага Холбоотой тал,account.data_account_type_receivable,account.account_tag_operating,1,mn_chart_1
+account_template_1206_0601,12060601,Богино хугацаат зээлийн авлага ААН,account.data_account_type_receivable,account.account_tag_operating,1,mn_chart_1
+account_template_1206_0701,12060701,Богино хугацаат зээлийн авлага Хувь хүн,account.data_account_type_receivable,account.account_tag_operating,1,mn_chart_1
+account_template_1206_0801,12060801,Богино хугацаат зээлийн авлага Холбоотой тал,account.data_account_type_receivable,account.account_tag_operating,1,mn_chart_1
+account_template_1206_0901,12060901,Ногдол ашгийн авлага,account.data_account_type_receivable,account_tag_investing_dividends,1,mn_chart_1
+account_template_1206_1001,12061001,Барьцаалсан авлага,account.data_account_type_receivable,account.account_tag_operating,1,mn_chart_1
+account_template_1206_1101,12061101,Хүүний авлага,account.data_account_type_receivable,account_tag_operating_interest,1,mn_chart_1
+account_template_1206_1201,12061201,Үндсэн хөрөнгө худалдааны авлага,account.data_account_type_receivable,account_tag_investing_fasset,1,mn_chart_1
+account_template_1206_1301,12061301,Биет бус хөрөнгө худалдааны авлага,account.data_account_type_receivable,account_tag_investing_iasset,1,mn_chart_1
+account_template_1206_1401,12061401,Хөрөнгө оруулалт худалдааны авлага,account.data_account_type_receivable,account_tag_investing_invest,1,mn_chart_1
+account_template_1206_9901,12069901,Бусад худалдааны бус авлага,account.data_account_type_receivable,account.account_tag_operating,1,mn_chart_1
+account_template_1301_0101,13010101,Түргэн борлогдох үнэт цаас,account.data_account_type_current_assets,account.account_tag_financing,0,mn_chart_1
+account_template_1301_0201,13010201,Хугацаат хадгаламж,account.data_account_type_current_assets,account.account_tag_financing,0,mn_chart_1
+account_template_1302_0101,13020101,Бусад дэлгэрэнгүй орлого болох бодит үнэ цэнээр илэрхийлэх санхүүгийн хөрөнгө,account.data_account_type_current_assets,account.account_tag_operating,0,mn_chart_1
+account_template_1303_0101,13030101,Хорогдуулсан өртөгөөр илэрхийлэгдэх санхүүгийн өртөг,account.data_account_type_current_assets,account.account_tag_operating,0,mn_chart_1
+account_template_1401_0101,14010101,Агуулахад байгаа бараа материал,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1401_0401,14010401,Татан авалтын өртөгийн зөрүү,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1401_0701,14010701,Амьтан,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1401_0801,14010801,Мал,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1401_0901,14010901,Ургамал,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1402_0101,14020101,Агуулахад байгаа түүхий эд материал,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1403_0301,14030301,Агуулахад байгаа үндсэн хөрөнгийн хэсэг,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1403_0401,14030401,Агуулахад байгаа хангамжийн материал,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1403_0501,14030501,Ашиглалтанд байгаа хангамжийн материал,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1403_0502,14030502,Ашиглалтанд байгаа хангамжийн материалын элэгдэл,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1404_0101,14040101,Дуусаагүй үйлдвэрлэл - Шууд материал,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1404_0201,14040201,Дуусаагүй үйлдвэрлэл - Шууд хөдөлмөр,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1404_0301,14040301,Дуусаагүй үйлдвэрлэл - НЗ,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1404_0401,14040401,Хагас боловсруулсан бүтээгдэхүүн,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1405_0101,14050101,Агентад байгаа бараа,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1406_0101,14060101,Замд яваа бараа,account.data_account_type_current_assets,,0,mn_chart_1
+account_template_1407_0101,14070101,Ирж буй барааны өртөгийн түр данс,account.data_account_type_current_assets,,1,mn_chart_1
+account_template_1408_0101,14080101,Гарч буй барааны өртөгийн түр данс,account.data_account_type_current_assets,,1,mn_chart_1
+account_template_1501_0101,15010101,Урьдчилж төлсөн түрээс,account.data_account_type_prepayments,account_tag_operating_cost,0,mn_chart_1
+account_template_1501_0201,15010201,Урьдчилж төлсөн хураамж,account.data_account_type_prepayments,account_tag_operating_loyalty,0,mn_chart_1
+account_template_1501_0301,15010301,Урьдчилж төлсөн цалин,account.data_account_type_prepayments,account_tag_operating_employee,0,mn_chart_1
+account_template_1501_0901,15010901,Урьдчилж төлсөн бусад зардал,account.data_account_type_prepayments,account.account_tag_operating,0,mn_chart_1
+account_template_1502_0101,15020101,Бэлтгэн нийлүүлэгчид төлсөн урьдчилгаа,account.data_account_type_prepayments,account_tag_operating_product,0,mn_chart_1
+account_template_1502_0201,15020201,"Дараа тайлангийн урьдчилгаа, томилолт",account.data_account_type_prepayments,account_tag_operating_employee,0,mn_chart_1
+account_template_1502_0301,15020301,Үндсэн хөрөнгө худалдан авахад төлсөн урьдчилгаа,account.data_account_type_prepayments,account_tag_investing_fasset,0,mn_chart_1
+account_template_1502_0401,15020401,Биет бус хөрөнгө худалдан авахад төлсөн урьдчилгаа,account.data_account_type_prepayments,account_tag_investing_iasset,0,mn_chart_1
+account_template_1502_0501,15020501,Хөрөнгө оруулалт худалдан авахад төлсөн урьдчилгаа,account.data_account_type_prepayments,account_tag_investing_invest,0,mn_chart_1
+account_template_1502_0601,15020601,Хангамжийн зүйлс худалдан авахад төлсөн урьдчилгаа,account.data_account_type_prepayments,account_tag_operating_product,0,mn_chart_1
+account_template_1502_0901,15020901,Бусад худалдааны бус урьдчилгаа,account.data_account_type_prepayments,account.account_tag_operating,0,mn_chart_1
+account_template_1601_0101,16010101,Бусад эргэлтийн хөрөнгө,account.data_account_type_current_assets,account.account_tag_operating,0,mn_chart_1
+account_template_1701_0101,17010101,Борлуулах зорилгоор эзэмшиж буй эргэлтийн бус хөрөнгө,account.data_account_type_current_assets,account.account_tag_operating,0,mn_chart_1
+account_template_2001_0101,20010101,Биет хөрөнгө - Газрын сайржуулалт,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2001_0201,20010201,Биет хөрөнгө - Газрын сайжруулалтын элэгдэл,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2002_0101,20020101,"Биет хөрөнгө - Барилга, байгууламж",account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2002_0201,20020201,"Биет хөрөнгө - Барилга, байгууламжын элэгдэл",account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2003_0101,20030101,Биет хөрөнгө - Машин тоног төхөөрөмж,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2003_0201,20030201,Биет хөрөнгө - Машин тоног төхөөрөмжийн элэгдэл,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2004_0101,20040101,Биет хөрөнгө - Тээврийн хэрэгсэл,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2004_0201,20040201,Биет хөрөнгө - Тээврийн хэрэгслийн элэгдэл,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2005_0101,20050101,Биет хөрөнгө - Тавилга эд хогшил,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2005_0201,20050201,Биет хөрөнгө - Тавилга эд хогшлын элэгдэл,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2006_0101,20060101,"Биет хөрөнгө - Компьютер, дагалдах хэрэгсэл",account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2006_0201,20060201,"Биет хөрөнгө - Компьютер, дагалдах хэрэгслийн элэгдэл",account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2008_0101,20080101,Биет хөрөнгө - Дуусаагүй барилга,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2008_0201,20080201,Биет хөрөнгө - Дуусаагүй барилгын элэгдэл,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2009_0101,20090101,Биет хөрөнгө - Бусад хөрөнгө,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2009_0201,20090201,Биет хөрөнгө - Бусад хөрөнгийн элэгдэл,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2101_0101,21010101,Биет бус хөрөнгө - Зохиогчийн эрх,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2101_0201,21010201,Биет бус хөрөнгө - Зохиогчийн эрхийн хорогдол,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2102_0101,21020101,Биет бус хөрөнгө - Програм хангамж,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2102_0201,21020201,Биет бус хөрөнгө - Програм хангамжын хорогдол,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2103_0101,21030101,Биет бус хөрөнгө - Патент,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2103_0201,21030201,Биет бус хөрөнгө - Патентын хорогдол,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2104_0101,21040101,Биет бус хөрөнгө - Барааны тэмдэг,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2104_0201,21040201,Биет бус хөрөнгө - Барааны тэмдэгийн хорогдол,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2105_0101,21050101,Биет бус хөрөнгө - Тусгай зөвшөөрөл,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2105_0201,21050201,Биет бус хөрөнгө - Тусгай зөвшөөрлийн хорогдол,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2109_0101,21090101,Биет бус хөрөнгө - Бусад хөрөнгө,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2109_0201,21090201,Биет бус хөрөнгө - Бусад хөрөнгийн элэгдэл,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2401_0101,24010101,Биологийн хөрөнгө - Мал амьтад,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2401_0201,24010201,Биологийн хөрөнгө - Мал амьтадын хорогдол,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2402_0101,24020101,Биологийн хөрөнгө - Ургамал,account.data_account_type_fixed_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2402_0201,24020201,Биологийн хөрөнгө - Ургамалын хорогдол,account.data_account_type_fixed_assets,,0,mn_chart_1
+account_template_2201_0101,22010101,Ашиг алдагдлаарх бодит үнэ цэнээр илэрхийлэх санхүүгийн хөрөнгө,account.data_account_type_non_current_assets,account_tag_investing_invest,0,mn_chart_1
+account_template_2204_0101,22040101,"Хараат компани, хамтарсан үйлдвэрт оруулсан хөрөнгө оруулалт",account.data_account_type_non_current_assets,account_tag_investing_invest,0,mn_chart_1
+account_template_2205_0101,22050101,Охин компанид оруулсан хөрөнгө оруулалт,account.data_account_type_non_current_assets,account_tag_investing_invest,0,mn_chart_1
+account_template_2301_0101,23010101,Хайгуул ба үнэлгээний хөрөнгө,account.data_account_type_non_current_assets,account_tag_investing_invest,0,mn_chart_1
+account_template_2501_0101,25010101,Хойшлогдсон татварын хөрөнгө,account.data_account_type_non_current_assets,account.account_tag_financing,0,mn_chart_1
+account_template_2601_0101,26010101,Хөрөнгө оруулалтын зориулалттай үндсэн хөрөнгө,account.data_account_type_non_current_assets,account_tag_investing_invest,0,mn_chart_1
+account_template_2601_0201,26010201,Үндсэн хөрөнгийн үнэ цэнийн бууралт,account.data_account_type_non_current_assets,account_tag_investing_fasset,0,mn_chart_1
+account_template_2701_0101,27010101,Урт хугацаат зээлийн авлага ААН,account.data_account_type_non_current_assets,account_tag_investing_loan,0,mn_chart_1
+account_template_2701_0201,27010201,Урт хугацаат зээлийн авлага Хувь хүн,account.data_account_type_non_current_assets,account_tag_investing_loan,0,mn_chart_1
+account_template_2701_0301,27010301,Урт хугацаат зээлийн авлага Холбоотой тал,account.data_account_type_non_current_assets,account_tag_investing_loan,0,mn_chart_1
+account_template_2709_0101,27090101,Бусад эргэлтийн бус хөрөнгө,account.data_account_type_non_current_assets,account.account_tag_financing,0,mn_chart_1
+account_template_3101_0101,31010101,Худалдааны БН-д өгөх өглөг ААН,account.data_account_type_payable,account_tag_operating_product,1,mn_chart_1
+account_template_3101_0201,31010201,Худалдааны БН-д өгөх өглөг Хувь хүн,account.data_account_type_payable,account_tag_operating_product,1,mn_chart_1
+account_template_3101_0301,31010301,Худалдааны БН-д өгөх өглөг Холбоотой тал,account.data_account_type_payable,account_tag_operating_product,1,mn_chart_1
+account_template_3201_0101,32010101,Урьдчилж орсон худалдааны орлого,account.data_account_type_current_liabilities,account_tag_operating_product,1,mn_chart_1
+account_template_3202_0101,32020101,Урьдчилж орсон түрээсийн орлого,account.data_account_type_current_liabilities,account_tag_operating_product,1,mn_chart_1
+account_template_3203_0101,32030101,Урьдчилж орсон зээлийн хүүгийн орлого,account.data_account_type_current_liabilities,account_tag_operating_interest,1,mn_chart_1
+account_template_3301_0101,33010101,Цалингийн өглөг,account.data_account_type_payable,account_tag_operating_employee,1,mn_chart_1
+account_template_3401_0101,34010101,Татварын өглөг - ААНОАТатвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3401_0201,34010201,Татварын өглөг - НӨАТатвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3401_0301,34010301,Татварын өглөг - ХХОАТатвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3401_0401,34010401,Татварын өглөг - Гаалийн албан татвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3401_0501,34010501,Татварын өглөг - Үл хөдлөх хөрөнгийн албан татвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3401_0601,34010601,Татварын өглөг - Газрын албан татвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3401_0701,34010701,Татварын өглөг - АТӨЯХАТатвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3401_0801,34010801,Татварын өглөг - Агаарын бохирдлын татвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3401_0901,34010901,Татварын өглөг - Онцгой албан татвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3401_1001,34011001,Татварын өглөг - Байгалийн нөөц ашиглалтын татвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3401_1101,34011101,Татварын өглөг – НХАТатвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3401_9901,34019901,Татварын өглөг - Бусад татвар,account.data_account_type_current_liabilities,account_tag_operating_taxrefund,0,mn_chart_1
+account_template_3402_0101,34020101,"ЭМ, НДШ-ийн өглөг",account.data_account_type_current_liabilities,account_tag_operating_insurance,0,mn_chart_1
+account_template_3402_0201,34020201,Сайн дурын даатгалын өглөг,account.data_account_type_current_liabilities,account_tag_operating_insurance2,0,mn_chart_1
+account_template_3501_0101,35010101,Банкны богино хугацаат зээлийн өглөг,account.data_account_type_payable,account_tag_operating_interest,1,mn_chart_1
+account_template_3501_0201,35010201,ББСБ-ын богино хугацаат зээлийн өглөг,account.data_account_type_payable,account_tag_operating_interest,1,mn_chart_1
+account_template_3501_0301,35010301,Хувь хүний богино хугацаат зээлийн өглөг,account.data_account_type_payable,account_tag_operating_interest,1,mn_chart_1
+account_template_3502_0101,35020101,Банкны богино хугацаат зээлийн хүү,account.data_account_type_payable,account_tag_operating_interest,1,mn_chart_1
+account_template_3502_0201,35020201,ББСБ-ын богино хугацаат зээлийн хүү,account.data_account_type_payable,account_tag_operating_interest,1,mn_chart_1
+account_template_3502_0301,35020301,Хувь хүний богино хугацаат зээлийн хүү,account.data_account_type_payable,account_tag_operating_interest,1,mn_chart_1
+account_template_3601_0101,36010101,Ногдол ашгийн өглөг,account.data_account_type_current_liabilities,account_tag_investing_dividends,0,mn_chart_1
+account_template_3701_0101,37010101,Нөөц өр төлбөр,account.data_account_type_current_liabilities,account_tag_exchange,0,mn_chart_1
+account_template_3801_0101,38010101,Даатгалын өглөг,account.data_account_type_payable,account_tag_operating_insurance2,1,mn_chart_1
+account_template_3802_0101,38020101,Гишүүнчлэлийн өглөг,account.data_account_type_payable,account_tag_operating_loyalty,1,mn_chart_1
+account_template_3803_0101,38030101,Ажилчдад өгөх өглөг,account.data_account_type_payable,account_tag_operating_employee,1,mn_chart_1
+account_template_3804_0101,38040101,Хувь хүмүүст өгөх өглөг,account.data_account_type_payable,account.account_tag_operating,1,mn_chart_1
+account_template_3805_0101,38050101,"Баталгаа, барьцааны өглөг",account.data_account_type_payable,account.account_tag_operating,1,mn_chart_1
+account_template_3806_0101,38060101,Худалдааны бус бэлтгэн нийлүүлэгчид өгөх өглөг - ААН,account.data_account_type_payable,account.account_tag_operating,1,mn_chart_1
+account_template_3806_0201,38060201,Худалдааны бус бэлтгэн нийлүүлэгчид өгөх өглөг - Хувь хүн,account.data_account_type_payable,account.account_tag_operating,1,mn_chart_1
+account_template_3806_0301,38060301,Худалдааны бус бэлтгэн нийлүүлэгчид өгөх өглөг - Холбоотой тал,account.data_account_type_payable,account.account_tag_operating,1,mn_chart_1
+account_template_3806_0401,38060401,Худалдан авсан үндсэн хөрөнгийн өглөг,account.data_account_type_payable,account_tag_investing_fasset,1,mn_chart_1
+account_template_3806_0501,38060501,Худалдан авсан хөрөнгө оруулалтын өглөг,account.data_account_type_payable,account_tag_investing_fasset,1,mn_chart_1
+account_template_3809_0101,38090101,Бусад богино хугацаат өр төлбөр,account.data_account_type_current_liabilities,account.account_tag_operating,0,mn_chart_1
+account_template_3901_0101,39010101,Борлуулах зорилгоор эзэмшиж буй эргэлтийн бус хөрөнгөнд хамаарах өр,account.data_account_type_current_liabilities,account_tag_exchange,0,mn_chart_1
+account_template_4001_0101,40010101,Банкны урт хугацаат зээл,account.data_account_type_non_current_liabilities,account_tag_investing_loan,0,mn_chart_1
+account_template_4001_0201,40010201,ББСБ-ын урт хугацаат зээл,account.data_account_type_non_current_liabilities,account_tag_investing_loan,0,mn_chart_1
+account_template_4001_0301,40010301,Хувь хүнээс авсан урт хугацаат зээл,account.data_account_type_non_current_liabilities,account_tag_investing_loan,0,mn_chart_1
+account_template_4001_0401,40010401,Холбоотой талын урт хугацаат зээл,account.data_account_type_non_current_liabilities,account_tag_investing_loan,0,mn_chart_1
+account_template_4002_0101,40020101,Урт хугацаат нөөц өр төлбөр,account.data_account_type_non_current_liabilities,account_tag_investing_loan,0,mn_chart_1
+account_template_4003_0101,40030101,Хойшлогдсон татварын өр,account.data_account_type_non_current_liabilities,account_tag_investing_loan,0,mn_chart_1
+account_template_4009_0101,40090101,Бусад урт хугацаат өр төлбөр,account.data_account_type_non_current_liabilities,account_tag_investing_loan,0,mn_chart_1
+account_template_4101_0101,41010101,Эздийн өмч - төрийн,account.data_account_type_equity,,0,mn_chart_1
+account_template_4102_0101,41020101,Эздийн өмч - хувийн,account.data_account_type_equity,,0,mn_chart_1
+account_template_4103_0101,41030101,Эздийн өмч - хувьцаат,account.data_account_type_equity,,0,mn_chart_1
+account_template_4201_0101,42010101,Халаасны хувьцаа,account.data_account_type_equity,,0,mn_chart_1
+account_template_4301_0101,43010101,Нэмж төлөгдсөн капитал,account.data_account_type_equity,,0,mn_chart_1
+account_template_4401_0101,44010101,Хөрөнгийн дахин үнэлгээний нэмэгдэл,account.data_account_type_equity,,0,mn_chart_1
+account_template_4501_0101,45010101,Гадаад валютын хөрвүүлэлтийн нөөц,account.data_account_type_equity,,0,mn_chart_1
+account_template_4509_0101,45090101,Эздийн өмчийн бусад хэсэг,account.data_account_type_equity,,0,mn_chart_1
+account_template_4601_0101,46010101,Тайлант үеийн хуримтлагдсан ашиг,account.data_account_type_equity,,0,mn_chart_1
+account_template_4601_0201,46010201,Өмнөх үеийн хуримтлагдсан ашиг,account.data_account_type_equity,,0,mn_chart_1
+account_template_5101_0101,51010101,Борлуулалтын орлого,account.data_account_type_revenue,"account_tag_operating_product,tax_report_tag7",0,mn_chart_1
+account_template_5101_0201,51010201,Борлуулалтын орлого (НӨАТ-с чөлөөлөгдөх),account.data_account_type_revenue,"account_tag_operating_product,tax_report_tag2",0,mn_chart_1
+account_template_5102_0101,51020101,Түрээсийн орлого,account.data_account_type_other_income,"account_tag_operating_product,tax_report_tag5",0,mn_chart_1
+account_template_5103_0101,51030101,Хүүний орлого,account.data_account_type_other_income,"account_tag_operating_interest,tax_report_tag39",0,mn_chart_1
+account_template_5104_0101,51040101,Ногдол ашгийн орлого,account.data_account_type_other_income,"account_tag_investing_dividends,tax_report_tag46",0,mn_chart_1
+account_template_5105_0101,51050101,Эрхийн шимтгэлийн орлого,account.data_account_type_other_income,"account_tag_operating_loyalty,tax_report_tag48",0,mn_chart_1
+account_template_5201_0101,52010101,Тооллогын зөрүүний олз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5201_0201,52010201,Тооллогын зөрүүний алдагдал,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5201_0901,52010901,Бусад дэлгэрэнгүй орлого,account.data_account_type_other_income,"account.account_tag_operating,tax_report_tag8",0,mn_chart_1
+account_template_5301_0101,53010101,Валютын ханшийн зөрүүний хэрэгжсэн олз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5301_0201,53010201,Валютын ханшийн зөрүүний хэрэгжээгүй олз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5302_0101,53020101,Валютын ханшийн зөрүүний хэрэгжсэн гарз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5302_0201,53020201,Валютын ханшийн зөрүүний хэрэгжээгүй гарз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5401_0101,54010101,Үндсэн хөрөнгө борлуулсны олз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5401_0201,54010201,Үндсэн хөрөнгө борлуулсны гарз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5501_0101,55010101,Биет бус хөрөнгө борлуулсны олз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5501_0201,55010201,Биет бус хөрөнгө борлуулсны гарз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5601_0101,56010101,Хөрөнгө оруулалт борлуулсны олз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5601_0201,56010201,Хөрөнгө оруулалт борлуулсны гарз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5701_0101,57010101,Үндсэн бус үйл ажиллагааны бусад олз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_5701_0201,57010201,Үндсэн бус үйл ажиллагааны бусад гарз,account.data_account_type_other_income,,0,mn_chart_1
+account_template_6001_0101,60010101,Борлуулалтын хөнгөлөлт,account.data_account_type_revenue,,0,mn_chart_1
+account_template_6002_0101,60020101,Борлуулалтын урамшуулал,account.data_account_type_revenue,,0,mn_chart_1
+account_template_6003_0101,60030101,Борлуулалтын буцаалт,account.data_account_type_revenue,,0,mn_chart_1
+account_template_6004_0101,60040101,Бэлнээр төлсөний хөнгөлөлт,account.data_account_type_revenue,,0,mn_chart_1
+account_template_6005_0101,60050101,Хугацаанаас өмнө төлсний хөнгөлөлт,account.data_account_type_revenue,,0,mn_chart_1
+account_template_6006_0101,60060101,Борлуулалтын үнийн бууралт,account.data_account_type_revenue,,0,mn_chart_1
+account_template_6101_0101,61010101,"Борлуулсан бараа, бүтээгдэхүүний өртөг",account.data_account_type_direct_costs,,0,mn_chart_1
+account_template_6102_0101,61020101,Борлуулсан үйлчилгээний өртөг,account.data_account_type_direct_costs,,0,mn_chart_1
+account_template_7001_0101,70010101,Удирд - Үндсэн цалингийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7001_0201,70010201,Удирд - Нэмэгдэл цалингийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0101,70020101,Удирд - ЭМД-ийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0201,70020201,Удирд - НДШ-ийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0301,70020301,Удирд - Банкны шимтгэл хураамжын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0401,70020401,Удирд - Гадаад томилолтын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0402,70020402,Удирд - Дотоод томилолтын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0501,70020501,Удирд - Бичиг хэргийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0601,70020601,Удирд - Шуудан холбооны зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0602,70020602,Удирд - Суурин утасны зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0603,70020603,Удирд - Үүрэн утасны зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0604,70020604,Удирд - Интернетийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0701,70020701,Удирд - Мэргэжлийн үйлчилгээний зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0801,70020801,Удирд - Хүний нөөцийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_0901,70020901,Удирд - Сонин сэтгүүл захиалгын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_1001,70021001,Удирд - Даатгалын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_1101,70021101,Удирд - Ашиглалтын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_1201,70021201,Удирд - Засварын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_1301,70021301,Удирд - Элэгдэл хорогдлын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_1401,70021401,Удирд - Түрээсийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_1501,70021501,Удирд - Харуул хамгаалалтын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_1601,70021601,Удирд - Цэвэрлэгээ үйлчилгээний зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_1701,70021701,Удирд - Тээврийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_1801,70021801,Удирд - Шатахууны зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_1901,70021901,Удирд - Хүлээн авалтын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_2001,70022001,Удирд - Зар сурталчилгааны зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_2101,70022101,Удирд - Төсөл хөгжлийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_2201,70022201,"Удирд - Судалгаа, туршилтын зардал",account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_2301,70022301,"Удирд - Хүргэлт, түгээлтийн зардал",account.data_account_type_expenses,,0,mn_chart_1
+account_template_7002_9901,70029901,Удирд - Үйл ажиллагааны бусад зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7101_0101,71010101,Борл - Үндсэн цалингийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7101_0201,71010201,Борл - Нэмэгдэл цалингийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0101,71020101,Борл - ЭМД-ийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0201,71020201,Борл - НДШ-ийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0301,71020301,Борл - Банкны шимтгэл хураамжын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0401,71020401,Борл - Гадаад томилолтын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0402,71020402,Борл - Дотоод томилолтын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0501,71020501,Борл - Бичиг хэргийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0601,71020601,Борл - Шуудан холбооны зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0602,71020602,Борл - Суурин утасны зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0603,71020603,Борл - Үүрэн утасны зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0604,71020604,Борл - Интернетийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0701,71020701,Борл - Мэргэжлийн үйлчилгээний зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0801,71020801,Борл - Хүний нөөцийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_0901,71020901,Борл - Сонин сэтгүүл захиалгын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_1001,71021001,Борл - Даатгалын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_1101,71021101,Борл - Ашиглалтын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_1201,71021201,Борл - Засварын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_1301,71021301,Борл - Элэгдэл хорогдлын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_1401,71021401,Борл - Түрээсийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_1501,71021501,Борл - Харуул хамгаалалтын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_1601,71021601,Борл - Цэвэрлэгээ үйлчилгээний зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_1701,71021701,Борл - Тээврийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_1801,71021801,Борл - Шатахууны зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_1901,71021901,Борл - Хүлээн авалтын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_2001,71022001,Борл - Зар сурталчилгааны зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_2101,71022101,Борл - Төсөл хөгжлийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_2201,71022201,"Борл - Судалгаа, туршилтын зардал",account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_2301,71022301,"Борл - Хүргэлт, түгээлтийн зардал",account.data_account_type_expenses,,0,mn_chart_1
+account_template_7102_9901,71029901,Борл - Үйл ажиллагааны бусад зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7201_0101,72010101,Эргэлтийн хөрөнгө санхүүжүүлэх зээлийн хүүгийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7202_0101,72020101,Үндсэн хөрөнгө санхүүжүүлэх зээлийн хүүгийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7203_0101,72030101,Төсөл санхүүжүүлэх зээлийн хүүгийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7301_0101,73010101,"Хайгуул, үнэлгээний зардал",account.data_account_type_expenses,,0,mn_chart_1
+account_template_7401_0101,74010101,"Алданги, торгуулийн зардал",account.data_account_type_expenses,,0,mn_chart_1
+account_template_7402_0101,74020101,"Хандив, тусламжийн зардал",account.data_account_type_expenses,,0,mn_chart_1
+account_template_7403_0101,74030101,Найдваргүй авлагын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7404_0101,74040101,Болзошгүй алдагдлын санд төвлөрүүлсэн хөрөнгийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7405_0101,74050101,Франчайзын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7406_0101,74060101,Банкны үйлчилгээний шимтгэлийн зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7407_0101,74070101,Хөрөнгө акталж устгасны зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7408_0101,74080101,Үйлдвэрийн ослын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7409_0101,74090101,"Туслах үйлдвэрлэл, үйлчилгээний зардал",account.data_account_type_expenses,,0,mn_chart_1
+account_template_7410_0101,74100101,Үндсэн хөрөнгийн үнэ цэнийн бууралтын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_7499_0101,74990101,Бусад ерөнхий зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_9101_0101,91010101,Тайлант үеийн орлогын албан татварын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_9102_0101,91020101,Хойшлогдсон татварын зардал,account.data_account_type_expenses,,0,mn_chart_1
+account_template_9103_0101,91030101,Татвар ногдохгүй бусад дэлгэрэнгүй орлого,account.data_account_type_expenses,,0,mn_chart_1
+account_template_9301_0101,93010101,Эхний үлдэгдлийн эх үүсвэр,account.data_unaffected_earnings,,0,mn_chart_1
diff --git a/addons/l10n_mn/data/account.chart.template.csv b/addons/l10n_mn/data/account.chart.template.csv
new file mode 100644
index 00000000000..3c63ba77781
--- /dev/null
+++ b/addons/l10n_mn/data/account.chart.template.csv
@@ -0,0 +1,2 @@
+id,property_account_receivable_id/id,property_account_payable_id/id,property_account_expense_categ_id/id,property_account_income_categ_id/id,income_currency_exchange_account_id/id,expense_currency_exchange_account_id/id
+mn_chart_1,account_template_1201_0201,account_template_3101_0201,account_template_7002_1101,account_template_5101_0201,account_template_5301_0201,account_template_5302_0201
diff --git a/addons/l10n_mn/data/account.tax.group.csv b/addons/l10n_mn/data/account.tax.group.csv
new file mode 100644
index 00000000000..9e2a71ba648
--- /dev/null
+++ b/addons/l10n_mn/data/account.tax.group.csv
@@ -0,0 +1,7 @@
+id,name,sequence
+account_tax_group1,НӨАТ ногдох бараа,10
+account_tax_group2,НӨАТ ногдох үйлчилгээ,10
+account_tax_group3,Экспортын борлуулалт,11
+account_tax_group4,"Худалдан авсан бараа, үйлчилгээ",12
+account_tax_group5,Санхүүгийн түрээсийн зүйл,13
+account_tax_group6,НӨАТ-с чөлөөлөгдөх,14
diff --git a/addons/l10n_mn/data/account_chart_template_configuration_data.xml b/addons/l10n_mn/data/account_chart_template_configuration_data.xml
new file mode 100644
index 00000000000..2de735fa360
--- /dev/null
+++ b/addons/l10n_mn/data/account_chart_template_configuration_data.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo noupdate="1">
+
+ <function model="account.chart.template" name="try_loading_for_current_company">
+ <value eval="[ref('l10n_mn.mn_chart_1')]"/>
+ </function>
+
+</odoo>
diff --git a/addons/l10n_mn/data/account_chart_template_data.xml b/addons/l10n_mn/data/account_chart_template_data.xml
new file mode 100644
index 00000000000..bb64e86c95e
--- /dev/null
+++ b/addons/l10n_mn/data/account_chart_template_data.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <menuitem id="account_reports_mn_statements_menu" name="Mongolia" parent="account.menu_finance_reports" sequence="0" groups="account.group_account_user"/>
+
+ <record id="mn_chart_1" model="account.chart.template">
+ <field name="name">Mongolia</field>
+ <field name="bank_account_code_prefix">11</field>
+ <field name="cash_account_code_prefix">10</field>
+ <field name="transfer_account_code_prefix">1109</field>
+ <field name="code_digits">8</field>
+ <field name="currency_id" ref="base.MNT"/>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_mn/data/account_fiscal_position_template_data.xml b/addons/l10n_mn/data/account_fiscal_position_template_data.xml
new file mode 100644
index 00000000000..94780439b51
--- /dev/null
+++ b/addons/l10n_mn/data/account_fiscal_position_template_data.xml
@@ -0,0 +1,399 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <!-- = = = = = = = = = = = = = = = -->
+ <!-- Fiscal Position Templates -->
+ <!-- = = = = = = = = = = = = = = = -->
+ <record id="fiscal_position_vat0" model="account.fiscal.position.template">
+ <field name="name">Дотоодын борлуулалт - Худалдан авалт</field>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="auto_apply" eval="True" />
+ <field name="country_id" ref="base.mn"/>
+ </record>
+
+ <record id="fiscal_position_vat2" model="account.fiscal.position.template">
+ <field name="name">НӨАТ 0% (3000000)</field>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ </record>
+
+ <record id="fiscal_position_vat3" model="account.fiscal.position.template">
+ <field name="name">НӨАТ-гүй (2000000)</field>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ </record>
+
+ <record id="fiscal_position_vat4" model="account.fiscal.position.template">
+ <field name="name">Гадаад борлуулалт - Худалдан авалт</field>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="auto_apply" eval="True" />
+ </record>
+
+ <!-- = = = = = = = = = = = = = = = -->
+ <!-- Fiscal Position Tax Templates -->
+ <!-- = = = = = = = = = = = = = = = -->
+
+ <record id="fp_tax_template_vat2_sale_vat1" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat1" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat4" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat4" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat5" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat5" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat6" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat6" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat7" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat7" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat8" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat8" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat9" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat9" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat10" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat10" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat11" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat11" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat12" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat12" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat13" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat13" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat14" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat14" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat15" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat15" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat16" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat16" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat17" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat17" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat18" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat18" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat19" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat19" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat20" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat20" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat21" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat21" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat2_sale_vat22" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat2" />
+ <field name="tax_src_id" ref="account_tax_sale_vat22" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat2" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat1" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat1" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat4" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat4" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat5" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat5" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat6" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat6" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat7" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat7" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat8" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat8" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat9" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat9" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat10" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat10" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat11" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat11" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat12" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat12" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat13" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat13" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat14" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat14" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat15" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat15" />
+ <field name="tax_src_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat16" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat16" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat17" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat17" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat18" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat18" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat19" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat19" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat20" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat20" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat21" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat21" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat22" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat3" />
+ <field name="tax_src_id" ref="account_tax_sale_vat22" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat3" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat1" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat1" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat23" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat4" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat4" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat23" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat5" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat5" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat23" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat6" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat6" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat23" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat7" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat7" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat23" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat8" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat8" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat9" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat9" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat10" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat10" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat11" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat11" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat12" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat12" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat13" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat13" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat14" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat14" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat15" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat15" />
+ <field name="tax_src_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat16" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat16" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat17" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat17" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat18" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat18" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat19" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat19" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat20" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat20" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat21" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat21" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_sale_vat22" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_sale_vat22" />
+ <field name="tax_dest_id" ref="account_tax_sale_vat24" />
+ </record>
+
+ <record id="fp_tax_template_vat3_purchase_vat1" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_vat4" />
+ <field name="tax_src_id" ref="account_tax_purchase_vat1" />
+ <field name="tax_dest_id" ref="account_tax_purchase_vat2" />
+ </record>
+
+</odoo>
diff --git a/addons/l10n_mn/data/account_tax_template_data.xml b/addons/l10n_mn/data/account_tax_template_data.xml
new file mode 100644
index 00000000000..8f5d16d9b75
--- /dev/null
+++ b/addons/l10n_mn/data/account_tax_template_data.xml
@@ -0,0 +1,1476 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <record id="account_tax_sale_vat1" model="account.tax.template">
+ <field name="name">НӨАТ бараа</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">1</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group1"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag5')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag5')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag5')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag5')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat1" model="account.tax.template">
+ <field name="name">НӨАТ-тэй худалдан авалт</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">1</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group4"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag36'),ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag36')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag36'),ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag36')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat2" model="account.tax.template">
+ <field name="name">НӨАТ-гүй худалдан авалт</field>
+ <field name="amount">0.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">10</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">0%</field>
+ <field name="tax_group_id" ref="account_tax_group4"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat2" model="account.tax.template">
+ <field name="name">НӨАТ 0% борлуулалт</field>
+ <field name="amount">0.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">10</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">0%</field>
+ <field name="tax_group_id" ref="account_tax_group6"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag2')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag2')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat3" model="account.tax.template">
+ <field name="name">НӨАТ чөлөөлөх борлуулалт</field>
+ <field name="amount">0.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">10</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">0%</field>
+ <field name="tax_group_id" ref="account_tax_group6"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag2')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag2')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat4" model="account.tax.template">
+ <field name="name">НӨАТ бусад барааны борлуулалт</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group1"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag6')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag6')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag6')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag6')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat5" model="account.tax.template">
+ <field name="name">НӨАТ эрх борлуулалт</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group1"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag7')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag7')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag7')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag7')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat6" model="account.tax.template">
+ <field name="name">НӨАТ татан буугдах үеийн борлуулалт</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group1"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag8')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag8')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag8')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag8')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat7" model="account.tax.template">
+ <field name="name">НӨАТ өрийн төлбөрт өгсөн бараа</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group1"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag9')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag9')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag9')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag9')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat8" model="account.tax.template">
+ <field name="name">НӨАТ нотариат үйлчилгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag11')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag11')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag11')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag11')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat9" model="account.tax.template">
+ <field name="name">НӨАТ өрийн төлбөрт өгсөн үйлчилгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag12')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag12')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag12')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag12')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat10" model="account.tax.template">
+ <field name="name">НӨАТ цахилгаан, дулаан, ус, шуудан, холбоо</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag13')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag13')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag13')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag13')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat11" model="account.tax.template">
+ <field name="name">НӨАТ бараа түрээслүүлэх, эзэмшүүлэх</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag14')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag14')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag14')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag14')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat12" model="account.tax.template">
+ <field name="name">НӨАТ байр түрээслүүлэх, эзэмшүүлэх</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag15')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag15')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag15')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag15')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat13" model="account.tax.template">
+ <field name="name">НӨАТ хөрөнгө түрээслүүлэх, эзэмшүүлэх</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag16')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag16')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag16')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag16')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat14" model="account.tax.template">
+ <field name="name">НӨАТ бүтээл, загвар, зохиогчийн эрх</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag17')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag17')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag17')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag17')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat15" model="account.tax.template">
+ <field name="name">НӨАТ хонжворт сугалаа, таавар, бооцоо</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag18')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag18')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag18')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag18')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat16" model="account.tax.template">
+ <field name="name">НӨАТ зуучлалын үйлчилгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag19')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag19')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag19')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag19')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat17" model="account.tax.template">
+ <field name="name">НӨАТ авсан хүү торгууль, алданги</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag20')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag20')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag20')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag20')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat18" model="account.tax.template">
+ <field name="name">НӨАТ хөрөнгийн үнэлгээний үйлчилгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag21')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag21')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag21')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag21')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat19" model="account.tax.template">
+ <field name="name">НӨАТ төсвийн санхүүжилт, татаас</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag22')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag22')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag22')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag22')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat20" model="account.tax.template">
+ <field name="name">НӨАТ хууль зүйн үйлчилгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag23')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag23')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag23')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag23')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat21" model="account.tax.template">
+ <field name="name">НӨАТ үсчин, гоо сайхан, угаалга, цэвэрлэгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag24')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag24')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag24')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag24')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat22" model="account.tax.template">
+ <field name="name">НӨАТ бусад үйлчилгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">20</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group2"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag25')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag25')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag25')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag25')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat23" model="account.tax.template">
+ <field name="name">НӨАТ 0% экспорт бараа</field>
+ <field name="amount">0.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">30</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">0%</field>
+ <field name="tax_group_id" ref="account_tax_group3"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag28')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag28')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat24" model="account.tax.template">
+ <field name="name">НӨАТ 0% экспорт үйлчилгээ</field>
+ <field name="amount">0.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">30</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">0%</field>
+ <field name="tax_group_id" ref="account_tax_group3"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag29')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag29')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat3" model="account.tax.template">
+ <field name="name">НӨАТ импортоор авсан бараа, үйлчилгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">40</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group4"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33'),ref('vat_report_tag35')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag35')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33'),ref('vat_report_tag35')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag35')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat4" model="account.tax.template">
+ <field name="name">НӨАТ төлөгчөөр бүртгүүлэх үед худалдан авсан</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">40</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group4"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33'),ref('vat_report_tag37')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag37')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33'),ref('vat_report_tag37')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag37')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat5" model="account.tax.template">
+ <field name="name">НӨАТ мал аж ахуй эрхлэгчээс худалдан авсан</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">40</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group4"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33'),ref('vat_report_tag38')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag38')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33'),ref('vat_report_tag38')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag38')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat6" model="account.tax.template">
+ <field name="name">НӨАТ автомашин, эд анги худалдан авалт</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">40</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group4"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag41')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag41')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat7" model="account.tax.template">
+ <field name="name">НӨАТ ажлын хэрэгцээнд авсан бараа</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">40</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group4"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag42')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag42')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat8" model="account.tax.template">
+ <field name="name">НӨАТ импортоор авсан үндсэн хөрөнгө</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">40</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group4"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag43')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag43')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat9" model="account.tax.template">
+ <field name="name">НӨАТ чөлөөлөгдөх үйлдвэрлэлд зориулсан бараа, үйлчилгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">40</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group4"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag44')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag44')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat10" model="account.tax.template">
+ <field name="name">НӨАТ хайгуулд зориулсан импортын бараа, үйлчилгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">40</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group4"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag45')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag45')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat11" model="account.tax.template">
+ <field name="name">НӨАТ импортын бусад хамааралгүй бараа, үйлчилгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">40</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group4"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag46')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag33')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag46')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat25" model="account.tax.template">
+ <field name="name">НӨАТ дотоодод зарсан санхүүгийн түрээс</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">50</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group5"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag48')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag48')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag48')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag48')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_sale_vat26" model="account.tax.template">
+ <field name="name">НӨАТ Факторинг, форфайтинг хэлцлийн үйлчилгээ</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">50</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">sale</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group5"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag49')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag49')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag49')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_3401_0201'),
+ 'tag_ids': [ref('vat_report_tag49')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat12" model="account.tax.template">
+ <field name="name">НӨАТ Санхүүгийн түрээсийн худалдан авалт</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">50</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group5"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag51')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag51')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag51')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag51')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="account_tax_purchase_vat13" model="account.tax.template">
+ <field name="name">НӨАТ Факторинг, форфайтинг худалдан авалт</field>
+ <field name="amount">10.0</field>
+ <field name="price_include" eval="1"/>
+ <field name="sequence">50</field>
+ <field name="amount_type">percent</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="description">10%</field>
+ <field name="tax_group_id" ref="account_tax_group5"/>
+ <field name="chart_template_id" ref="mn_chart_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag52')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag52')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'tag_ids': [ref('vat_report_tag52')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('account_template_1204_0301'),
+ 'tag_ids': [ref('vat_report_tag52')],
+ }),
+ ]"/>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_mn/static/description/icon.png b/addons/l10n_mn/static/description/icon.png
new file mode 100644
index 00000000000..dae4b68b81e
--- /dev/null
+++ b/addons/l10n_mn/static/description/icon.png
Binary files differ
diff --git a/addons/l10n_mx/data/account_tag_data.xml b/addons/l10n_mx/data/account_tag_data.xml
index 244c9b65ff3..1e8b23dacda 100644
--- a/addons/l10n_mx/data/account_tag_data.xml
+++ b/addons/l10n_mx/data/account_tag_data.xml
@@ -479,25 +479,25 @@
<field name='name'>119.01 IVA pendiente de pago</field>
<field name='color'>4</field>
<field name='applicability'>accounts</field>
- <field name='nature'>A</field>
+ <field name='nature'>D</field>
</record>
<record id='account_tag_119_02' model='account.account.tag'>
<field name='name'>119.02 IVA de importación pendiente de pago</field>
<field name='color'>4</field>
<field name='applicability'>accounts</field>
- <field name='nature'>A</field>
+ <field name='nature'>D</field>
</record>
<record id='account_tag_119_03' model='account.account.tag'>
<field name='name'>119.03 IEPS pendiente de pago</field>
<field name='color'>4</field>
<field name='applicability'>accounts</field>
- <field name='nature'>A</field>
+ <field name='nature'>D</field>
</record>
<record id='account_tag_119_04' model='account.account.tag'>
<field name='name'>119.04 IEPS pendiente de pago en importación</field>
<field name='color'>4</field>
<field name='applicability'>accounts</field>
- <field name='nature'>A</field>
+ <field name='nature'>D</field>
</record>
<record id='account_tag_120_01' model='account.account.tag'>
<field name='name'>120.01 Anticipo a proveedores nacional</field>
@@ -917,7 +917,7 @@
<field name='name'>180.01 Crédito mercantil</field>
<field name='color'>4</field>
<field name='applicability'>accounts</field>
- <field name='nature'>A</field>
+ <field name='nature'>D</field>
</record>
<record id='account_tag_181_01' model='account.account.tag'>
<field name='name'>181.01 Gastos de instalación</field>
@@ -2423,7 +2423,7 @@
<field name='name'>503 Devoluciones, descuentos o bonificaciones sobre compras</field>
<field name='color'>1</field>
<field name='applicability'>accounts</field>
- <field name='nature'>D</field>
+ <field name='nature'>A</field>
</record>
<record id='account_tag_503_01' model='account.account.tag'>
<field name='name'>503.01 Devoluciones, descuentos o bonificaciones sobre compras</field>
@@ -2585,13 +2585,13 @@
<field name='name'>505.01 Costo por venta de activo fijo</field>
<field name='color'>4</field>
<field name='applicability'>accounts</field>
- <field name='nature'>A</field>
+ <field name='nature'>D</field>
</record>
<record id='account_tag_505_02' model='account.account.tag'>
<field name='name'>505.02 Costo por baja de activo fijo</field>
<field name='color'>4</field>
<field name='applicability'>accounts</field>
- <field name='nature'>A</field>
+ <field name='nature'>D</field>
</record>
<record id='account_tag_601_01' model='account.account.tag'>
<field name='name'>601.01 Sueldos y salarios</field>
@@ -4781,7 +4781,7 @@
<field name='name'>607.01 Participación de los trabajadores en las utilidades</field>
<field name='color'>4</field>
<field name='applicability'>accounts</field>
- <field name='nature'>A</field>
+ <field name='nature'>D</field>
</record>
<record id='account_tag_608_01' model='account.account.tag'>
<field name='name'>608.01 Participación en resultados de subsidiarias</field>
@@ -4799,7 +4799,7 @@
<field name='name'>610.01 Participación de los trabajadores en las utilidades diferida</field>
<field name='color'>4</field>
<field name='applicability'>accounts</field>
- <field name='nature'>A</field>
+ <field name='nature'>D</field>
</record>
<record id='account_tag_611_01' model='account.account.tag'>
<field name='name'>611.01 Impuesto Sobre la renta</field>
@@ -4817,7 +4817,7 @@
<field name='name'>612.01 Gastos no deducibles para CUFIN</field>
<field name='color'>4</field>
<field name='applicability'>accounts</field>
- <field name='nature'>A</field>
+ <field name='nature'>D</field>
</record>
<record id='account_tag_613_01' model='account.account.tag'>
<field name='name'>613.01 Depreciación de edificios</field>
diff --git a/addons/l10n_za/__init__.py b/addons/l10n_za/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/addons/l10n_za/__init__.py
diff --git a/addons/l10n_za/__manifest__.py b/addons/l10n_za/__manifest__.py
new file mode 100644
index 00000000000..692ffecfc47
--- /dev/null
+++ b/addons/l10n_za/__manifest__.py
@@ -0,0 +1,26 @@
+# -*- encoding: utf-8 -*-
+
+# Copyright (C) 2017 Paradigm Digital (<http://www.paradigmdigital.co.za>).
+
+{
+ 'name': 'South Africa - Accounting',
+ 'version': '1.0',
+ 'category': 'Localization',
+ 'description': """
+This is the latest basic South African localisation necessary to run Odoo in ZA:
+================================================================================
+ - a generic chart of accounts
+ - SARS VAT Ready Structure""",
+ 'author': 'Paradigm Digital',
+ 'website': 'https://www.paradigmdigital.co.za',
+ 'depends': ['account', 'base_vat'],
+ 'data': [
+ 'data/account_tax_report_data.xml',
+ 'data/account.tax.group.csv',
+ 'data/account_chart_template_data.xml',
+ 'data/account.account.template.csv',
+ 'data/account_tax_template_data.xml',
+ 'data/account_chart_template_post_data.xml',
+ 'data/account_chart_template_configure_data.xml',
+ ],
+}
diff --git a/addons/l10n_za/data/account.account.tag.csv b/addons/l10n_za/data/account.account.tag.csv
new file mode 100644
index 00000000000..9b8832a53eb
--- /dev/null
+++ b/addons/l10n_za/data/account.account.tag.csv
@@ -0,0 +1,17 @@
+id,name,applicability
+tag_ST1,ST1,taxes
+tag_ST1A,ST1A,taxes
+tag_ST2,ST2,taxes
+tag_ST2A,ST2A,taxes
+tag_ST3,ST3,taxes
+tag_ST5,ST5,taxes
+tag_ST7,ST7,taxes
+tag_ST10,ST10,taxes
+tag_ST12,ST12,taxes
+tag_PT14,PT14,taxes
+tag_PT14A,PT14A,taxes
+tag_PT15,PT15,taxes
+tag_PT15A,PT15A,taxes
+tag_PT16,PT16,taxes
+tag_PT17,PT17,taxes
+tag_PT18,PT18,taxes \ No newline at end of file
diff --git a/addons/l10n_za/data/account.account.template.csv b/addons/l10n_za/data/account.account.template.csv
new file mode 100644
index 00000000000..404eb24a86e
--- /dev/null
+++ b/addons/l10n_za/data/account.account.template.csv
@@ -0,0 +1,111 @@
+id,code,name,user_type_id/id,reconcile,chart_template_id/id
+100020,100020,Stock Valuation Account,account.data_account_type_current_assets,True,default_chart_template
+100030,100030,Stock Work In Progress,account.data_account_type_current_assets,False,default_chart_template
+100040,100040,Stock Finished Goods,account.data_account_type_current_assets,False,default_chart_template
+100050,100050,Stock Delivered Control Account,account.data_account_type_current_assets,True,default_chart_template
+100060,100060,Purchase Tax Control Account,account.data_account_type_current_assets,False,default_chart_template
+100070,100070,Other Current Assets,account.data_account_type_current_assets,False,default_chart_template
+110010,110010,Debtors Control Account,account.data_account_type_receivable,True,default_chart_template
+110020,110020,Sundry Debtors,account.data_account_type_receivable,True,default_chart_template
+124010,124010,Credit Card Merchant Account,account.data_account_type_liquidity,False,default_chart_template
+126010,126010,Cash In Hand,account.data_account_type_liquidity,False,default_chart_template
+130010,130010,Prepayments,account.data_account_type_prepayments,False,default_chart_template
+140010,140010,Software,account.data_account_type_fixed_assets,False,default_chart_template
+140020,140020,Patents & Trademarks,account.data_account_type_fixed_assets,False,default_chart_template
+140030,140030,Fixtures & Fittings,account.data_account_type_fixed_assets,False,default_chart_template
+140040,140040,Land & Buildings,account.data_account_type_fixed_assets,False,default_chart_template
+140050,140050,Motor Vehicles,account.data_account_type_fixed_assets,False,default_chart_template
+140060,140060,Office Equipment (incl computer equipment),account.data_account_type_fixed_assets,False,default_chart_template
+140070,140070,Plant & Machinery,account.data_account_type_fixed_assets,False,default_chart_template
+150010,150010,Non-current assets,account.data_account_type_non_current_assets,False,default_chart_template
+200010,200010,Stock Received Control Account,account.data_account_type_current_liabilities,True,default_chart_template
+200020,200020,Sundry Creditors,account.data_account_type_current_liabilities,False,default_chart_template
+200030,200030,Other Creditors,account.data_account_type_current_liabilities,False,default_chart_template
+200040,200040,Accruals,account.data_account_type_current_liabilities,False,default_chart_template
+200050,200050,Bad debt provision,account.data_account_type_current_liabilities,False,default_chart_template
+200060,200060,Sales Tax Control Account,account.data_account_type_current_liabilities,False,default_chart_template
+200070,200070,Manual Adjustments & VAT,account.data_account_type_current_liabilities,False,default_chart_template
+200080,200080,Loans,account.data_account_type_current_liabilities,False,default_chart_template
+200090,200090,Hire Purchase,account.data_account_type_current_liabilities,False,default_chart_template
+200100,200100,Mortgages,account.data_account_type_current_liabilities,False,default_chart_template
+210010,210010,Company Credit Card,account.data_account_type_credit_card,False,default_chart_template
+220010,220010,Creditors Control Account,account.data_account_type_payable,True,default_chart_template
+220020,220020,SARS - VAT Account,account.data_account_type_payable,True,default_chart_template
+220030,220030,P.A.Y.E. & UIF,account.data_account_type_payable,True,default_chart_template
+220040,220040,Net Wages,account.data_account_type_payable,True,default_chart_template
+220050,220050,Pension Fund,account.data_account_type_payable,True,default_chart_template
+220060,220060,Corporation Tax,account.data_account_type_payable,True,default_chart_template
+300010,300010,Called up share capital,account.data_account_type_equity,False,default_chart_template
+300020,300020,Share premium account,account.data_account_type_equity,False,default_chart_template
+300030,300030,Revaluation reserve,account.data_account_type_equity,False,default_chart_template
+300040,300040,Other reserves,account.data_account_type_equity,False,default_chart_template
+300050,300050,Capital,account.data_account_type_equity,False,default_chart_template
+300060,300060,Dividends,account.data_account_type_equity,False,default_chart_template
+300070,300070,Drawings,account.data_account_type_equity,False,default_chart_template
+400010,400010,Undistributed Profits/Losses,account.data_unaffected_earnings,False,default_chart_template
+500010,500010,Sales category 1,account.data_account_type_revenue,False,default_chart_template
+500020,500020,Sales category 2,account.data_account_type_revenue,False,default_chart_template
+500030,500030,Sales category 3,account.data_account_type_revenue,False,default_chart_template
+500040,500040,Sales category 4,account.data_account_type_revenue,False,default_chart_template
+500050,500050,Bank Interest received,account.data_account_type_revenue,False,default_chart_template
+500060,500060,Investment Interest received,account.data_account_type_revenue,False,default_chart_template
+500070,500070,Profits/Losses on disposals of assets,account.data_account_type_revenue,False,default_chart_template
+500080,500080,Rental Income,account.data_account_type_revenue,False,default_chart_template
+510010,510010,Other Income,account.data_account_type_other_income,False,default_chart_template
+600010,600010,Cost of sales 1,account.data_account_type_direct_costs,False,default_chart_template
+600020,600020,Cost of sales 2,account.data_account_type_direct_costs,False,default_chart_template
+600030,600030,Cost of sales 3,account.data_account_type_direct_costs,False,default_chart_template
+600040,600040,Cost of sales 4,account.data_account_type_direct_costs,False,default_chart_template
+610010,610010,Marketing,account.data_account_type_expenses,False,default_chart_template
+610020,610020,Exhibitions and events,account.data_account_type_expenses,False,default_chart_template
+610030,610030,PR,account.data_account_type_expenses,False,default_chart_template
+610040,610040,Distribution vehicles,account.data_account_type_expenses,False,default_chart_template
+610050,610050,Distribution salaries and wages,account.data_account_type_expenses,False,default_chart_template
+610060,610060,Shipping,account.data_account_type_expenses,False,default_chart_template
+610070,610070,Directors pension,account.data_account_type_expenses,False,default_chart_template
+610080,610080,Directors remuneration,account.data_account_type_expenses,False,default_chart_template
+610090,610090,Gross Salaries,account.data_account_type_expenses,False,default_chart_template
+610100,610100,Employers SDL & UIF,account.data_account_type_expenses,False,default_chart_template
+610110,610110,Subcontractors payments,account.data_account_type_expenses,False,default_chart_template
+610120,610120,Rent and rates,account.data_account_type_expenses,False,default_chart_template
+610130,610130,Light / heat and power,account.data_account_type_expenses,False,default_chart_template
+610140,610140,Repairs and maintenance,account.data_account_type_expenses,False,default_chart_template
+610150,610150,Car hire,account.data_account_type_expenses,False,default_chart_template
+610160,610160,Car fuel,account.data_account_type_expenses,False,default_chart_template
+610170,610170,Car maintenance,account.data_account_type_expenses,False,default_chart_template
+610180,610180,Telephone,account.data_account_type_expenses,False,default_chart_template
+610190,610190,Internet & hosting,account.data_account_type_expenses,False,default_chart_template
+610200,610200,Mobiles,account.data_account_type_expenses,False,default_chart_template
+610210,610210,Stationery,account.data_account_type_expenses,False,default_chart_template
+610220,610220,Office consumables,account.data_account_type_expenses,False,default_chart_template
+610230,610230,Postage and Carriage,account.data_account_type_expenses,False,default_chart_template
+610240,610240,Books,account.data_account_type_expenses,False,default_chart_template
+610250,610250,Network costs,account.data_account_type_expenses,False,default_chart_template
+610260,610260,Software expenses,account.data_account_type_expenses,False,default_chart_template
+610270,610270,Other computer costs,account.data_account_type_expenses,False,default_chart_template
+610280,610280,Recruitment fees,account.data_account_type_expenses,False,default_chart_template
+610290,610290,Other admin expenses,account.data_account_type_expenses,False,default_chart_template
+610300,610300,Accounting,account.data_account_type_expenses,False,default_chart_template
+610310,610310,Auditing,account.data_account_type_expenses,False,default_chart_template
+610320,610320,Consultancy,account.data_account_type_expenses,False,default_chart_template
+610330,610330,Legal and professional charges,account.data_account_type_expenses,False,default_chart_template
+610340,610340,Exchange gains/losses,account.data_account_type_expenses,False,default_chart_template
+610350,610350,Other sundry expenses,account.data_account_type_expenses,False,default_chart_template
+610360,610360,Bad debts,account.data_account_type_expenses,False,default_chart_template
+610370,610370,Interest paid,account.data_account_type_expenses,False,default_chart_template
+610380,610380,Bank Charges,account.data_account_type_expenses,False,default_chart_template
+610390,610390,Donations,account.data_account_type_expenses,False,default_chart_template
+610400,610400,Entertaining,account.data_account_type_expenses,False,default_chart_template
+610410,610410,Insurance,account.data_account_type_expenses,False,default_chart_template
+610420,610420,Travel and subsistence,account.data_account_type_expenses,False,default_chart_template
+610430,610430,Corporation tax expense,account.data_account_type_expenses,False,default_chart_template
+610440,610440,Foreign Exchange Gains/Losses,account.data_account_type_expenses,False,default_chart_template
+610450,610450,Price Differences Control Account,account.data_account_type_expenses,False,default_chart_template
+610460,610460,Cash Register Gains/Losses,account.data_account_type_expenses,False,default_chart_template
+620010,620010,Software Depreciation,account.data_account_type_depreciation,False,default_chart_template
+620020,620020,Patents & Trademarks Depreciation,account.data_account_type_depreciation,False,default_chart_template
+620030,620030,Fixtures and fittings Depreciation,account.data_account_type_depreciation,False,default_chart_template
+620040,620040,Land and buildings Depreciation,account.data_account_type_depreciation,False,default_chart_template
+620050,620050,Motor vehicles Depreciation,account.data_account_type_depreciation,False,default_chart_template
+620060,620060,Office equipment (inc computer equipment) Depreciation,account.data_account_type_depreciation,False,default_chart_template
+620070,620070,Plant and machinery Depreciation,account.data_account_type_depreciation,False,default_chart_template \ No newline at end of file
diff --git a/addons/l10n_za/data/account.tax.group.csv b/addons/l10n_za/data/account.tax.group.csv
new file mode 100644
index 00000000000..ec35d11456c
--- /dev/null
+++ b/addons/l10n_za/data/account.tax.group.csv
@@ -0,0 +1,3 @@
+id,name
+tax_group_0,VAT 0%
+tax_group_1,VAT 15%
diff --git a/addons/l10n_za/data/account_chart_template_configure_data.xml b/addons/l10n_za/data/account_chart_template_configure_data.xml
new file mode 100644
index 00000000000..6a3db99a6d5
--- /dev/null
+++ b/addons/l10n_za/data/account_chart_template_configure_data.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo noupdate="1">
+
+ <function model="account.chart.template" name="try_loading_for_current_company">
+ <value eval="[ref('l10n_za.default_chart_template')]"/>
+ </function>
+
+</odoo>
diff --git a/addons/l10n_za/data/account_chart_template_data.xml b/addons/l10n_za/data/account_chart_template_data.xml
new file mode 100644
index 00000000000..652646a5d18
--- /dev/null
+++ b/addons/l10n_za/data/account_chart_template_data.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <menuitem id="account_reports_za_statements_menu" name="South Africa" parent="account.menu_finance_reports" sequence="0" groups="account.group_account_user"/>
+
+ <record id="default_chart_template" model="account.chart.template">
+ <field name="name">South African Tax and Account Chart Template (by Paradigm Digital)</field>
+ <field name="bank_account_code_prefix">1200</field>
+ <field name="cash_account_code_prefix">1250</field>
+ <field name="transfer_account_code_prefix">1010</field>
+ <field name="code_digits">6</field>
+ <field name="currency_id" ref="base.ZAR"/>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_za/data/account_chart_template_post_data.xml b/addons/l10n_za/data/account_chart_template_post_data.xml
new file mode 100644
index 00000000000..1d1fa398173
--- /dev/null
+++ b/addons/l10n_za/data/account_chart_template_post_data.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <record id="default_chart_template" model="account.chart.template">
+ <field name="property_account_receivable_id" ref="110010"/>
+ <field name="property_account_payable_id" ref="220010"/>
+ <field name="property_account_expense_categ_id" ref="600010"/>
+ <field name="property_account_income_categ_id" ref="500010"/>
+ <field name="property_stock_account_input_categ_id" ref="200010"/>
+ <field name="property_stock_account_output_categ_id" ref="100050"/>
+ <field name="property_stock_valuation_account_id" ref="100020"/>
+ <field name="income_currency_exchange_account_id" ref="610440"/>
+ <field name="expense_currency_exchange_account_id" ref="610440"/>
+ <field name="use_anglo_saxon" eval="True"/>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_za/data/account_tax_report_data.xml b/addons/l10n_za/data/account_tax_report_data.xml
new file mode 100644
index 00000000000..e0a53422a55
--- /dev/null
+++ b/addons/l10n_za/data/account_tax_report_data.xml
@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <record id="total_vat_payable" model="account.tax.report.line">
+ <field name="name">[20] VAT PAYABLE/REFUNDABLE (Total A - Total B)</field>
+ <field name="formula"> (VAT4 + VAT4A + (SEC6 * 60/100 + SEC7) + VAT11 + VAT12) - (VAT14 + VAT14A + VAT15 + VAT16 + VAT17 + VAT18)</field>
+ <field name="country_id" ref="base.za"/>
+ <field name="sequence" eval="1"/>
+ </record>
+
+ <record id="total_output_tax" model="account.tax.report.line">
+ <field name="name">[13] Total A: TOTAL OUTPUT TAX (4 + 4A + 9 + 11 + 12)</field>
+ <field name="sequence" eval="1"/>
+ <field name="formula">VAT4 + VAT4A + (SEC6 * 60/100 + SEC7) + VAT11 + VAT12</field>
+ <field name="parent_id" ref='total_vat_payable'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="total_input_tax" model="account.tax.report.line">
+ <field name="name">[19] Total B: TOTAL INPUT TAX (14 + 14A + 15 + 16 + 17 + 18)</field>
+ <field name="sequence" eval="2"/>
+ <field name="formula">VAT14 + VAT14A + VAT15 + VAT16 + VAT17 + VAT18</field>
+ <field name="parent_id" ref='total_vat_payable'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="standard_rate_exclude_capital_goods_service" model="account.tax.report.line">
+ <field name="name">[1] Standard Rate (Excluding Capital goods and/or services and accomodation)</field>
+ <field name="tag_name">[1] Standard Rate (Excluding Capital goods and/or services and accomodation)</field>
+ <field name="code">VAT1</field>
+ <field name="sequence" eval="1"/>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="vat_on_standard_rate_exclude_capital_goods_service" model="account.tax.report.line">
+ <field name="name">[4] x 15/ (100 + 15)</field>
+ <field name="tag_name">[4] x 15/ (100 + 15)</field>
+ <field name="code">VAT4</field>
+ <field name="sequence" eval="2"/>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="standard_rate_only_capital_goods_service" model="account.tax.report.line">
+ <field name="name">[1A] Standard Rate (Only Capital goods and/or services)</field>
+ <field name="tag_name">[1A] Standard Rate (Only Capital goods and/or services)</field>
+ <field name="code">VAT1A</field>
+ <field name="sequence" eval="3"/>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="vat_on_standard_rate_only_capital_goods_service" model="account.tax.report.line">
+ <field name="name">[4A] x 15/ (100 + 15)</field>
+ <field name="tag_name">[4A] x 15/ (100 + 15)</field>
+ <field name="code">VAT4A</field>
+ <field name="sequence" eval="5"/>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="zero_rate_exclude_goods_exported" model="account.tax.report.line">
+ <field name="name">[2] Zero Rate (excluding goods exported)</field>
+ <field name="tag_name">[2] Zero Rate (excluding goods exported)</field>
+ <field name="code">VAT2</field>
+ <field name="sequence" eval="6"/>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="zero_rate_only_goods_exported" model="account.tax.report.line">
+ <field name="name">[2A] Zero Rate (Only goods exported)</field>
+ <field name="tag_name">[2A] Zero Rate (Only goods exported)</field>
+ <field name="code">VAT2A</field>
+ <field name="sequence" eval="7"/>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="exempt_and_non_supplies" model="account.tax.report.line">
+ <field name="name">[3] Exempt and Non supplies</field>
+ <field name="tag_name">[3] Exempt and Non supplies</field>
+ <field name="code">VAT3</field>
+ <field name="sequence" eval="8"/>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="accomodation_exceeding_28_days" model="account.tax.report.line">
+ <field name="name">[5] Accomodation exceeding 28 days</field>
+ <field name="tag_name">[5] Accomodation exceeding 28 days</field>
+ <field name="code">VAT5</field>
+ <field name="sequence" eval="9"/>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="accomodation_exceeding_28_days_60_percent" model="account.tax.report.line">
+ <field name="name">[6] x 60%</field>
+ <field name="sequence" eval="11"/>
+ <field name="formula"> VAT5 * 60 / 100</field>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="accomodation_under_28_days" model="account.tax.report.line">
+ <field name="name">[7] Accomodation under 28 days</field>
+ <field name="tag_name">[7] Accomodation under 28 days</field>
+ <field name="code">VAT7</field>
+ <field name="sequence" eval="12"/>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="accomodation_28_days" model="account.tax.report.line">
+ <field name="name">[8] Total (6 + 7)</field>
+ <field name="sequence" eval="14"/>
+ <field name="formula">(VAT5 * 60 / 100) + VAT7</field>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="vat_on_accomodation_28_days" model="account.tax.report.line">
+ <field name="name">[9] x 15 / (100 ??) </field>
+ <field name="sequence" eval="15"/>
+ <field name="formula"> SEC6 * 60/100 + SEC7 </field>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="vat_on_accomodation_exceeding_28_days" model="account.tax.report.line">
+ <field name="name">VAT on Accomodation exceeding 28 days</field>
+ <field name="tag_name">VAT on Accomodation exceeding 28 days</field>
+ <field name="code">SEC6</field>
+ <field name="sequence" eval="1"/>
+ <field name="parent_id" ref='vat_on_accomodation_28_days'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="vat_on_accomodation_under_28_days" model="account.tax.report.line">
+ <field name="name">VAT on Accomodation under 28 days</field>
+ <field name="tag_name">VAT on Accomodation under 28 days</field>
+ <field name="code">SEC7</field>
+ <field name="sequence" eval="2"/>
+ <field name="parent_id" ref='vat_on_accomodation_28_days'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="vat_plus_base_of_second_hand_goods_export" model="account.tax.report.line">
+ <field name="name">[10] Change in use and export of second-hand goods</field>
+ <field name="sequence" eval="17"/>
+ <field name="formula"> VAT10a + VAT11</field>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="second_hand_goods_export" model="account.tax.report.line">
+ <field name="name">[10] Base Amount: Change in use and export of second-hand goods</field>
+ <field name="tag_name">[10] Change in use and export of second-hand goods</field>
+ <field name="code">VAT10a</field>
+ <field name="sequence" eval="1"/>
+ <field name="parent_id" ref='vat_plus_base_of_second_hand_goods_export'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="vat_on_second_hand_goods_export" model="account.tax.report.line">
+ <field name="name">[11] x 15 / (100 + 15)</field>
+ <field name="tag_name">[11] x 15 / (100 + 15)</field>
+ <field name="code">VAT11</field>
+ <field name="sequence" eval="18"/>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="other_imported_services" model="account.tax.report.line">
+ <field name="name">[12] Other and imported services</field>
+ <field name="tag_name">[12] Other and imported services</field>
+ <field name="code">VAT12</field>
+ <field name="sequence" eval="19"/>
+ <field name="parent_id" ref='total_output_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="capital_goods_services_supplied" model="account.tax.report.line">
+ <field name="name">[14] Capital Goods and/or services supplied to you</field>
+ <field name="tag_name">[14] Capital Goods and/or services supplied to you</field>
+ <field name="code">VAT14</field>
+ <field name="sequence" eval="21"/>
+ <field name="parent_id" ref='total_input_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="capital_goods_imported" model="account.tax.report.line">
+ <field name="name">[14A] Capital Goods imported by you</field>
+ <field name="tag_name">[14A] Capital Goods imported by you</field>
+ <field name="code">VAT14A</field>
+ <field name="sequence" eval="22"/>
+ <field name="parent_id" ref='total_input_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="other_goods_services_supplied" model="account.tax.report.line">
+ <field name="name">[15] Other goods and/or services supplied to you (not Capital Goods)</field>
+ <field name="tag_name">[15] Other goods and/or services supplied to you (not Capital Goods)</field>
+ <field name="code">VAT15</field>
+ <field name="sequence" eval="23"/>
+ <field name="parent_id" ref='total_input_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="other_goods_imported" model="account.tax.report.line">
+ <field name="name">[15A] Other goods imported by you (not Capital Goods)</field>
+ <field name="tag_name">[15A] Other goods imported by you (not Capital Goods)</field>
+ <field name="code">VAT15A</field>
+ <field name="sequence" eval="24"/>
+ <field name="parent_id" ref='total_input_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="change_in_use" model="account.tax.report.line">
+ <field name="name">[16] Change in Use</field>
+ <field name="tag_name">[16] Change in Use</field>
+ <field name="code">VAT16</field>
+ <field name="sequence" eval="25"/>
+ <field name="parent_id" ref='total_input_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="bad_debts" model="account.tax.report.line">
+ <field name="name">[17] Bad Debts</field>
+ <field name="tag_name">[17] Bad Debts</field>
+ <field name="code">VAT17</field>
+ <field name="sequence" eval="26"/>
+ <field name="parent_id" ref='total_input_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+ <record id="others" model="account.tax.report.line">
+ <field name="name">[18] Other</field>
+ <field name="tag_name">[18] Other</field>
+ <field name="code">VAT18</field>
+ <field name="sequence" eval="27"/>
+ <field name="parent_id" ref='total_input_tax'/>
+ <field name="country_id" ref="base.za"/>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_za/data/account_tax_template_data.xml b/addons/l10n_za/data/account_tax_template_data.xml
new file mode 100644
index 00000000000..60c86758117
--- /dev/null
+++ b/addons/l10n_za/data/account_tax_template_data.xml
@@ -0,0 +1,558 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <record id="ST1" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">sale</field>
+ <field name="name">Standard Rate</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('standard_rate_exclude_capital_goods_service')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ 'plus_report_line_ids': [ref('standard_rate_exclude_capital_goods_service'), ref('vat_on_standard_rate_exclude_capital_goods_service')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('standard_rate_exclude_capital_goods_service')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ 'minus_report_line_ids': [ref('standard_rate_exclude_capital_goods_service'), ref('vat_on_standard_rate_exclude_capital_goods_service')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="ST1A" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">sale</field>
+ <field name="name">Standard Rate (Capital Goods)</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('standard_rate_only_capital_goods_service')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ 'plus_report_line_ids': [ref('standard_rate_only_capital_goods_service'), ref('vat_on_standard_rate_only_capital_goods_service')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('standard_rate_only_capital_goods_service')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ 'minus_report_line_ids': [ref('standard_rate_only_capital_goods_service'), ref('vat_on_standard_rate_only_capital_goods_service')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="ST2" model="account.tax.template">
+ <field name="description">0%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">sale</field>
+ <field name="name">Zero Rate</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">0</field>
+ <field name="tax_group_id" ref="tax_group_0"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('zero_rate_exclude_goods_exported')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('zero_rate_exclude_goods_exported')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ }),
+ ]"/>
+ </record>
+
+ <record id="ST2A" model="account.tax.template">
+ <field name="description">0%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">sale</field>
+ <field name="name">Zero Rate Exports</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">0</field>
+ <field name="tax_group_id" ref="tax_group_0"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('zero_rate_only_goods_exported')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('zero_rate_only_goods_exported')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ }),
+ ]"/>
+ </record>
+
+ <record id="ST3" model="account.tax.template">
+ <field name="description">0%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">sale</field>
+ <field name="name">Exempt and Non-Supplies</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">0</field>
+ <field name="tax_group_id" ref="tax_group_0"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('exempt_and_non_supplies')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('exempt_and_non_supplies')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ }),
+ ]"/>
+ </record>
+
+ <record id="ST5" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">sale</field>
+ <field name="name">Accommodation (28+ days)</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('accomodation_exceeding_28_days')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ 'plus_report_line_ids': [ref('vat_on_accomodation_exceeding_28_days')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('accomodation_exceeding_28_days')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ 'minus_report_line_ids': [ref('vat_on_accomodation_exceeding_28_days')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="ST7" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">sale</field>
+ <field name="name">Accommodation (Under 28 days)</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('accomodation_under_28_days')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ 'plus_report_line_ids': [ref('vat_on_accomodation_under_28_days')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('accomodation_under_28_days')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ 'minus_report_line_ids': [ref('vat_on_accomodation_under_28_days')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="ST10" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">sale</field>
+ <field name="name">Export of Second-hand Goods/ Change in Use</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'plus_report_line_ids': [ref('second_hand_goods_export')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ 'plus_report_line_ids': [ref('vat_on_second_hand_goods_export')],
+ }),
+ ]"/>
+ <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'base',
+ 'minus_report_line_ids': [ref('second_hand_goods_export')],
+ }),
+ (0,0, {
+ 'factor_percent': 100,
+ 'repartition_type': 'tax',
+ 'account_id': ref('200060'),
+ 'minus_report_line_ids': [ref('vat_on_second_hand_goods_export')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="ST12" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">sale</field>
+ <field name="name">VAT Adjustments and Manual VAT</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <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('200060'),
+ 'plus_report_line_ids': [ref('other_imported_services')],
+ }),
+ ]"/>
+ <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('200060'),
+ 'minus_report_line_ids': [ref('other_imported_services')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="PT15" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="name">Standard Rate</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <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('100060'),
+ 'plus_report_line_ids': [ref('other_goods_services_supplied')],
+ }),
+ ]"/>
+ <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('100060'),
+ 'minus_report_line_ids': [ref('other_goods_services_supplied')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="PT14" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="name">Standard Rate (Capital Goods)</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <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('100060'),
+ 'plus_report_line_ids': [ref('capital_goods_services_supplied')],
+ }),
+ ]"/>
+ <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('100060'),
+ 'minus_report_line_ids': [ref('capital_goods_services_supplied')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="PT14A" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="name">Capital Goods Imported</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <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('100060'),
+ 'plus_report_line_ids': [ref('capital_goods_imported')],
+ }),
+ ]"/>
+ <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('100060'),
+ 'minus_report_line_ids': [ref('capital_goods_imported')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="PT15A" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="name">Goods and Services Imported</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <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('100060'),
+ 'plus_report_line_ids': [ref('other_goods_imported')],
+ }),
+ ]"/>
+ <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('100060'),
+ 'minus_report_line_ids': [ref('other_goods_imported')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="PT16" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="name">Change in Use</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <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('100060'),
+ 'plus_report_line_ids': [ref('change_in_use')],
+ }),
+ ]"/>
+ <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('100060'),
+ 'minus_report_line_ids': [ref('change_in_use')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="PT17" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="name">Bad Debts</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <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('100060'),
+ 'plus_report_line_ids': [ref('bad_debts')],
+ }),
+ ]"/>
+ <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('100060'),
+ 'minus_report_line_ids': [ref('bad_debts')],
+ }),
+ ]"/>
+ </record>
+
+ <record id="PT18" model="account.tax.template">
+ <field name="description">15%</field>
+ <field name="chart_template_id" ref="default_chart_template"/>
+ <field name="type_tax_use">purchase</field>
+ <field name="name">Other Adjustments</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">15</field>
+ <field name="tax_group_id" ref="tax_group_1"/>
+ <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('100060'),
+ 'plus_report_line_ids': [ref('others')],
+ }),
+ ]"/>
+ <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('100060'),
+ 'minus_report_line_ids': [ref('others')],
+ }),
+ ]"/>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_za/static/description/icon.png b/addons/l10n_za/static/description/icon.png
new file mode 100644
index 00000000000..1f82ab5459c
--- /dev/null
+++ b/addons/l10n_za/static/description/icon.png
Binary files differ
diff --git a/addons/lunch/controllers/main.py b/addons/lunch/controllers/main.py
index ac645c2edc4..2097f86659a 100644
--- a/addons/lunch/controllers/main.py
+++ b/addons/lunch/controllers/main.py
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import http, fields
+from odoo import _, http, fields
+from odoo.exceptions import AccessError
from odoo.http import request
from odoo.osv import expression
from odoo.tools import float_round, float_repr
@@ -10,6 +11,7 @@ from odoo.tools import float_round, float_repr
class LunchController(http.Controller):
@http.route('/lunch/infos', type='json', auth='user')
def infos(self, user_id=None):
+ self._check_user_impersonification(user_id)
user = request.env['res.users'].browse(user_id) if user_id else request.env.user
infos = self._make_infos(user, order=False)
@@ -35,6 +37,7 @@ class LunchController(http.Controller):
@http.route('/lunch/trash', type='json', auth='user')
def trash(self, user_id=None):
+ self._check_user_impersonification(user_id)
user = request.env['res.users'].browse(user_id) if user_id else request.env.user
lines = self._get_current_lines(user.id)
@@ -43,6 +46,7 @@ class LunchController(http.Controller):
@http.route('/lunch/pay', type='json', auth='user')
def pay(self, user_id=None):
+ self._check_user_impersonification(user_id)
user = request.env['res.users'].browse(user_id) if user_id else request.env.user
lines = self._get_current_lines(user.id)
@@ -60,13 +64,15 @@ class LunchController(http.Controller):
@http.route('/lunch/user_location_set', type='json', auth='user')
def set_user_location(self, location_id, user_id=None):
+ self._check_user_impersonification(user_id)
user = request.env['res.users'].browse(user_id) if user_id else request.env.user
- user.last_lunch_location_id = request.env['lunch.location'].browse(location_id)
+ user.sudo().last_lunch_location_id = request.env['lunch.location'].browse(location_id)
return True
@http.route('/lunch/user_location_get', type='json', auth='user')
def get_user_location(self, user_id=None):
+ self._check_user_impersonification(user_id)
user = request.env['res.users'].browse(user_id) if user_id else request.env.user
return user.last_lunch_location_id.id
@@ -87,7 +93,9 @@ class LunchController(http.Controller):
'currency': {'symbol': currency.symbol, 'position': currency.position},
})
- user_location = user.last_lunch_location_id if user.last_lunch_location_id else request.env['lunch.location'].search([], limit=1)
+ if not user.last_lunch_location_id:
+ user.last_lunch_location_id = request.env['lunch.location'].search([], limit=1)
+ user_location = user.last_lunch_location_id
alert_domain = expression.AND([
[('available_today', '=', True)],
@@ -101,6 +109,10 @@ class LunchController(http.Controller):
return res
+ def _check_user_impersonification(self, user_id=None):
+ if (user_id and request.env.uid != user_id and not request.env.user.has_group('lunch.group_lunch_manager')):
+ raise AccessError(_('You are trying to impersonate another user, but this can only be done by a lunch manager'))
+
def _get_current_lines(self, user_id):
return request.env['lunch.order'].search([('user_id', '=', user_id), ('date', '=', fields.Date.today()), ('state', '!=', 'cancelled')])
diff --git a/addons/lunch/models/lunch_order.py b/addons/lunch/models/lunch_order.py
index 52e4917e58d..fdf5a1a5559 100644
--- a/addons/lunch/models/lunch_order.py
+++ b/addons/lunch/models/lunch_order.py
@@ -42,6 +42,10 @@ class LunchOrder(models.Model):
display_toppings = fields.Text('Toppings', compute='_compute_display_toppings', store=True)
+ def init(self):
+ self._cr.execute("""CREATE INDEX IF NOT EXISTS lunch_order_user_product_date ON %s (user_id, product_id, date)"""
+ % self._table)
+
@api.depends('topping_ids_1', 'topping_ids_2', 'topping_ids_3', 'product_id', 'quantity')
def _compute_total_price(self):
for line in self:
@@ -69,8 +73,8 @@ class LunchOrder(models.Model):
'To add some money to your wallet, please contact your lunch manager.'))
def action_order(self):
- self._check_wallet()
self.write({'state': 'ordered'})
+ self._check_wallet()
def action_confirm(self):
self.write({'state': 'confirmed'})
diff --git a/addons/lunch/report/lunch_product_report.py b/addons/lunch/report/lunch_product_report.py
index 7c9fcfcb000..2615cb827ee 100644
--- a/addons/lunch/report/lunch_product_report.py
+++ b/addons/lunch/report/lunch_product_report.py
@@ -81,13 +81,12 @@ class LunchProductReport(models.Model):
users.id AS user_id,
fav.user_id IS NOT NULL AS is_favorite,
product.new_until >= current_date AS is_new,
- max(l_order.date) AS last_order_date
+ orders.last_order_date
FROM lunch_product product
- RIGHT JOIN res_users users ON users.company_id = product.company_id -- multi company
- LEFT JOIN lunch_product_favorite_user_rel fav ON fav.product_id = product.id AND fav.user_id = users.id
- JOIN res_groups_users_rel groups ON groups.uid = users.id -- only generate for internal users
- JOIN lunch_order l_order ON l_order.product_id = product.id AND l_order.user_id = users.id AND l_order.date <= current_date
+ INNER JOIN res_users users ON users.company_id = product.company_id -- multi company
+ INNER JOIN res_groups_users_rel groups ON groups.uid = users.id -- only generate for internal users
+ LEFT JOIN LATERAL (select max(date) AS last_order_date FROM lunch_order where user_id=users.id and product_id=product.id) AS orders ON TRUE
+ LEFT JOIN LATERAL (select user_id FROM lunch_product_favorite_user_rel where user_id=users.id and product_id=product.id) AS fav ON TRUE
WHERE users.active AND product.active AND groups.gid = %%s --only take into account active products and users
- GROUP BY product.id, product.name, product.category_id, product.description, product.price, product.supplier_id, product.company_id, product.active, users.id, is_favorite, is_new
);
""" % self._table, (self.env.ref('base.group_user').id,))
diff --git a/addons/lunch/static/src/js/lunch_kanban_controller.js b/addons/lunch/static/src/js/lunch_kanban_controller.js
index 80a39adae96..f1acf81b301 100644
--- a/addons/lunch/static/src/js/lunch_kanban_controller.js
+++ b/addons/lunch/static/src/js/lunch_kanban_controller.js
@@ -77,13 +77,14 @@ var LunchKanbanController = KanbanController.extend({
*/
_renderLunchKanbanWidget: function () {
var self = this;
- if (this.widget) {
- this.widget.destroy();
- }
+ var oldWidget = this.widget;
this.widgetData.wallet = parseFloat(this.widgetData.wallet).toFixed(2);
this.widget = new LunchKanbanWidget(this, _.extend(this.widgetData, {edit: this.editMode}));
return this.widget.appendTo(document.createDocumentFragment()).then(function () {
self.$('.o_lunch_kanban').prepend(self.widget.$el);
+ if (oldWidget) {
+ oldWidget.destroy();
+ }
});
},
_showPaymentDialog: function (title) {
diff --git a/addons/lunch/static/tests/lunch_kanban_tests.js b/addons/lunch/static/tests/lunch_kanban_tests.js
index 32cac593dcc..8bc4879f9e5 100644
--- a/addons/lunch/static/tests/lunch_kanban_tests.js
+++ b/addons/lunch/static/tests/lunch_kanban_tests.js
@@ -128,6 +128,53 @@ QUnit.module('LunchKanbanView', {
kanban.destroy();
});
+ QUnit.test('no flickering at reload', async function (assert) {
+ assert.expect(2);
+
+ const self = this;
+ let infosProm = Promise.resolve();
+ const kanban = await createLunchKanbanView({
+ View: LunchKanbanView,
+ model: 'product',
+ data: this.data,
+ arch: `
+ <kanban>
+ <templates>
+ <t t-name="kanban-box">
+ <div><field name="name"/></div>
+ </t>
+ </templates>
+ </kanban>
+ `,
+ mockRPC: function (route, args) {
+ if (route === '/lunch/user_location_get') {
+ return Promise.resolve(self.data['lunch.location'].records[0].id);
+ }
+ if (route === '/lunch/infos') {
+ return Promise.resolve(self.regularInfos);
+ }
+ var result = this._super.apply(this, arguments);
+ if (args.method === 'xmlid_to_res_id') {
+ // delay the rendering of the lunch widget
+ return infosProm.then(_.constant(result));
+ }
+ return result;
+ },
+ });
+
+ infosProm = testUtils.makeTestPromise();
+ kanban.reload();
+
+ assert.strictEqual(kanban.$('.o_lunch_widget').length, 1,
+ "old widget should still be present");
+
+ await infosProm.resolve();
+
+ assert.strictEqual(kanban.$('.o_lunch_widget').length, 1);
+
+ kanban.destroy();
+ });
+
QUnit.module('LunchKanbanWidget', function () {
QUnit.test('empty cart', async function (assert) {
@@ -814,7 +861,6 @@ QUnit.module('LunchKanbanView', {
kanban.destroy();
});
-
});
});
diff --git a/addons/lunch/views/res_config_settings.xml b/addons/lunch/views/res_config_settings.xml
index bd99a708b4c..fc73ba99151 100644
--- a/addons/lunch/views/res_config_settings.xml
+++ b/addons/lunch/views/res_config_settings.xml
@@ -14,6 +14,7 @@
title="None">
<div class="o_setting_right_pane">
<span class="o_form_label">Lunch Overdraft</span>
+ <span class="fa fa-lg fa-building-o" title="Values set here are company-specific." aria-label="Values set here are company-specific." groups="base.group_multi_company" role="img"/>
<div class="text-muted">
Maximum overdraft that your employees can reach
</div>
diff --git a/addons/lunch/wizard/lunch_line_wizard.py b/addons/lunch/wizard/lunch_line_wizard.py
index e933c1e6008..4a704215443 100644
--- a/addons/lunch/wizard/lunch_line_wizard.py
+++ b/addons/lunch/wizard/lunch_line_wizard.py
@@ -86,7 +86,7 @@ class LunchOrderWizard(models.TransientModel):
sum((wizard.topping_ids_1 | wizard.topping_ids_2 | wizard.topping_ids_3).mapped('price')))
def _get_matching_lines(self):
- domain = [('product_id', '=', self.product_id.id), ('date', '=', fields.Date.today()), ('note', '=', self._get_note())]
+ domain = [('user_id', '=', self.user_id.id), ('product_id', '=', self.product_id.id), ('date', '=', fields.Date.today()), ('note', '=', self._get_note())]
lines = self.env['lunch.order'].search(domain)
return lines.filtered(lambda line: (line.topping_ids_1 | line.topping_ids_2 | line.topping_ids_3) == self.topping_ids_1)
diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py
index 3dee568ca10..1f04378e601 100644
--- a/addons/mail/models/mail_activity.py
+++ b/addons/mail/models/mail_activity.py
@@ -149,7 +149,7 @@ class MailActivity(models.Model):
('today', 'Today'),
('planned', 'Planned')], 'State',
compute='_compute_state')
- recommended_activity_type_id = fields.Many2one('mail.activity.type', string="Recommended Activity Type", readonly=True)
+ recommended_activity_type_id = fields.Many2one('mail.activity.type', string="Recommended Activity Type")
previous_activity_type_id = fields.Many2one('mail.activity.type', string='Previous Activity Type', readonly=True)
has_recommended_activities = fields.Boolean(
'Next activities available',
@@ -302,7 +302,7 @@ class MailActivity(models.Model):
target_record = self.env[activity.res_model].browse(activity.res_id)
if hasattr(target_record, 'company_id') and (
target_record.company_id != target_user.company_id and (
- len(target_user.company_ids) > 1)):
+ len(target_user.sudo().company_ids) > 1)):
return # in that case we skip the check, assuming it would fail because of the company
model.browse(activity.res_id).check_access_rule('read')
except exceptions.AccessError:
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 2fab423412b..84607013996 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -4,6 +4,7 @@
import logging
import re
+from binascii import Error as binascii_error
from operator import itemgetter
from email.utils import formataddr
from openerp.http import request
@@ -998,7 +999,7 @@ class Message(models.Model):
for record in self:
model = model or record.model
res_id = res_id or record.res_id
- if 'message_ids' in self.env[model]:
+ if issubclass(self.pool[model], self.pool['mail.thread']):
self.env[model].invalidate_cache(fnames=[
'message_ids',
'message_unread',
@@ -1032,15 +1033,20 @@ class Message(models.Model):
key = match.group(2)
if not data_to_url.get(key):
name = match.group(4) if match.group(4) else 'image%s' % len(data_to_url)
- attachment = Attachments.create({
- 'name': name,
- 'datas': match.group(2),
- 'res_model': values.get('model'),
- 'res_id': values.get('res_id'),
- })
- attachment.generate_access_token()
- values['attachment_ids'].append((4, attachment.id))
- data_to_url[key] = ['/web/image/%s?access_token=%s' % (attachment.id, attachment.access_token), name]
+ try:
+ attachment = Attachments.create({
+ 'name': name,
+ 'datas': match.group(2),
+ 'res_model': values.get('model'),
+ 'res_id': values.get('res_id'),
+ })
+ except binascii_error:
+ _logger.warning("Impossible to create an attachment out of badly formated base64 embedded image. Image has been removed.")
+ return match.group(3) # group(3) is the url ending single/double quote matched by the regexp
+ else:
+ attachment.generate_access_token()
+ values['attachment_ids'].append((4, attachment.id))
+ data_to_url[key] = ['/web/image/%s?access_token=%s' % (attachment.id, attachment.access_token), name]
return '%s%s alt="%s"' % (data_to_url[key][0], match.group(3), data_to_url[key][1])
values['body'] = _image_dataurl.sub(base64_to_boundary, tools.ustr(values['body']))
diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js
index f81bcb2d0e2..6bb11d0a090 100644
--- a/addons/mail/static/src/js/chatter.js
+++ b/addons/mail/static/src/js/chatter.js
@@ -139,7 +139,7 @@ var Chatter = Widget.extend({
if (this.fields.activity) {
this.fields.activity.$el.detach();
}
- if (this.fields.thread) {
+ if (this.fields.thread && this.fields.thread.$el ) {
this.fields.thread.$el.detach();
}
@@ -404,7 +404,7 @@ var Chatter = Widget.extend({
self.fields.followers.$el.insertBefore(self.$('.o_chatter_button_attachment'));
}
}
- if (self.fields.thread) {
+ if (self.fields.thread && self.fields.thread.$el) {
self.fields.thread.$el.appendTo(self.$el);
}
}).then(always).guardedCatch(always);
diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js
index 73c4ece9025..b5b2e068cbf 100644
--- a/addons/mail/static/src/js/models/messages/message.js
+++ b/addons/mail/static/src/js/models/messages/message.js
@@ -579,7 +579,8 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
* @return {boolean}
*/
_isOdoobotAuthor: function () {
- return this._serverAuthorID === this.call('mail_service', 'getOdoobotID');
+ return this._serverAuthorID &&
+ this._serverAuthorID[0] === this.call('mail_service', 'getOdoobotID')[0];
},
/**
* State whether the message is transient or not
diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js
index 27a7183ec88..dedeacca013 100644
--- a/addons/mail/static/src/js/services/mail_manager.js
+++ b/addons/mail/static/src/js/services/mail_manager.js
@@ -46,7 +46,7 @@ var PREVIEW_MSG_MAX_SIZE = 350; // optimal for native english speakers
var MailManager = AbstractService.extend({
dependencies: ['ajax', 'bus_service', 'local_storage'],
- _ODOOBOT_ID: "ODOOBOT", // default authorID for transient messages
+ _ODOOBOT_ID: ["ODOOBOT", "ODOOBOT"], // authorID for transient messages
/**
* @override
@@ -258,7 +258,7 @@ var MailManager = AbstractService.extend({
/**
* Get the OdooBot ID, which is the default authorID for transient messages
*
- * @returns {string}
+ * @returns {Array<string>}
*/
getOdoobotID: function () {
return this._ODOOBOT_ID;
diff --git a/addons/mail/static/src/js/services/mail_notification_manager.js b/addons/mail/static/src/js/services/mail_notification_manager.js
index 75754f375d8..caf0c3c4e12 100644
--- a/addons/mail/static/src/js/services/mail_notification_manager.js
+++ b/addons/mail/static/src/js/services/mail_notification_manager.js
@@ -471,12 +471,11 @@ MailManager.include({
*
* @private
* @param {Object} data
- * @param {string} data.author_id
*/
_handlePartnerTransientMessageNotification: function (data) {
var lastMessage = _.last(this._messages);
data.id = (lastMessage ? lastMessage.getID() : 0) + 0.01;
- data.author_id = data.author_id || this.getOdoobotID();
+ data.author_id = this.getOdoobotID();
this.addMessage(data);
},
/**
diff --git a/addons/mail/static/src/js/services/mail_status_manager.js b/addons/mail/static/src/js/services/mail_status_manager.js
index 4a2df2af94f..7891461c9af 100644
--- a/addons/mail/static/src/js/services/mail_status_manager.js
+++ b/addons/mail/static/src/js/services/mail_status_manager.js
@@ -30,6 +30,9 @@ MailManager.include({
getImStatus: function (data) {
var self = this;
var partnerID = data.partnerID;
+ if (partnerID === this.getOdoobotID()[0]) {
+ return 'bot';
+ }
if (!this._imStatus[partnerID]) {
// Add to list to call it in next bus update or _fetchMissingImStatus
this._imStatus[partnerID] = undefined;
@@ -84,9 +87,10 @@ MailManager.include({
return Promise.resolve();
}
return this._rpc({
- model: 'res.partner',
- method: 'read',
- args: [partnerIDs, ['id', 'im_status']],
+ route: '/longpolling/im_status',
+ params: { partner_ids: partnerIDs },
+ }, {
+ shadow: true,
}).then( function (results) {
self.updateImStatus(results);
});
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js
index 91b9a28661e..29df0733930 100644
--- a/addons/mail/static/tests/chatter_tests.js
+++ b/addons/mail/static/tests/chatter_tests.js
@@ -1034,6 +1034,8 @@ QUnit.test('chatter: receive notif when document is open', async function (asser
assert.strictEqual(thread.getUnreadCounter(), 1,
"document thread should now have one unread message");
+ // do not destroy form too early. wait rendering to avoid race condition in service call.
+ await testUtils.nextTick();
form.destroy();
});
diff --git a/addons/mail/static/tests/mail_status_manager_tests.js b/addons/mail/static/tests/mail_status_manager_tests.js
index be769e8d0b0..f21cefdec9e 100644
--- a/addons/mail/static/tests/mail_status_manager_tests.js
+++ b/addons/mail/static/tests/mail_status_manager_tests.js
@@ -39,7 +39,7 @@ QUnit.test('simple set im_status', function (assert) {
});
QUnit.test('multi get_im_status', async function (assert) {
- assert.expect(9);
+ assert.expect(8);
var readCount = 0;
var parent = testUtils.createParent({
//data: this.data,
@@ -48,9 +48,8 @@ QUnit.test('multi get_im_status', async function (assert) {
if (route === '/mail/init_messaging') {
return this._super.apply(this, arguments);
}
- if (args.method === 'read' && args.model === 'res.partner') {
- assert.deepEqual(args.args[0], [2,3]);
- assert.deepEqual(args.args[1], ['id', 'im_status']);
+ if (route === '/longpolling/im_status') {
+ assert.deepEqual(args.partner_ids, [2,3]);
readCount++;
return Promise.resolve([
{id: 2, im_status: 'away'},
@@ -85,7 +84,7 @@ QUnit.test('multi get_im_status', async function (assert) {
});
QUnit.test('update loop', async function (assert) {
- assert.expect(12);
+ assert.expect(10);
var readCount = 0;
var parent = testUtils.createParent({
services: this.services,
@@ -93,9 +92,8 @@ QUnit.test('update loop', async function (assert) {
if (route === '/mail/init_messaging') {
return this._super.apply(this, arguments);
}
- if (args.method === 'read' && args.model === 'res.partner') {
- assert.deepEqual(args.args[0], [1, 2]);
- assert.deepEqual(args.args[1], ['id', 'im_status']);
+ if (route === '/longpolling/im_status') {
+ assert.deepEqual(args.partner_ids, [1, 2]);
readCount++;
return Promise.resolve([
{"id": 1, "im_status": "online"},
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index ba65f787257..def7b58033b 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -410,6 +410,7 @@ class MailComposer(models.TransientModel):
values = self.generate_email_for_composer(template_id, [res_id])[res_id]
# transform attachments into attachment_ids; not attached to the document because this will
# be done further in the posting process, allowing to clean database if email not send
+ attachment_ids = []
Attachment = self.env['ir.attachment']
for attach_fname, attach_datas in values.pop('attachments', []):
data_attach = {
@@ -419,7 +420,9 @@ class MailComposer(models.TransientModel):
'res_id': 0,
'type': 'binary', # override default_type from context, possibly meant for another model!
}
- values.setdefault('attachment_ids', list()).append(Attachment.create(data_attach).id)
+ attachment_ids.append(Attachment.create(data_attach).id)
+ if values.get('attachment_ids', []) or attachment_ids:
+ values['attachment_ids'] = [(5,)] + values.get('attachment_ids', []) + attachment_ids
else:
default_values = self.with_context(default_composition_mode=composition_mode, default_model=model, default_res_id=res_id).default_get(['composition_mode', 'model', 'res_id', 'parent_id', 'partner_ids', 'subject', 'body', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'])
values = dict((key, default_values[key]) for key in ['subject', 'body', 'partner_ids', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'] if key in default_values)
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index b4cdf4109fc..243fc33af0a 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -460,7 +460,7 @@ class MassMailing(models.Model):
return res
active = fields.Boolean(default=True)
- subject = fields.Char('Subject', help='Subject of emails to send', required=True)
+ subject = fields.Char('Subject', help='Subject of emails to send', required=True, translate=True)
email_from = fields.Char(string='From', required=True,
default=lambda self: self.env['mail.message']._get_default_from())
sent_date = fields.Datetime(string='Sent Date', oldname='date', copy=False)
diff --git a/addons/mass_mailing/wizard/test_mailing.py b/addons/mass_mailing/wizard/test_mailing.py
index 8389ce269ef..7f2459d4a07 100644
--- a/addons/mass_mailing/wizard/test_mailing.py
+++ b/addons/mass_mailing/wizard/test_mailing.py
@@ -27,7 +27,7 @@ class TestMassMailing(models.TransientModel):
'email_from': mailing.email_from,
'reply_to': mailing.reply_to,
'email_to': test_mail,
- 'subject': mailing.name,
+ 'subject': mailing.subject,
'body_html': mass_mail_layout.render({'body': body}, engine='ir.qweb', minimal_qcontext=True),
'notification': True,
'mailing_id': mailing.id,
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index 1705297358a..f8b6544a61b 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -492,6 +492,8 @@ class MrpProduction(models.Model):
}
def _generate_finished_moves(self):
+ if self.product_id in self.bom_id.byproduct_ids.mapped('product_id'):
+ raise UserError(_("You cannot have %s as the finished product and in the Byproducts") % self.product_id.name)
moves_values = [self._get_finished_move_value(self.product_id.id, self.product_qty, self.product_uom_id.id)]
for byproduct in self.bom_id.byproduct_ids:
product_uom_factor = self.product_uom_id._compute_quantity(self.product_qty, self.bom_id.product_uom_id)
@@ -835,6 +837,8 @@ class MrpProduction(models.Model):
order._cal_price(moves_to_do)
moves_to_finish = order.move_finished_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
moves_to_finish = moves_to_finish._action_done()
+ order.workorder_ids.mapped('raw_workorder_line_ids').unlink()
+ order.workorder_ids.mapped('finished_workorder_line_ids').unlink()
order.action_assign()
consume_move_lines = moves_to_do.mapped('move_line_ids')
for moveline in moves_to_finish.mapped('move_line_ids'):
diff --git a/addons/mrp/models/stock_warehouse.py b/addons/mrp/models/stock_warehouse.py
index 8b6e0676bb6..e7fee7f0def 100644
--- a/addons/mrp/models/stock_warehouse.py
+++ b/addons/mrp/models/stock_warehouse.py
@@ -249,7 +249,7 @@ class StockWarehouse(models.Model):
@api.multi
def _get_all_routes(self):
- routes = super(StockWarehouse, self).get_all_routes_for_wh()
+ routes = super(StockWarehouse, self)._get_all_routes()
routes |= self.filtered(lambda self: self.manufacture_to_resupply and self.manufacture_pull_id and self.manufacture_pull_id.route_id).mapped('manufacture_pull_id').mapped('route_id')
return routes
diff --git a/addons/mrp/tests/test_workorder_operation.py b/addons/mrp/tests/test_workorder_operation.py
index b214fd3ad76..f1953f0d2c0 100644
--- a/addons/mrp/tests/test_workorder_operation.py
+++ b/addons/mrp/tests/test_workorder_operation.py
@@ -863,6 +863,29 @@ class TestWorkOrderProcess(TestMrpCommon):
self.assertAlmostEqual(workorder.date_planned_start, date_start, delta=timedelta(seconds=1), msg="Workorder should be planned tomorrow.")
self.assertAlmostEqual(workorder.date_planned_finished, date_start + timedelta(hours=1), delta=timedelta(seconds=1), msg="Workorder should be done one hour later.")
+ def test_change_production_1(self):
+ """Change the quantity to produce on the MO while workorders are already planned."""
+ dining_table = self.env.ref("mrp.product_product_computer_desk")
+ dining_table.tracking = 'lot'
+ production_table_form = Form(self.env['mrp.production'])
+ production_table_form.product_id = dining_table
+ production_table_form.bom_id = self.env.ref("mrp.mrp_bom_desk")
+ production_table_form.product_qty = 1.0
+ production_table_form.product_uom_id = dining_table.uom_id
+ production_table = production_table_form.save()
+ production_table.action_confirm()
+
+ # Create work order
+ production_table.button_plan()
+
+ context = {'active_id': production_table.id, 'active_model': 'mrp.production'}
+ change_qty_form = Form(self.env['change.production.qty'].with_context(context))
+ change_qty_form.product_qty = 2.00
+ change_qty = change_qty_form.save()
+ change_qty.change_prod_qty()
+
+ self.assertEqual(production_table.workorder_ids[0].qty_producing, 2, "Quantity to produce not updated")
+
def test_planning_0(self):
""" Test alternative conditions
1. alternative relation is directionnal
diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py
index 2757d9ec0bb..f4bb7746c37 100644
--- a/addons/mrp/wizard/change_production_qty.py
+++ b/addons/mrp/wizard/change_production_qty.py
@@ -106,7 +106,7 @@ class ChangeProductionQty(models.TransientModel):
(moves_finished + moves_raw).write({'workorder_id': wo.id})
if wo.state not in ('done', 'cancel'):
line_values = wo._update_workorder_lines()
- self._workorder_line_ids().create(line_values['to_create'])
+ wo._workorder_line_ids().create(line_values['to_create'])
if line_values['to_delete']:
line_values['to_delete'].unlink()
for line, vals in line_values['to_update'].items():
diff --git a/addons/payment/controllers/portal.py b/addons/payment/controllers/portal.py
index a4da9dbbb34..90c0065b643 100644
--- a/addons/payment/controllers/portal.py
+++ b/addons/payment/controllers/portal.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
+import psycopg2
from odoo import http, _
from odoo.http import request
@@ -40,7 +41,7 @@ class PaymentProcessing(http.Controller):
# I prefer to let the controller chose when to access to payment.transaction using sudo
return request.session.get("__payment_tx_ids__", [])
- @http.route(['/payment/process'], type="http", auth="public", website=True)
+ @http.route(['/payment/process'], type="http", auth="public", website=True, sitemap=False)
def payment_status_page(self, **kwargs):
# When the customer is redirect to this website page,
# we retrieve the payment transaction list from his session
@@ -93,11 +94,15 @@ class PaymentProcessing(http.Controller):
tx_to_process = payment_transaction_ids.filtered(lambda x: x.state == 'done' and x.is_processed is False)
try:
tx_to_process._post_process_after_done()
+ except psycopg2.OperationalError as e:
+ request.env.cr.rollback()
+ result['success'] = False
+ result['error'] = "tx_process_retry"
except Exception as e:
request.env.cr.rollback()
result['success'] = False
result['error'] = str(e)
- _logger.error("Error while processing transaction(s) %s, exception \"%s\"", tx_to_process.ids, str(e))
+ _logger.exception("Error while processing transaction(s) %s, exception \"%s\"", tx_to_process.ids, str(e))
return result
@@ -122,7 +127,7 @@ class WebsitePayment(http.Controller):
}
return request.render("payment.pay_methods", values)
- @http.route(['/website_payment/pay'], type='http', auth='public', website=True)
+ @http.route(['/website_payment/pay'], type='http', auth='public', website=True, sitemap=False)
def pay(self, reference='', order_id=None, amount=False, currency_id=None, acquirer_id=None, **kw):
env = request.env
user = env.user.sudo()
@@ -262,7 +267,7 @@ class WebsitePayment(http.Controller):
except Exception as e:
return request.redirect('/payment/process')
- @http.route(['/website_payment/confirm'], type='http', auth='public', website=True)
+ @http.route(['/website_payment/confirm'], type='http', auth='public', website=True, sitemap=False)
def confirm(self, **kw):
tx_id = int(kw.get('tx_id', 0))
if tx_id:
diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py
index 21320cc04b3..f79252c622f 100644
--- a/addons/payment/models/payment_acquirer.py
+++ b/addons/payment/models/payment_acquirer.py
@@ -917,7 +917,7 @@ class PaymentTransaction(models.Model):
# custom create
custom_method_name = '%s_create' % acquirer.provider
- if hasattr(acquirer, custom_method_name):
+ if hasattr(self, custom_method_name):
values.update(getattr(self, custom_method_name)(values))
if not values.get('reference'):
diff --git a/addons/payment/static/src/js/payment_processing.js b/addons/payment/static/src/js/payment_processing.js
index 4bad7fcb4ef..ec1028188ec 100644
--- a/addons/payment/static/src/js/payment_processing.js
+++ b/addons/payment/static/src/js/payment_processing.js
@@ -44,6 +44,8 @@ odoo.define('payment.processing', function (require) {
}
else {
switch(data.error) {
+ case "tx_process_retry":
+ break;
case "no_tx_found":
self.displayContent("payment.no_tx_found", {});
break;
@@ -53,14 +55,8 @@ odoo.define('payment.processing', function (require) {
}
}
self.startPolling();
- }).then(function() {
- if ($.blockUI) {
- $.unblockUI();
- }
+
}).guardedCatch(function() {
- if ($.blockUI) {
- $.unblockUI();
- }
self.displayContent("payment.rpc_error", {});
self.startPolling();
});
@@ -110,6 +106,7 @@ odoo.define('payment.processing', function (require) {
},
displayContent: function (xmlid, render_values) {
var html = core.qweb.render(xmlid, render_values);
+ $.unblockUI();
this.$el.find('.o_payment_processing_content').html(html);
},
displayLoading: function () {
diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py
index 79d0a7589c5..922d9df1bf1 100644
--- a/addons/payment_authorize/models/authorize_request.py
+++ b/addons/payment_authorize/models/authorize_request.py
@@ -226,6 +226,12 @@ class AuthorizeAPI():
etree.SubElement(customer, "email").text = partner.email or ''
response = self._authorize_request(root)
res = dict()
+ if not response.find('customerProfileId'):
+ _logger.warning(
+ 'Unable to create customer payment profile, data missing from transaction. Transaction_id: %s - Partner_id: %s'
+ % (transaction_id, partner)
+ )
+ return res
res['profile_id'] = response.find('customerProfileId').text
res['payment_profile_id'] = response.find('customerPaymentProfileIdList/numericString').text
root_profile = self._base_tree('getCustomerPaymentProfileRequest')
diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py
index 5a6ed0bf8e1..946f031484d 100644
--- a/addons/payment_authorize/models/payment.py
+++ b/addons/payment_authorize/models/payment.py
@@ -228,17 +228,18 @@ class TxAuthorize(models.Model):
(self.type == 'form_save' or self.acquirer_id.save_token == 'always'):
transaction = AuthorizeAPI(self.acquirer_id)
res = transaction.create_customer_profile_from_tx(self.partner_id, self.acquirer_reference)
- token_id = self.env['payment.token'].create({
- 'authorize_profile': res.get('profile_id'),
- 'name': res.get('name'),
- 'acquirer_ref': res.get('payment_profile_id'),
- 'acquirer_id': self.acquirer_id.id,
- 'partner_id': self.partner_id.id,
- })
- self.payment_token_id = token_id
-
- if self.payment_token_id:
- self.payment_token_id.verified = True
+ if res:
+ token_id = self.env['payment.token'].create({
+ 'authorize_profile': res.get('profile_id'),
+ 'name': res.get('name'),
+ 'acquirer_ref': res.get('payment_profile_id'),
+ 'acquirer_id': self.acquirer_id.id,
+ 'partner_id': self.partner_id.id,
+ })
+ self.payment_token_id = token_id
+
+ if self.payment_token_id:
+ self.payment_token_id.verified = True
return True
elif status_code == self._authorize_pending_tx_status:
self.write({'acquirer_reference': data.get('x_trans_id')})
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 1362497f657..0fdbe554f19 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -327,6 +327,7 @@ class PosOrder(models.Model):
'partner_id': partner_id,
'order_id': order.id,
'tax_repartition_line_id': tax['tax_repartition_line_id'],
+ 'tax_base_amount': tax['base'],
'tag_ids': tax['tag_ids'],
}
if currency_id != cur_company:
@@ -904,7 +905,7 @@ class PosOrder(models.Model):
# a serialnumber always has a quantity of 1 product, a lot number takes the full quantity of the order line
qty = 1.0
if stock_production_lot.product_id.tracking == 'lot':
- qty = pos_pack_lot.pos_order_line_id.qty
+ qty = abs(pos_pack_lot.pos_order_line_id.qty)
qty_done += qty
pack_lots.append({'lot_id': stock_production_lot.id, 'qty': qty})
else:
diff --git a/addons/point_of_sale/static/src/js/gui.js b/addons/point_of_sale/static/src/js/gui.js
index 5c946406133..845233af3c5 100644
--- a/addons/point_of_sale/static/src/js/gui.js
+++ b/addons/point_of_sale/static/src/js/gui.js
@@ -404,7 +404,7 @@ var Gui = core.Class.extend({
}
} else if (input === '-') {
if (options.firstinput) {
- newbuf = '-';
+ newbuf = '-0';
} else if ( newbuf[0] === '-' ) {
newbuf = newbuf.substring(1,newbuf.length);
} else {
@@ -419,6 +419,9 @@ var Gui = core.Class.extend({
newbuf += input;
}
}
+ if (newbuf === "-") {
+ newbuf = "";
+ }
// End of input buffer at 12 characters.
if (newbuf.length > buffer.length && newbuf.length > 12) {
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index bb1c74b9718..124042b3345 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -1447,7 +1447,7 @@ exports.Orderline = Backbone.Model.extend({
var lots_required = 1;
if (this.product.tracking == 'serial') {
- lots_required = this.quantity;
+ lots_required = Math.abs(this.quantity);
}
return lots_required;
@@ -1995,8 +1995,11 @@ var PacklotlineCollection = Backbone.Collection.extend({
set_quantity_by_lot: function() {
if (this.order_line.product.tracking == 'serial') {
- var valid_lots = this.get_valid_lots();
- this.order_line.set_quantity(valid_lots.length);
+ var valid_lots_quantity = this.get_valid_lots().length;
+ if (this.order_line.quantity < 0){
+ valid_lots_quantity = -valid_lots_quantity;
+ }
+ this.order_line.set_quantity(valid_lots_quantity);
}
}
});
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 94b1d4e31f7..75eeb66b355 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -1002,7 +1002,7 @@
<line t-foreach='taxes' t-as='taxe'>
<left><t t-esc='taxe.name' /></left>
<right>
- <t t-esc='widget.format_currency_no_symbol(taxe.total)'/>
+ <t t-esc='widget.format_currency_no_symbol(taxe.tax_amount)'/>
</right>
</line>
diff --git a/addons/point_of_sale/views/point_of_sale_dashboard.xml b/addons/point_of_sale/views/point_of_sale_dashboard.xml
index cd627937472..c8f1ebca3d4 100644
--- a/addons/point_of_sale/views/point_of_sale_dashboard.xml
+++ b/addons/point_of_sale/views/point_of_sale_dashboard.xml
@@ -47,7 +47,6 @@
<field name="arch" type="xml">
<kanban create="false" class="o_kanban_dashboard o_pos_kanban">
<field name="name"/>
- <field name="session_ids"/>
<field name="current_session_id"/>
<field name="current_session_state"/>
<field name="last_session_closing_date"/>
diff --git a/addons/portal/controllers/mail.py b/addons/portal/controllers/mail.py
index 39e83703a3c..7376e17521b 100644
--- a/addons/portal/controllers/mail.py
+++ b/addons/portal/controllers/mail.py
@@ -139,7 +139,7 @@ class PortalChatter(http.Controller):
raise Forbidden()
# Non-employee see only messages with not internal subtype (aka, no internal logs)
if not request.env['res.users'].has_group('base.group_user'):
- domain = expression.AND([['&', '&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False), ('website_published', '=', True)], domain])
+ domain = expression.AND([Message._non_employee_message_domain(), domain])
Message = request.env['mail.message'].sudo()
return {
'messages': Message.search(domain, limit=limit, offset=offset).portal_message_format(),
diff --git a/addons/portal/models/mail_message.py b/addons/portal/models/mail_message.py
index 4a5d1f804d9..afe97f1b8ca 100644
--- a/addons/portal/models/mail_message.py
+++ b/addons/portal/models/mail_message.py
@@ -21,3 +21,7 @@ class MailMessage(models.Model):
message_tree = dict((m.id, m) for m in self.sudo())
self._message_read_dict_postprocess(message_values, message_tree)
return message_values
+
+ @api.model
+ def _non_employee_message_domain(self):
+ return ['&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)]
diff --git a/addons/pos_sale/report/sale_report.py b/addons/pos_sale/report/sale_report.py
index 13f16a69c03..561d3698095 100644
--- a/addons/pos_sale/report/sale_report.py
+++ b/addons/pos_sale/report/sale_report.py
@@ -20,11 +20,6 @@ class SaleReport(models.Model):
('invoiced', 'Invoiced')], string='Status', readonly=True)
def _query(self, with_clause='', fields={}, groupby='', from_clause=''):
- with_clause += '''pol_tax AS (SELECT line_tax.pos_order_line_id AS pol_id,sum(tax.amount/100) AS amount
- FROM account_tax_pos_order_line_rel line_tax
- JOIN account_tax tax ON line_tax.account_tax_id = tax.id
- GROUP BY line_tax.pos_order_line_id)'''
-
res = super(SaleReport, self)._query(with_clause, fields, groupby, from_clause)
select_ = '''
@@ -35,10 +30,10 @@ class SaleReport(models.Model):
sum(l.qty * u.factor) AS qty_delivered,
CASE WHEN pos.state = 'invoiced' THEN sum(qty) ELSE 0 END AS qty_invoiced,
CASE WHEN pos.state != 'invoiced' THEN sum(qty) ELSE 0 END AS qty_to_invoice,
- (COALESCE(COALESCE((l.price_unit * l.qty * u.factor * ((1 - COALESCE(l.discount / 100, 0)) )) + (l.price_unit * l.qty * u.factor * ((1 - COALESCE(l.discount / 100, 0)) ) * pol_tax.amount), (l.price_unit * l.qty * u.factor * ((1 - COALESCE(l.discount / 100, 0)) ))), 1.0)) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS price_total,
- (COALESCE((l.qty * u.factor * l.price_unit * ((1 - COALESCE(l.discount / 100, 0)) )), 1.0)) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS price_subtotal,
- (CASE WHEN pos.state != 'invoiced' THEN ((l.price_unit * l.qty * u.factor * ((1 - COALESCE(l.discount / 100, 0)))) + (l.price_unit * l.qty * u.factor * (1 - COALESCE(l.discount / 100, 0)) * pol_tax.amount)) ELSE 0 END) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS amount_to_invoice,
- (CASE WHEN pos.state = 'invoiced' THEN ((l.price_unit * l.qty * u.factor * ((1 - COALESCE(l.discount / 100, 0)) )) + (l.price_unit * l.qty * u.factor * (1 - COALESCE(l.discount / 100, 0)) * pol_tax.amount)) ELSE 0 END) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS amount_invoiced,
+ SUM(l.price_subtotal_incl) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS price_total,
+ SUM(l.price_subtotal) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS price_subtotal,
+ (CASE WHEN pos.state != 'invoiced' THEN SUM(l.price_subtotal_incl) ELSE 0 END) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS amount_to_invoice,
+ (CASE WHEN pos.state = 'invoiced' THEN SUM(l.price_subtotal_incl) ELSE 0 END) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS amount_invoiced,
count(*) AS nbr,
pos.name AS name,
pos.date_order AS date,
@@ -79,7 +74,6 @@ class SaleReport(models.Model):
pos_order_line l
join pos_order pos on (l.order_id=pos.id)
left join res_partner partner ON (pos.partner_id = partner.id OR pos.partner_id = NULL)
- left join pol_tax pol_tax on pol_tax.pol_id=l.id
left join product_product p on (l.product_id=p.id)
left join product_template t on (p.product_tmpl_id=t.id)
LEFT JOIN uom_uom u ON (u.id=t.uom_id)
@@ -107,7 +101,6 @@ class SaleReport(models.Model):
partner.country_id,
partner.industry_id,
partner.commercial_partner_id,
- pol_tax.amount,
u.factor,
config.crm_team_id
'''
diff --git a/addons/product/models/product.py b/addons/product/models/product.py
index a7cc6779f34..a8df285d257 100644
--- a/addons/product/models/product.py
+++ b/addons/product/models/product.py
@@ -196,6 +196,7 @@ class ProductProduct(models.Model):
('active', '=', True),
]) <= 1
):
+ record.image_raw_original = False
record.product_tmpl_id.image_original = record.image_original
else:
record.image_raw_original = record.image_original
@@ -391,6 +392,16 @@ class ProductProduct(models.Model):
product._set_standard_price(vals.get('standard_price') or 0.0)
# `_get_variant_id_for_combination` depends on existing variants
self.clear_caches()
+ self.env['product.template'].invalidate_cache(
+ fnames=[
+ 'valid_archived_variant_ids',
+ 'valid_existing_variant_ids',
+ 'product_variant_ids',
+ 'product_variant_id',
+ 'product_variant_count'
+ ],
+ ids=products.mapped('product_tmpl_id').ids
+ )
return products
@api.multi
diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py
index dbbb3d488f9..d09975cc4da 100644
--- a/addons/product/models/product_template.py
+++ b/addons/product/models/product_template.py
@@ -135,8 +135,10 @@ class ProductTemplate(models.Model):
compute="_compute_valid_attributes", string='Valid Product Attribute Values Without No Variant Attributes', help="Technical compute")
valid_product_attribute_wnva_ids = fields.Many2many('product.attribute',
compute="_compute_valid_attributes", string='Valid Product Attributes Without No Variant Attributes', help="Technical compute")
+ # valid_archived_variant_ids deprecated
valid_archived_variant_ids = fields.Many2many('product.product',
- compute="_compute_valid_attributes", string='Valid Archived Variants', help="Technical compute")
+ compute="_compute_valid_archived_variant_ids", string='Valid Archived Variants', help="Technical compute")
+ # valid_existing_variant_ids deprecated
valid_existing_variant_ids = fields.Many2many('product.product',
compute="_compute_valid_existing_variant_ids", string='Valid Existing Variants', help="Technical compute")
@@ -606,8 +608,6 @@ class ProductTemplate(models.Model):
For what is considered an archived variant, see `_has_valid_attributes`.
"""
- archived_variants = self.env['product.product'].search([('product_tmpl_id', 'in', self.ids), ('active', '=', False)])
-
# prefetch
self.mapped('attribute_line_ids.value_ids.id')
self.mapped('attribute_line_ids.attribute_id.create_variant')
@@ -622,6 +622,12 @@ class ProductTemplate(models.Model):
record.valid_product_attribute_ids = record.valid_product_template_attribute_line_ids.mapped('attribute_id')
record.valid_product_attribute_wnva_ids = record.valid_product_template_attribute_line_wnva_ids.mapped('attribute_id')
+ @api.multi
+ def _compute_valid_archived_variant_ids(self):
+ """This compute is done outside of `_compute_valid_attributes` because
+ it is often not needed, and it can be very bad on performance."""
+ archived_variants = self.env['product.product'].search([('product_tmpl_id', 'in', self.ids), ('active', '=', False)])
+ for record in self:
valid_value_ids = record.valid_product_attribute_value_wnva_ids
valid_attribute_ids = record.valid_product_attribute_wnva_ids
@@ -699,14 +705,12 @@ class ProductTemplate(models.Model):
:return: dict of exclusions
- exclusions: from this product itself
+ - parent_combination: ids of the given parent_combination
- parent_exclusions: from the parent_combination
- - archived_combinations: variants that are archived
- - existing_combinations: variants that are existing (as opposed to
- deleted, because deleted need to be considered impossible if
- there are no dynamic attributes).
- - has_dynamic_attributes: whether there is a dynamic attribute
- - no_variant_product_template_attribute_value_ids: values that are
- no_variant
+ - archived_combinations: deprecated
+ - existing_combinations: deprecated
+ - has_dynamic_attributes: deprecated
+ - no_variant_product_template_attribute_value_ids: deprecated
- parent_product_name: the name of the parent product if any, used in the interface
to explain why some combinations are not available.
(e.g: Not available with Customizable Desk (Legs: Steel))
@@ -715,13 +719,15 @@ class ProductTemplate(models.Model):
(e.g: Not available with Color: Black)
"""
self.ensure_one()
+ parent_combination = parent_combination or self.env['product.template.attribute.value']
return {
'exclusions': self._complete_inverse_exclusions(self._get_own_attribute_exclusions()),
'parent_exclusions': self._get_parent_attribute_exclusions(parent_combination),
- 'archived_combinations': self._get_archived_combinations(),
+ 'parent_combination': parent_combination.ids,
+ 'archived_combinations': [],
'has_dynamic_attributes': self.has_dynamic_attributes(),
- 'existing_combinations': self._get_existing_combinations(),
- 'no_variant_product_template_attribute_value_ids': self._get_no_variant_product_template_attribute_values(),
+ 'existing_combinations': [],
+ 'no_variant_product_template_attribute_value_ids': [],
'parent_product_name': parent_name,
'mapped_attribute_names': self._get_mapped_attribute_names(parent_combination),
}
@@ -789,29 +795,21 @@ class ProductTemplate(models.Model):
@api.multi
def _get_archived_combinations(self):
- """Get archived combinations.
-
- Array, each element is an array with ids of an archived combination.
- """
+ """Deprecated"""
self.ensure_one()
return [archived_variant.product_template_attribute_value_ids.ids
for archived_variant in self.valid_archived_variant_ids]
@api.multi
def _get_existing_combinations(self):
- """Get existing combinations.
-
- Needed because when not using dynamic attributes, the combination is
- not ok if it doesn't exist (= if the variant has been deleted).
-
- Array, each element is an array with ids of an existing combination.
- """
+ """Deprecated"""
self.ensure_one()
return [variant.product_template_attribute_value_ids.ids
for variant in self.valid_existing_variant_ids]
@api.multi
def _get_no_variant_product_template_attribute_values(self):
+ """Deprecated"""
self.ensure_one()
product_template_attribute_values = self._get_valid_product_template_attribute_lines().mapped('product_template_value_ids')
return product_template_attribute_values.filtered(
@@ -876,9 +874,16 @@ class ProductTemplate(models.Model):
# combination has different values than the ones configured on the template
return False
- if not self.has_dynamic_attributes() and not self._get_variant_for_combination(combination):
- # the variant has been deleted
- return False
+ variant = self._get_variant_for_combination(combination)
+
+ if self.has_dynamic_attributes():
+ if variant and not variant.active:
+ # dynamic and the variant has been archived
+ return False
+ else:
+ if not variant or not variant.active:
+ # not dynamic, the variant has been archived or deleted
+ return False
exclusions = self._get_own_attribute_exclusions()
if exclusions:
@@ -898,11 +903,6 @@ class ProductTemplate(models.Model):
if exclusion in combination.ids:
return False
- filtered_combination = combination._without_no_variant_attributes()
- archived_combinations = self._get_archived_combinations()
- if archived_combinations and filtered_combination.ids in archived_combinations:
- return False
-
return True
@api.multi
@@ -936,7 +936,7 @@ class ProductTemplate(models.Model):
for pav in attribute_values:
domain = expression.AND([[('attribute_value_ids', 'in', pav.id)], domain])
- res = self.env['product.product'].with_context(active_test=False).search(domain)
+ res = self.env['product.product'].with_context(active_test=False).search(domain, order='active DESC')
# The domain above is checking for the `product.attribute.value`, but we
# need to make sure it's the same `product.template.attribute.value`.
diff --git a/addons/product/tests/test_product_attribute_value_config.py b/addons/product/tests/test_product_attribute_value_config.py
index e966adbdbdc..497f6fbeba3 100644
--- a/addons/product/tests/test_product_attribute_value_config.py
+++ b/addons/product/tests/test_product_attribute_value_config.py
@@ -312,6 +312,21 @@ class TestProductAttributeValueConfig(TestProductAttributeValueSetup):
variant.unlink()
self.assertFalse(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_8 + computer_hdd_1))
+ # CASE: if multiple variants exist for the same combination and at least
+ # one of them is not archived, the combination is possible
+ values = self.ssd_256 + self.ram_8 + self.hdd_1
+ self.env['product.product'].create({
+ 'product_tmpl_id': self.computer.id,
+ 'attribute_value_ids': [(6, 0, values.ids)],
+ 'active': False,
+ })
+ self.env['product.product'].create({
+ 'product_tmpl_id': self.computer.id,
+ 'attribute_value_ids': [(6, 0, values.ids)],
+ 'active': True,
+ })
+ self.assertTrue(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_8 + computer_hdd_1))
+
def test_get_first_possible_combination(self):
computer_ssd_256 = self._get_product_template_attribute_value(self.ssd_256)
computer_ssd_512 = self._get_product_template_attribute_value(self.ssd_512)
diff --git a/addons/product/tests/test_variants.py b/addons/product/tests/test_variants.py
index 9381868b623..ed7f1f777c5 100644
--- a/addons/product/tests/test_variants.py
+++ b/addons/product/tests/test_variants.py
@@ -1,6 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import base64
+import io
+from PIL import Image
+
from . import common
from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase
@@ -461,3 +465,72 @@ class TestVariantsManyAttributes(common.TestAttributesCommon):
self.assertEqual(len(toto.attribute_line_ids.mapped('attribute_id')), 10)
self.assertEqual(len(toto.attribute_line_ids.mapped('value_ids')), 100)
self.assertEqual(len(toto.product_variant_ids), 0)
+
+
+class TestVariantsImages(common.TestProductCommon):
+
+ def setUp(self):
+ res = super(TestVariantsImages, self).setUp()
+
+ self.colors = {'red': '#FF0000', 'green': '#00FF00', 'blue': '#0000FF'}
+ self.images = {}
+
+ product_attribute = self.env['product.attribute'].create({'name': 'Color'})
+
+ self.template = self.env['product.template'].create({
+ 'name': 'template',
+ })
+
+ for color in self.colors:
+ attr = self.env['product.attribute.value'].create({
+ 'name': color,
+ 'attribute_id': product_attribute.id,
+ })
+
+ f = io.BytesIO()
+ Image.new('RGB', (800, 500), self.colors[color]).save(f, 'PNG')
+ f.seek(0)
+ self.images.update({color: base64.b64encode(f.read())})
+
+ self.env['product.product'].create({
+ 'image_raw_original': self.images[color],
+ 'attribute_value_ids': [(6, 0, [attr.id])],
+ 'product_tmpl_id': self.template.id,
+ })
+ # the first one has no image, no color
+ self.variants = self.template.product_variant_ids.sorted('id')
+
+ return res
+
+ def test_variant_images(self):
+ """Check that on variant, the image used is the image_raw_original if set,
+ and defaults to the template image otherwise.
+ """
+ f = io.BytesIO()
+ Image.new('RGB', (800, 500), '#000000').save(f, 'PNG')
+ f.seek(0)
+ image_black = base64.b64encode(f.read())
+
+ images = self.variants.mapped('image_original')
+ self.assertEqual(len(set(images)), 4)
+
+ variant_no_image = self.variants[0]
+ self.assertFalse(variant_no_image.image)
+ self.template.image = image_black
+
+ # the first has no image variant, all the others do
+ self.assertFalse(variant_no_image.image_raw_original)
+ self.assertTrue(all(images[1:]))
+
+ # template image is the same as this one, since it has no image variant
+ self.assertEqual(variant_no_image.image, self.template.image)
+ # having changed the template image should not have changed these
+ self.assertEqual(images[1:], self.variants.mapped('image')[1:])
+
+ def test_update_images_with_archived_variants(self):
+ """Update images after variants have been archived"""
+ self.variants[1:].write({'active': False})
+ self.variants[0].image = self.images['red']
+ self.assertEqual(self.template.image, self.images['red'])
+ self.assertEqual(self.variants[0].image_raw_original, False)
+ self.assertEqual(self.variants[0].image, self.images['red'])
diff --git a/addons/product_email_template/models/product.py b/addons/product_email_template/models/product.py
index 9ef844e8f46..e043717e6e8 100644
--- a/addons/product_email_template/models/product.py
+++ b/addons/product_email_template/models/product.py
@@ -11,4 +11,6 @@ class ProductTemplate(models.Model):
_inherit = "product.template"
email_template_id = fields.Many2one('mail.template', string='Product Email Template',
- help='Send a product-specific email once the invoice is paid.')
+ help='When validating an invoice, an email will be sent to the customer '
+ 'based on this template. The customer will receive an email for each '
+ 'product linked to an email template.')
diff --git a/addons/product_email_template/views/product_views.xml b/addons/product_email_template/views/product_views.xml
index 393ab6d3ec9..9afb300f64f 100644
--- a/addons/product_email_template/views/product_views.xml
+++ b/addons/product_email_template/views/product_views.xml
@@ -10,7 +10,7 @@
<attribute name="invisible">0</attribute>
</xpath>
<xpath expr="//group[@name='email_template_and_project']" position="inside">
- <field name="email_template_id" string="Email Template" help="Send a product-specific email once the invoice is paid"
+ <field name="email_template_id" string="Email Template" help="Send a product-specific email once the invoice is validated"
domain="[('model','=','product.template')]"
context="{
'form_view_ref':'product_email_template.email_template_form_simplified',
diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py
index 150c5c02cb9..74811a27436 100644
--- a/addons/purchase/models/account_invoice.py
+++ b/addons/purchase/models/account_invoice.py
@@ -103,11 +103,6 @@ class AccountInvoice(models.Model):
#as there's no invoice line yet, we keep the currency of the PO
self.currency_id = self.purchase_id.currency_id
- vendor_ref = self.purchase_id.partner_ref
- if vendor_ref:
- self.reference = ", ".join([self.reference, vendor_ref]) if (
- self.reference and vendor_ref not in self.reference) else vendor_ref
-
new_lines = self.env['account.invoice.line']
for line in self.purchase_id.order_line - self.invoice_line_ids.mapped('purchase_line_id'):
data = self._prepare_invoice_line_from_po_line(line)
diff --git a/addons/purchase_stock/models/account_invoice.py b/addons/purchase_stock/models/account_invoice.py
index c6d138352cf..a98e0dda26f 100644
--- a/addons/purchase_stock/models/account_invoice.py
+++ b/addons/purchase_stock/models/account_invoice.py
@@ -96,27 +96,27 @@ class AccountInvoice(models.Model):
interim_account_price = valuation_price_unit * line['quantity']
invoice_cur_prec = inv.currency_id.decimal_places
- if float_compare(valuation_price_unit, i_line.price_unit, precision_digits=invoice_cur_prec) != 0 and float_compare(line['price_unit'], i_line.price_unit, precision_digits=invoice_cur_prec) == 0:
-
- # price with discount and without tax included
- price_unit = i_line.price_unit * (1 - (i_line.discount or 0.0) / 100.0)
- tax_ids = []
- if line['tax_ids']:
- #line['tax_ids'] is -2many commands list for account.move.line
- tax_ids = [tax_data[id] for tax_data in self.env['account.move.line'].resolve_2many_commands('tax_ids', line['tax_ids'], ['id'])]
- taxes = self.env['account.tax'].browse(tax_ids)
- price_unit = taxes.compute_all(price_unit, currency=inv.currency_id, quantity=1.0, is_refund=inv.type in ('in_refund', 'out_refund'))['total_excluded']
- for tax in taxes:
- tax_ids.append((4, tax.id, None))
- for child in tax.children_tax_ids:
- if child.type_tax_use != 'none':
- tax_ids.append((4, child.id, None))
-
+ # price with discount and without tax included
+ price_unit = i_line.price_unit * (1 - (i_line.discount or 0.0) / 100.0)
+ tax_ids = []
+ if line['tax_ids']:
+ #line['tax_ids'] is -2many commands list for account.move.line
+ tax_ids = [tax_data[id] for tax_data in self.env['account.move.line'].resolve_2many_commands('tax_ids', line['tax_ids'], ['id'])]
+ taxes = self.env['account.tax'].browse(tax_ids)
+ price_unit = taxes.compute_all(price_unit, currency=inv.currency_id, quantity=1.0, is_refund=inv.type in ('in_refund', 'out_refund'))['total_excluded']
+
+ for tax in taxes:
+ tax_ids.append((4, tax.id, None))
+ for child in tax.children_tax_ids:
+ if child.type_tax_use != 'none':
+ tax_ids.append((4, child.id, None))
+
+ if float_compare(valuation_price_unit, price_unit, precision_digits=invoice_cur_prec) != 0 and float_compare(line['price_unit'], i_line.price_unit, precision_digits=invoice_cur_prec) == 0:
price_before = line.get('price', 0.0)
price_unit_val_dif = price_unit - valuation_price_unit
price_val_dif = price_before - interim_account_price
- if inv.currency_id.compare_amounts(i_line.price_unit, valuation_price_unit) != 0 and acc:
+ if inv.currency_id.compare_amounts(price_unit, valuation_price_unit) != 0 and acc:
# If the unit prices have not changed and we have a
# valuation difference, it means this difference is due to exchange rates,
# so we don't create anything, the exchange rate entries will
@@ -134,9 +134,12 @@ class AccountInvoice(models.Model):
'tax_ids': tax_ids,
}
# We update the original line accordingly
- line['price_unit'] = line['price_unit'] - diff_line['price_unit']
- line['price'] = inv.currency_id.round(line['quantity'] * line['price_unit'])
- line['price_unit'] = inv.currency_id.round(line['price_unit'])
+ # line['price_unit'] doesn't contain the discount, so use price_unit
+ # instead. It could make sense to include the discount in line['price_unit'],
+ # but that doesn't seem a good idea in stable since it is done in
+ # "invoice_line_move_line_get" of "account.invoice".
+ line['price_unit'] = inv.currency_id.round(price_unit - diff_line['price_unit'])
+ line['price'] = inv.currency_id.round(line['price'] - diff_line['price'])
diff_res.append(diff_line)
return diff_res
return []
diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py
index 83405a9dad9..c652940b2e9 100644
--- a/addons/purchase_stock/models/purchase.py
+++ b/addons/purchase_stock/models/purchase.py
@@ -352,7 +352,9 @@ class PurchaseOrderLine(models.Model):
for move in self.move_ids.filtered(lambda x: x.state != 'cancel' and not x.location_dest_id.usage == "supplier"):
qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP')
template = {
- 'name': self.name or '',
+ # truncate to 2000 to avoid triggering index limit error
+ # TODO: remove index in master?
+ 'name': (self.name or '')[:2000],
'product_id': self.product_id.id,
'product_uom': self.product_uom.id,
'date': self.order_id.date_order,
diff --git a/addons/purchase_stock/models/stock.py b/addons/purchase_stock/models/stock.py
index 61e285db200..259035e4bd6 100644
--- a/addons/purchase_stock/models/stock.py
+++ b/addons/purchase_stock/models/stock.py
@@ -132,7 +132,7 @@ class StockWarehouse(models.Model):
@api.multi
def _get_all_routes(self):
- routes = super(StockWarehouse, self).get_all_routes_for_wh()
+ routes = super(StockWarehouse, self)._get_all_routes()
routes |= self.filtered(lambda self: self.buy_to_resupply and self.buy_pull_id and self.buy_pull_id.route_id).mapped('buy_pull_id').mapped('route_id')
return routes
diff --git a/addons/purchase_stock/tests/test_stockvaluation.py b/addons/purchase_stock/tests/test_stockvaluation.py
index 69208fd9080..8fcbabed1ad 100644
--- a/addons/purchase_stock/tests/test_stockvaluation.py
+++ b/addons/purchase_stock/tests/test_stockvaluation.py
@@ -1165,3 +1165,151 @@ class TestStockValuationWithCOA(AccountingTestCase):
exchange_stock_line = reconciled_lines.filtered(lambda l: l.journal_id == exhange_diff_journal)
self.assertEqual(exchange_stock_line.amount_currency, 0.00)
self.assertAlmostEqual(exchange_stock_line.balance, 119.32)
+
+ def test_anglosaxon_valuation_price_total_diff_discount(self):
+ """
+ PO: price unit: 110
+ Inv: price unit: 100
+ discount: 10
+ """
+ self.env.company.anglo_saxon_accounting = True
+ self.product1.categ_id.property_cost_method = 'fifo'
+ self.product1.categ_id.property_valuation = 'real_time'
+ self.product1.product_tmpl_id.invoice_policy = 'delivery'
+ self.product1.property_account_creditor_price_difference = self.price_diff_account
+
+ # Create PO
+ po_form = Form(self.env['purchase.order'])
+ po_form.partner_id = self.partner_id
+ with po_form.order_line.new() as po_line:
+ po_line.product_id = self.product1
+ po_line.product_qty = 1
+ po_line.price_unit = 110.0
+ order = po_form.save()
+ order.button_confirm()
+
+ # Receive the goods
+ receipt = order.picking_ids[0]
+ receipt.move_lines.quantity_done = 1
+ receipt.button_validate()
+
+ # Create an invoice with a different price and a discount
+ invoice = self.env['account.invoice'].create({
+ 'partner_id': order.partner_id.id,
+ 'purchase_id': order.id,
+ 'account_id': order.partner_id.property_account_payable_id.id,
+ 'type': 'in_invoice',
+ })
+ invoice.purchase_order_change()
+ invoice.invoice_line_ids[0].price_unit = 100.0
+ invoice.invoice_line_ids[0].discount = 10.0
+ invoice.action_invoice_open()
+
+ # Check what was posted in the price difference account
+ price_diff_aml = self.env['account.move.line'].search([('account_id','=', self.price_diff_account.id)])
+ self.assertEquals(len(price_diff_aml), 1, "Only one line should have been generated in the price difference account.")
+ self.assertAlmostEquals(price_diff_aml.credit, 20, "Price difference should be equal to 20 (110-90)")
+
+ # Check what was posted in stock input account
+ input_aml = self.env['account.move.line'].search([('account_id','=', self.stock_input_account.id)])
+ self.assertEquals(len(input_aml), 2, "Only two lines should have been generated in stock input account: one when receiving the product, one when making the invoice.")
+ self.assertAlmostEquals(sum(input_aml.mapped('debit')), 110, "Total debit value on stock input account should be equal to the original PO price of the product.")
+ self.assertAlmostEquals(sum(input_aml.mapped('credit')), 110, "Total credit value on stock input account should be equal to the original PO price of the product.")
+
+ def test_anglosaxon_valuation_discount(self):
+ """
+ PO: price unit: 100
+ Inv: price unit: 100
+ discount: 10
+ """
+ self.env.company.anglo_saxon_accounting = True
+ self.product1.categ_id.property_cost_method = 'fifo'
+ self.product1.categ_id.property_valuation = 'real_time'
+ self.product1.product_tmpl_id.invoice_policy = 'delivery'
+ self.product1.property_account_creditor_price_difference = self.price_diff_account
+
+ # Create PO
+ po_form = Form(self.env['purchase.order'])
+ po_form.partner_id = self.partner_id
+ with po_form.order_line.new() as po_line:
+ po_line.product_id = self.product1
+ po_line.product_qty = 1
+ po_line.price_unit = 100.0
+ order = po_form.save()
+ order.button_confirm()
+
+ # Receive the goods
+ receipt = order.picking_ids[0]
+ receipt.move_lines.quantity_done = 1
+ receipt.button_validate()
+
+ # Create an invoice with a different price and a discount
+ invoice = self.env['account.invoice'].create({
+ 'partner_id': order.partner_id.id,
+ 'purchase_id': order.id,
+ 'account_id': order.partner_id.property_account_payable_id.id,
+ 'type': 'in_invoice',
+ })
+ invoice.purchase_order_change()
+ invoice.invoice_line_ids[0].discount = 10.0
+ invoice.action_invoice_open()
+
+ # Check what was posted in the price difference account
+ price_diff_aml = self.env['account.move.line'].search([('account_id','=', self.price_diff_account.id)])
+ self.assertEquals(len(price_diff_aml), 1, "Only one line should have been generated in the price difference account.")
+ self.assertAlmostEquals(price_diff_aml.credit, 10, "Price difference should be equal to 20 (110-90)")
+
+ # Check what was posted in stock input account
+ input_aml = self.env['account.move.line'].search([('account_id','=', self.stock_input_account.id)])
+ self.assertEquals(len(input_aml), 2, "Only two lines should have been generated in stock input account: one when receiving the product, one when making the invoice.")
+ self.assertAlmostEquals(sum(input_aml.mapped('debit')), 100, "Total debit value on stock input account should be equal to the original PO price of the product.")
+ self.assertAlmostEquals(sum(input_aml.mapped('credit')), 100, "Total credit value on stock input account should be equal to the original PO price of the product.")
+
+ def test_anglosaxon_valuation_price_unit_diff_discount(self):
+ """
+ PO: price unit: 90
+ Inv: price unit: 100
+ discount: 10
+ """
+ self.env.company.anglo_saxon_accounting = True
+ self.product1.categ_id.property_cost_method = 'fifo'
+ self.product1.categ_id.property_valuation = 'real_time'
+ self.product1.product_tmpl_id.invoice_policy = 'delivery'
+ self.product1.property_account_creditor_price_difference = self.price_diff_account
+
+ # Create PO
+ po_form = Form(self.env['purchase.order'])
+ po_form.partner_id = self.partner_id
+ with po_form.order_line.new() as po_line:
+ po_line.product_id = self.product1
+ po_line.product_qty = 1
+ po_line.price_unit = 90.0
+ order = po_form.save()
+ order.button_confirm()
+
+ # Receive the goods
+ receipt = order.picking_ids[0]
+ receipt.move_lines.quantity_done = 1
+ receipt.button_validate()
+
+ # Create an invoice with a different price and a discount
+ invoice = self.env['account.invoice'].create({
+ 'partner_id': order.partner_id.id,
+ 'purchase_id': order.id,
+ 'account_id': order.partner_id.property_account_payable_id.id,
+ 'type': 'in_invoice',
+ })
+ invoice.purchase_order_change()
+ invoice.invoice_line_ids[0].price_unit = 100.0
+ invoice.invoice_line_ids[0].discount = 10.0
+ invoice.action_invoice_open()
+
+ # Check if something was posted in the price difference account
+ price_diff_aml = self.env['account.move.line'].search([('account_id','=', self.price_diff_account.id)])
+ self.assertEquals(len(price_diff_aml), 0, "No line should have been generated in the price difference account.")
+
+ # Check what was posted in stock input account
+ input_aml = self.env['account.move.line'].search([('account_id','=', self.stock_input_account.id)])
+ self.assertEquals(len(input_aml), 2, "Only two lines should have been generated in stock input account: one when receiving the product, one when making the invoice.")
+ self.assertAlmostEquals(sum(input_aml.mapped('debit')), 90, "Total debit value on stock input account should be equal to the original PO price of the product.")
+ self.assertAlmostEquals(sum(input_aml.mapped('credit')), 90, "Total credit value on stock input account should be equal to the original PO price of the product.")
diff --git a/addons/sale/controllers/variant.py b/addons/sale/controllers/variant.py
index 7a4ee7ea7c3..3afdc19bd8d 100644
--- a/addons/sale/controllers/variant.py
+++ b/addons/sale/controllers/variant.py
@@ -13,7 +13,18 @@ class VariantController(http.Controller):
ProductTemplate = request.env['product.template']
if 'context' in kw:
ProductTemplate = ProductTemplate.with_context(**kw.get('context'))
- return ProductTemplate.browse(int(product_template_id))._get_combination_info(combination, int(product_id or 0), int(add_qty or 1), pricelist)
+ product_template = ProductTemplate.browse(int(product_template_id))
+ res = product_template._get_combination_info(combination, int(product_id or 0), int(add_qty or 1), pricelist)
+ if 'parent_combination' in kw:
+ parent_combination = request.env['product.template.attribute.value'].browse(kw.get('parent_combination'))
+ if not combination.exists() and product_id:
+ product = request.env['product.product'].browse(int(product_id))
+ if product.exists():
+ combination = product.product_template_attribute_value_ids
+ res.update({
+ 'is_combination_possible': product_template._is_combination_possible(combination=combination, parent_combination=parent_combination),
+ })
+ return res
@http.route(['/sale/create_product_variant'], type='json', auth="user", methods=['POST'])
def create_product_variant(self, product_template_id, product_template_attribute_value_ids, **kwargs):
diff --git a/addons/sale/models/account_invoice.py b/addons/sale/models/account_invoice.py
index f439341bd29..235378d03e3 100644
--- a/addons/sale/models/account_invoice.py
+++ b/addons/sale/models/account_invoice.py
@@ -44,7 +44,7 @@ class AccountInvoice(models.Model):
inv_type = self.type or self.env.context.get('type', 'out_invoice')
if inv_type == 'out_invoice':
company = self.company_id or self.env.company
- self.comment = company.with_context(lang=self.partner_id.lang).invoice_terms
+ self.comment = company.with_context(lang=self.partner_id.lang).invoice_terms or (self._origin.company_id == company and self.comment)
@api.multi
def _prepare_refund(self, invoice, date_invoice=None, date=None, description=None, journal_id=None):
diff --git a/addons/sale/models/payment.py b/addons/sale/models/payment.py
index 8580878a45d..e89f4b78a2a 100644
--- a/addons/sale/models/payment.py
+++ b/addons/sale/models/payment.py
@@ -99,11 +99,11 @@ class PaymentTransaction(models.Model):
if self.env['ir.config_parameter'].sudo().get_param('sale.automatic_invoice'):
default_template = self.env['ir.config_parameter'].sudo().get_param('sale.default_email_template')
if default_template:
- ctx_company = {'company_id': self.acquirer_id.company_id.id,
- 'force_company': self.acquirer_id.company_id.id,
- 'mark_invoice_as_sent': True,
- }
for trans in self.filtered(lambda t: t.sale_order_ids):
+ ctx_company = {'company_id': trans.acquirer_id.company_id.id,
+ 'force_company': trans.acquirer_id.company_id.id,
+ 'mark_invoice_as_sent': True,
+ }
trans = trans.with_context(ctx_company)
for invoice in trans.invoice_ids:
invoice.message_post_with_template(int(default_template), email_layout_xmlid="mail.mail_notification_paynow")
@@ -112,9 +112,9 @@ class PaymentTransaction(models.Model):
@api.multi
def _invoice_sale_orders(self):
if self.env['ir.config_parameter'].sudo().get_param('sale.automatic_invoice'):
- ctx_company = {'company_id': self.acquirer_id.company_id.id,
- 'force_company': self.acquirer_id.company_id.id}
for trans in self.filtered(lambda t: t.sale_order_ids):
+ ctx_company = {'company_id': trans.acquirer_id.company_id.id,
+ 'force_company': trans.acquirer_id.company_id.id}
trans = trans.with_context(**ctx_company)
trans.sale_order_ids._force_lines_to_invoice_policy_order()
invoices = trans.sale_order_ids._create_invoices()
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 63d219df0c8..fc6197723bc 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -1270,7 +1270,7 @@ class SaleOrderLine(models.Model):
result.setdefault(so_line_id, 0.0)
uom = product_uom_map.get(item['product_uom_id'][0])
if so_line.product_uom.category_id == uom.category_id:
- qty = uom._compute_quantity(item['unit_amount'], so_line.product_uom)
+ qty = uom._compute_quantity(item['unit_amount'], so_line.product_uom, rounding_method='HALF-UP')
else:
qty = item['unit_amount']
result[so_line_id] += qty
@@ -1624,7 +1624,7 @@ class SaleOrderLine(models.Model):
new_list_price, self.order_id.pricelist_id.currency_id,
self.order_id.company_id, self.order_id.date_order or fields.Date.today())
discount = (new_list_price - price) / new_list_price * 100
- if discount > 0:
+ if (discount > 0 and new_list_price > 0) or (discount < 0 and new_list_price < 0):
self.discount = discount
def _is_delivery(self):
diff --git a/addons/sale/report/report_all_channels_sales.py b/addons/sale/report/report_all_channels_sales.py
index 1638c087d05..473ab1f1362 100644
--- a/addons/sale/report/report_all_channels_sales.py
+++ b/addons/sale/report/report_all_channels_sales.py
@@ -52,7 +52,7 @@ class PosSaleReport(models.Model):
LEFT JOIN product_pricelist pp ON (so.pricelist_id = pp.id)
LEFT JOIN uom_uom u on (u.id=sol.product_uom)
LEFT JOIN uom_uom u2 on (u2.id=pt.uom_id)
- WHERE so.state != 'cancel'
+ WHERE so.state in ('sale','done')
"""
return so_str
diff --git a/addons/sale/static/src/js/variant_mixin.js b/addons/sale/static/src/js/variant_mixin.js
index 19954256a75..907ebf603c8 100644
--- a/addons/sale/static/src/js/variant_mixin.js
+++ b/addons/sale/static/src/js/variant_mixin.js
@@ -1,6 +1,7 @@
odoo.define('sale.VariantMixin', function (require) {
'use strict';
+var concurrency = require('web.concurrency');
var core = require('web.core');
var utils = require('web.utils');
var ajax = require('web.ajax');
@@ -8,9 +9,8 @@ var _t = core._t;
var VariantMixin = {
events: {
- 'click .css_attribute_color input': '_onChangeColorAttribute',
+ 'change .css_attribute_color input': '_onChangeColorAttribute',
'change .main_product:not(.in_cart) input.js_quantity': 'onChangeAddQuantity',
- 'click button.js_add_cart_json': 'onClickAddCartJSON',
'change [data-attribute_exclusions]': 'onChangeVariant'
},
@@ -36,10 +36,25 @@ var VariantMixin = {
* @param {$.Element} [params.$container] force the used container
*/
onChangeVariant: function (ev, params) {
+ var $parent = $(ev.target).closest('.js_product');
+ if (!$parent.data('uniqueId')) {
+ $parent.data('uniqueId', _.uniqueId());
+ }
+ this._throttledGetCombinationInfo($parent.data('uniqueId'))(ev, params);
+ },
+ /**
+ * @see onChangeVariant
+ *
+ * @private
+ * @param {Event} ev
+ * @param {$.Element} [params.$container] force the used container
+ * @returns {Deferred}
+ */
+ _getCombinationInfo: function (ev, params) {
var self = this;
if ($(ev.target).hasClass('variant_custom_value')) {
- return;
+ return Promise.resolve();
}
var $component;
@@ -57,16 +72,21 @@ var VariantMixin = {
var qty = $component.find('input[name="add_qty"]').val();
var $parent = $(ev.target).closest('.js_product');
+
var combination = this.getSelectedVariantValues($parent);
+ var parentCombination = $parent.find('ul[data-attribute_exclusions]').data('attribute_exclusions').parent_combination;
+
+ var productTemplateId = parseInt($parent.find('.product_template_id').val());
self._checkExclusions($parent, combination);
- ajax.jsonRpc(this._getUri('/sale/get_combination_info'), 'call', {
- product_template_id: parseInt($parent.find('.product_template_id').val()),
- product_id: this._getProductId($parent),
- combination: combination,
- add_qty: parseInt(qty),
- pricelist_id: this.pricelistId || false,
+ return ajax.jsonRpc(this._getUri('/sale/get_combination_info'), 'call', {
+ 'product_template_id': productTemplateId,
+ 'product_id': this._getProductId($parent),
+ 'combination': combination,
+ 'add_qty': parseInt(qty),
+ 'pricelist_id': this.pricelistId || false,
+ 'parent_combination': parentCombination,
}).then(function (combinationData) {
self._onChangeCombination(ev, $parent, combinationData);
});
@@ -138,10 +158,13 @@ var VariantMixin = {
var $input = $link.closest('.input-group').find("input");
var min = parseFloat($input.data("min") || 0);
var max = parseFloat($input.data("max") || Infinity);
- var quantity = ($link.has(".fa-minus").length ? -1 : 1) + parseFloat($input.val() || 0, 10);
+ var previousQty = parseFloat($input.val() || 0, 10);
+ var quantity = ($link.has(".fa-minus").length ? -1 : 1) + previousQty;
var newQty = quantity > min ? (quantity < max ? quantity : max) : min;
- $input.val(newQty).trigger('change');
+ if (newQty !== previousQty) {
+ $input.val(newQty).trigger('change');
+ }
return false;
},
@@ -332,23 +355,6 @@ var VariantMixin = {
* @param {Array} combination the selected combination of product attribute values
*/
_checkExclusions: function ($parent, combination) {
-
- function areCombinationsEqual(c1, c2) {
- return c1.length === c2.length && _.every(c1, function (ptav) {
- return c2.indexOf(ptav) > -1;
- });
- }
-
- function isCombinationInList(c1, list) {
- return _.some(list, function (c2) {
- return areCombinationsEqual(c1, c2);
- });
- }
-
- function isPtavInCombination(ptav, combination) {
- return combination.indexOf(ptav) > -1;
- }
-
var self = this;
var combinationData = $parent
.find('ul[data-attribute_exclusions]')
@@ -360,17 +366,6 @@ var VariantMixin = {
.attr('title', '')
.data('excluded-by', '');
- var disable = false;
-
- // compatibility 12.0
- var filteredCombination = combination;
- if (combinationData.no_variant_product_template_attribute_value_ids !== undefined) {
- var no_variants = combinationData.no_variant_product_template_attribute_value_ids;
- filteredCombination = _.filter(combination, function (ptav) {
- return !isPtavInCombination(ptav, no_variants);
- });
- }
-
// exclusion rules: array of ptav
// for each of them, contains array with the other ptav they exclude
if (combinationData.exclusions) {
@@ -379,11 +374,6 @@ var VariantMixin = {
if (combinationData.exclusions.hasOwnProperty(current_ptav)) {
// for each exclusion of the current attribute:
_.each(combinationData.exclusions[current_ptav], function (excluded_ptav) {
- // disable if it excludes any other attribute already in the combination
- if (isPtavInCombination(excluded_ptav, combination)) {
- disable = true;
- }
-
// disable the excluded input (even when not already selected)
// to give a visual feedback before click
self._disableInput(
@@ -401,9 +391,6 @@ var VariantMixin = {
_.each(combinationData.parent_exclusions, function (exclusions, excluded_by){
// check that the selected combination is in the parent exclusions
_.each(exclusions, function (ptav) {
- if (isPtavInCombination(ptav, combination)) {
- disable = true;
- }
// disable the excluded input (even when not already selected)
// to give a visual feedback before click
@@ -416,30 +403,8 @@ var VariantMixin = {
);
});
});
-
- // archived variants
- if (isCombinationInList(filteredCombination, combinationData.archived_combinations)) {
- disable = true;
- }
-
- // if not using dynamic attributes, exclude variants that are deleted
- if (filteredCombination.length && // compatibility 12.0 list view of variants
- combinationData.has_dynamic_attributes === false &&
- combinationData.existing_combinations !== undefined &&
- !isCombinationInList(filteredCombination, combinationData.existing_combinations)
- ) {
- disable = true;
- }
-
- $parent.toggleClass('css_not_available', disable);
- $parent.find("#add_to_cart").toggleClass('disabled', disable);
$parent.find("#buy_now").toggleClass('disabled', disable);
- $parent
- .parents('.modal')
- .find('.o_sale_product_configurator_add, .o_sale_product_configurator_edit')
- .toggleClass('disabled', disable);
},
-
/**
* Extracted to a method to be extendable by other modules
*
@@ -448,7 +413,6 @@ var VariantMixin = {
_getProductId: function ($parent) {
return parseInt($parent.find('.product_id').val());
},
-
/**
* Will disable the input/option that refers to the passed attributeValueId.
* This is used for showing the user that some combinations are not available.
@@ -490,7 +454,6 @@ var VariantMixin = {
$target.data('excluded-by', JSON.stringify(excludedByData));
}
},
-
/**
* @see onChangeVariant
*
@@ -507,6 +470,13 @@ var VariantMixin = {
$price.text(self._priceToStr(combination.price));
$default_price.text(self._priceToStr(combination.list_price));
+ var isCombinationPossible = true;
+ if (!_.isUndefined(combination.is_combination_possible)) {
+ isCombinationPossible = combination.is_combination_possible;
+ }
+ this._toggleDisable($parent, isCombinationPossible);
+
+
// compatibility_check to remove in master
// needed for fix in 12.0 in the case of git pull and no server restart
var compatibility_check = combination.list_price - combination.price >= 0.01;
@@ -546,7 +516,8 @@ var VariantMixin = {
combination.display_image,
combination.product_id,
combination.product_template_id,
- combination.carousel
+ combination.carousel,
+ isCombinationPossible
);
}
@@ -587,7 +558,46 @@ var VariantMixin = {
formatted[0] = utils.insert_thousand_seps(formatted[0]);
return formatted.join(l10n.decimal_point);
},
-
+ /**
+ * Returns a throttled `_getCombinationInfo` with a leading and a trailing
+ * call, which is memoized per `uniqueId`, and for which previous results
+ * are dropped.
+ *
+ * The uniqueId is needed because on the configurator modal there might be
+ * multiple elements triggering the rpc at the same time, and we need each
+ * individual product rpc to be executed, but only once per individual
+ * product.
+ *
+ * The leading execution is to keep good reactivity on the first call, for
+ * a better user experience. The trailing is because ultimately only the
+ * information about the last selected combination is useful. All
+ * intermediary rpc can be ignored and are therefore best not done at all.
+ *
+ * The DropMisordered is to make sure slower rpc are ignored if the result
+ * of a newer rpc has already been received.
+ *
+ * @private
+ * @param {string} uniqueId
+ * @returns {function}
+ */
+ _throttledGetCombinationInfo: _.memoize(function (uniqueId) {
+ var dropMisordered = new concurrency.DropMisordered();
+ var _getCombinationInfo = _.throttle(this._getCombinationInfo.bind(this), 500);
+ return function (ev, params) {
+ return dropMisordered.add(_getCombinationInfo(ev, params));
+ };
+ }),
+ /**
+ * Toggles the disabled class depending on the $parent element
+ * and the possibility of the current combination.
+ *
+ * @private
+ * @param {$.Element} $parent
+ * @param {boolean} isCombinationPossible
+ */
+ _toggleDisable: function ($parent, isCombinationPossible) {
+ $parent.toggleClass('css_not_available', !isCombinationPossible);
+ },
/**
* Updates the product image.
* This will use the productId if available or will fallback to the productTemplateId.
diff --git a/addons/sale/views/res_config_settings_views.xml b/addons/sale/views/res_config_settings_views.xml
index 6ae4798b98f..567f17e8dc9 100644
--- a/addons/sale/views/res_config_settings_views.xml
+++ b/addons/sale/views/res_config_settings_views.xml
@@ -61,7 +61,7 @@
<div class="o_setting_right_pane">
<label for="module_product_email_template" string="Deliver Content by Email"/>
<div class="text-muted">
- Send a product-specific email once the invoice is paid
+ Send a product-specific email once the invoice is validated
</div>
</div>
</div>
diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml
index 758108db0ea..0dc660a46ea 100644
--- a/addons/sale/views/sale_views.xml
+++ b/addons/sale/views/sale_views.xml
@@ -498,6 +498,7 @@
<field name="price_total" invisible="1"/>
<field name="price_unit"/>
<field name="display_type"/>
+ <field name="tax_id" invisible="1"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_card oe_kanban_global_click {{ record.display_type.raw_value ? 'o_is_' + record.display_type.raw_value : '' }}">
diff --git a/addons/sale_coupon/tests/common.py b/addons/sale_coupon/tests/common.py
index 03820578f64..fec5d669aba 100644
--- a/addons/sale_coupon/tests/common.py
+++ b/addons/sale_coupon/tests/common.py
@@ -1,14 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from openerp.tests import common
+from odoo.addons.sale.tests.test_sale_product_attribute_value_config import TestSaleProductAttributeValueSetup
-class TestSaleCouponCommon(common.TransactionCase):
+class TestSaleCouponCommon(TestSaleProductAttributeValueSetup):
def setUp(self):
super(TestSaleCouponCommon, self).setUp()
+ # set currency to not rely on demo data and avoid possible race condition
+ self.currency_ratio = 1.0
+ pricelist = self.env.ref('product.list0')
+ pricelist.currency_id = self._setup_currency(self.currency_ratio)
+
# Set all the existing programs to active=False to avoid interference
self.env['sale.coupon.program'].search([]).write({'active': False})
diff --git a/addons/sale_product_configurator/static/src/js/product_configurator_modal.js b/addons/sale_product_configurator/static/src/js/product_configurator_modal.js
index da3ac1d8494..7d52b07b76d 100644
--- a/addons/sale_product_configurator/static/src/js/product_configurator_modal.js
+++ b/addons/sale_product_configurator/static/src/js/product_configurator_modal.js
@@ -11,6 +11,7 @@ var optionalProductsMap = {};
var OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, {
events: _.extend({}, Dialog.prototype.events, VariantMixin.events, {
'click a.js_add, a.js_remove': '_onAddOrRemoveOption',
+ 'click button.js_add_cart_json': 'onClickAddCartJSON',
'change .in_cart.main_product input.js_quantity': '_onChangeQuantity',
'change .js_raw_price': '_computePriceTotal'
}),
@@ -60,7 +61,6 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, {
this.previousModalHeight = params.previousModalHeight;
this.dialogClass = 'oe_optional_products_modal';
this._productImageField = 'image_medium';
-
// reset any previously populated properties maps
optionalProductsMap = {};
@@ -437,6 +437,8 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, {
.text(combination.display_name);
VariantMixin._onChangeCombination.apply(this, arguments);
+
+ this._computePriceTotal();
},
/**
* When the quantity of the root product is updated, we need to update
@@ -496,4 +498,4 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, {
return OptionalProductsModal;
-}); \ No newline at end of file
+});
diff --git a/addons/sale_product_configurator/static/src/js/product_configurator_renderer.js b/addons/sale_product_configurator/static/src/js/product_configurator_renderer.js
index 339254c8e45..0e3829f69e1 100644
--- a/addons/sale_product_configurator/static/src/js/product_configurator_renderer.js
+++ b/addons/sale_product_configurator/static/src/js/product_configurator_renderer.js
@@ -5,6 +5,11 @@ var FormRenderer = require('web.FormRenderer');
var VariantMixin = require('sale.VariantMixin');
var ProductConfiguratorFormRenderer = FormRenderer.extend(VariantMixin, {
+
+ events: _.extend({}, FormRenderer.prototype.events, VariantMixin.events, {
+ 'click button.js_add_cart_json': 'onClickAddCartJSON',
+ }),
+
/**
* @override
*/
@@ -74,6 +79,17 @@ var ProductConfiguratorFormRenderer = FormRenderer.extend(VariantMixin, {
},
/**
+ * Toggles the add button depending on the possibility of the current
+ * combination.
+ *
+ * @override
+ */
+ _toggleDisable: function ($parent, isCombinationPossible) {
+ VariantMixin._toggleDisable.apply(this, arguments);
+ $parent.parents('.modal').find('.o_sale_product_configurator_add').toggleClass('disabled', !isCombinationPossible);
+ },
+
+ /**
* Will fill the custom values input based on the provided initial configuration.
*
* @private
diff --git a/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js b/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js
index 7bce9880f73..6c36897ef71 100644
--- a/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js
+++ b/addons/sale_product_configurator/static/tests/tours/product_configurator_ui.js
@@ -36,11 +36,7 @@ tour.register('sale_product_configurator_tour', {
run: 'click'
}, {
trigger: '.configurator_container span:contains("Steel")',
- run: function () {
- $('input.product_id').change(function () {
- $('.o_sale_product_configurator_add').attr('request_count', 1);
- });
- }
+ run: function () {},
}, {
trigger: '.configurator_container span:contains("Aluminium")',
run: 'click'
@@ -48,8 +44,11 @@ tour.register('sale_product_configurator_tour', {
trigger: 'span.oe_currency_value:contains("800.40")',
run: function (){} // check updated price
}, {
- trigger: '.o_sale_product_configurator_add[request_count="1"]',
- run: function (){} // used to sync with "get_combination_info" completion
+ trigger: 'input[data-value_name="Black"]'
+}, {
+ trigger: '.o_sale_product_configurator_add.disabled'
+}, {
+ trigger: 'input[data-value_name="White"]'
}, {
trigger: '.o_sale_product_configurator_add:not(.disabled)'
}, {
diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py
index 3d6b67ae58d..5d682178a0b 100644
--- a/addons/sale_stock/models/sale_order.py
+++ b/addons/sale_stock/models/sale_order.py
@@ -107,6 +107,21 @@ class SaleOrder(models.Model):
if self.warehouse_id.company_id:
self.company_id = self.warehouse_id.company_id.id
+ @api.onchange('partner_shipping_id')
+ def _onchange_partner_shipping_id(self):
+ res = {}
+ pickings = self.picking_ids.filtered(
+ lambda p: p.state not in ['done', 'cancel'] and p.partner_id != self.partner_shipping_id
+ )
+ if pickings:
+ res['warning'] = {
+ 'title': _('Warning!'),
+ 'message': _(
+ 'Do not forget to change the partner on the following delivery orders: %s'
+ ) % (','.join(pickings.mapped('name')))
+ }
+ return res
+
@api.multi
def action_view_delivery(self):
'''
diff --git a/addons/sale_timesheet/models/sale_order.py b/addons/sale_timesheet/models/sale_order.py
index ee49687a9d2..82654d8b81d 100644
--- a/addons/sale_timesheet/models/sale_order.py
+++ b/addons/sale_timesheet/models/sale_order.py
@@ -258,6 +258,10 @@ class SaleOrderLine(models.Model):
'partner_id': self.order_id.partner_id.id,
'email_from': self.order_id.partner_id.email,
})
+ # duplicating a project doesn't set the SO on sub-tasks
+ project.tasks.filtered(lambda task: task.parent_id != False).write({
+ 'sale_line_id': self.id,
+ })
else:
project = self.env['project.project'].create(values)
# link project as generated by current so line
diff --git a/addons/sales_team/tests/__init__.py b/addons/sales_team/tests/__init__.py
new file mode 100644
index 00000000000..7bb8a11fc5e
--- /dev/null
+++ b/addons/sales_team/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_default_team
diff --git a/addons/sales_team/tests/test_default_team.py b/addons/sales_team/tests/test_default_team.py
new file mode 100644
index 00000000000..9905272b3d9
--- /dev/null
+++ b/addons/sales_team/tests/test_default_team.py
@@ -0,0 +1,48 @@
+from odoo.tests import common
+
+
+class TestDefaultTeam(common.SavepointCase):
+ """Tests to check if correct default team is found."""
+
+ @classmethod
+ def setUpClass(cls):
+ """Set up data for default team tests."""
+ super(TestDefaultTeam, cls).setUpClass()
+ cls.CrmTeam = cls.env['crm.team']
+ ResUsers = cls.env['res.users'].with_context(
+ {'no_reset_password': True})
+ group_sale_manager = cls.env.ref('sales_team.group_sale_manager')
+ cls.user = ResUsers.create({
+ 'name': 'Team User',
+ 'login': 'sales_team_user',
+ 'email': 'sales.team.user@example.com',
+ 'groups_id': [(6, 0, [group_sale_manager.id])]
+ })
+ cls.team_1 = cls.env['crm.team'].create({
+ 'name': 'Test Team',
+ 'member_ids': [(4, cls.user.id)],
+ 'company_id': False
+ })
+ # Europe Team (fall back team)
+ cls.team_2 = cls.env.ref('sales_team.team_sales_department')
+
+ def test_01_user_team(self):
+ """Get default team, when user belongs to one."""
+ team = self.CrmTeam.sudo(self.user)._get_default_team_id()
+ self.assertEqual(team, self.team_1)
+
+ def test_02_fallback_team(self):
+ """Get default team when user does not belong to any team.
+
+ Case 1: fall back default team (from XML ref) is active.
+ Case 2: fall back default team is not active.
+ """
+ # Clear users from team.
+ self.team_1.member_ids = [(5,)]
+ # Case 1.
+ team = self.CrmTeam.sudo(self.user)._get_default_team_id()
+ self.assertEqual(team, self.team_2)
+ # Case 2.
+ self.team_2.active = False
+ team = self.CrmTeam.sudo(self.user)._get_default_team_id()
+ self.assertEqual(team, self.CrmTeam)
diff --git a/addons/sms/models/mail_thread.py b/addons/sms/models/mail_thread.py
index 1fb62a72de4..a439487e4e9 100644
--- a/addons/sms/models/mail_thread.py
+++ b/addons/sms/models/mail_thread.py
@@ -39,8 +39,8 @@ class MailThread(models.AbstractModel):
if not partners:
partners = self._get_default_sms_recipients()
- # Collect numbers, we will consider the message to be sent if at least one number can be found
- numbers = list(set([i.mobile for i in partners if i.mobile]))
+ # Collect numbers, we will consider the message to be sent if at least one number can be found
+ numbers = list(set([i.mobile for i in partners if i.mobile]))
if numbers:
try:
diff --git a/addons/sms/wizard/send_sms.py b/addons/sms/wizard/send_sms.py
index ac2d63a0d75..e264eab59f8 100644
--- a/addons/sms/wizard/send_sms.py
+++ b/addons/sms/wizard/send_sms.py
@@ -45,8 +45,8 @@ class SendSMS(models.TransientModel):
return number
if not phonenumbers.is_possible_number(phone_nbr) or not phonenumbers.is_valid_number(phone_nbr):
return number
- phone_fmt = phonenumbers.PhoneNumberFormat.INTERNATIONAL
- return phonenumbers.format_number(phone_nbr, phone_fmt).replace(' ', '')
+ phone_fmt = phonenumbers.PhoneNumberFormat.E164
+ return phonenumbers.format_number(phone_nbr, phone_fmt)
else:
return number
@@ -62,12 +62,11 @@ class SendSMS(models.TransientModel):
@api.model
def default_get(self, fields):
result = super(SendSMS, self).default_get(fields)
-
active_model = self.env.context.get('active_model')
- model = self.env[active_model]
- records = self._get_records(model)
- if getattr(records, '_get_default_sms_recipients') and not self.env.context.get('default_recipients'):
+ if not self.env.context.get('default_recipients') and active_model and hasattr(self.env[active_model], '_get_default_sms_recipients'):
+ model = self.env[active_model]
+ records = self._get_records(model)
partners = records._get_default_sms_recipients()
phone_numbers = []
no_phone_partners = []
@@ -84,12 +83,12 @@ class SendSMS(models.TransientModel):
return result
def action_send_sms(self):
- numbers = self.recipients.split(',')
+ numbers = [number.strip() for number in self.recipients.split(',') if number.strip()]
active_model = self.env.context.get('active_model')
- model = self.env[active_model]
- records = self._get_records(model)
- if getattr(records, 'message_post_send_sms'):
+ if active_model and hasattr(self.env[active_model], 'message_post_send_sms'):
+ model = self.env[active_model]
+ records = self._get_records(model)
records.message_post_send_sms(self.message, numbers=numbers)
else:
self.env['sms.api']._send_sms(numbers, self.message)
diff --git a/addons/snailmail/models/snailmail_letter.py b/addons/snailmail/models/snailmail_letter.py
index a0925515a2c..b9e2fb55477 100644
--- a/addons/snailmail/models/snailmail_letter.py
+++ b/addons/snailmail/models/snailmail_letter.py
@@ -10,7 +10,6 @@ from odoo.exceptions import UserError
from odoo.tools.safe_eval import safe_eval
DEFAULT_ENDPOINT = 'https://iap-snailmail.odoo.com'
-ESTIMATE_ENDPOINT = '/iap/snailmail/1/estimate'
PRINT_ENDPOINT = '/iap/snailmail/1/print'
ERROR_CODES = [
@@ -346,69 +345,6 @@ class SnailmailLetter(models.Model):
self.write({'state': 'canceled', 'error_code': False})
self.send_snailmail_update()
- @api.multi
- def _snailmail_estimate(self):
- """
- Send a request to estimate the cost of sending all the documents with
- the differents options.
-
- The JSON object sent is the one generated from self._snailmail_create()
-
- arguments sent:
- {
- "documents":[{
- pages: int,
- res_id: int (client_side, optional),
- res_model: int (client_side, optional),
- address: {
- country_code: char (country name)
- }
- }],
- 'color': # color on the letter,
- 'duplex': # one/two side printing,
- }
-
- The answer of the server is the same JSON object with some additionnal fields:
- {
- "total_cost": integer, //The cost of sending ALL the documents
- body: JSON object (same as body param except new param 'cost' in each documents)
- }
- """
- endpoint = self.env['ir.config_parameter'].sudo().get_param('snailmail.endpoint', DEFAULT_ENDPOINT)
- params = self._snailmail_create('estimate')
- req = jsonrpc(endpoint + '/iap/snailmail/1/estimate', params=params)
-
- return req['total_cost']
-
- @api.model
- def _snailmail_estimate_from_documents(self, res_model, res_ids, partner_field=None):
- endpoint = self.env['ir.config_parameter'].sudo().get_param('snailmail.endpoint', DEFAULT_ENDPOINT)
- docs = self.env[res_model].browse(res_ids)
- doc_list = []
- for doc in docs:
- if partner_field:
- country_code = doc[partner_field].country_id.code
- else:
- country_code = doc.country_id.code
- val = {
- 'pages': 1,
- 'address': {
- 'country_code': country_code,
- },
- }
- doc_list.append(val)
- params = {
- 'account_token': "",
- 'documents': doc_list,
- 'options': {
- 'color': self.env.company.snailmail_color,
- 'duplex': self.env.company.snailmail_duplex,
- 'currency_name': 'EUR',
- },
- }
- req = jsonrpc(endpoint + '/iap/snailmail/1/estimate', params=params)
- return req['total_cost']
-
@api.model
def _snailmail_cron(self):
letters_send = self.search([
diff --git a/addons/snailmail_account/wizard/account_invoice_send.py b/addons/snailmail_account/wizard/account_invoice_send.py
index d1545d808c4..de0829cd1d8 100644
--- a/addons/snailmail_account/wizard/account_invoice_send.py
+++ b/addons/snailmail_account/wizard/account_invoice_send.py
@@ -35,7 +35,7 @@ class AccountInvoiceSend(models.TransientModel):
@api.depends('snailmail_is_letter')
def _compute_snailmail_cost(self):
for wizard in self:
- wizard.snailmail_cost = wizard.env['snailmail.letter']._snailmail_estimate_from_documents(wizard.invoice_ids._name, wizard.invoice_ids.ids, 'partner_id')
+ wizard.snailmail_cost = len(wizard.invoice_ids.ids)
@api.multi
def snailmail_print_action(self):
diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py
index 5582ab5839f..2f9da1de3f1 100644
--- a/addons/stock/models/product.py
+++ b/addons/stock/models/product.py
@@ -517,6 +517,10 @@ class ProductTemplate(models.Model):
def _is_cost_method_standard(self):
return True
+ @api.depends(
+ 'product_variant_ids',
+ 'product_variant_ids.stock_quant_ids',
+ )
def _compute_quantities(self):
res = self._compute_quantities_dict()
for template in self:
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index f2e277f8245..e6874d6b71c 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -450,6 +450,7 @@ class StockMove(models.Model):
view = self.env.ref('stock.view_stock_move_operations')
+ picking_type_id = self.picking_type_id or self.picking_id.picking_type_id
return {
'name': _('Detailed Operations'),
'type': 'ir.actions.act_window',
@@ -462,8 +463,8 @@ class StockMove(models.Model):
'res_id': self.id,
'context': dict(
self.env.context,
- show_lots_m2o=self.has_tracking != 'none' and (self.picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), # able to create lots, whatever the value of ` use_create_lots`.
- show_lots_text=self.has_tracking != 'none' and self.picking_type_id.use_create_lots and not self.picking_type_id.use_existing_lots and self.state != 'done' and not self.origin_returned_move_id.id,
+ show_lots_m2o=self.has_tracking != 'none' and (picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), # able to create lots, whatever the value of ` use_create_lots`.
+ show_lots_text=self.has_tracking != 'none' and picking_type_id.use_create_lots and not picking_type_id.use_existing_lots and self.state != 'done' and not self.origin_returned_move_id.id,
show_source_location=self.location_id.child_ids and self.picking_type_id.code != 'incoming',
show_destination_location=self.location_dest_id.child_ids and self.picking_type_id.code != 'outgoing',
show_package=not self.location_id.usage == 'supplier',
diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py
index 4f694566f07..9e55e533b2a 100644
--- a/addons/stock/models/stock_warehouse.py
+++ b/addons/stock/models/stock_warehouse.py
@@ -894,7 +894,6 @@ class Warehouse(models.Model):
@api.returns('self')
def _get_all_routes(self):
- # TDE FIXME: check overrides
routes = self.mapped('route_ids') | self.mapped('mto_pull_id').mapped('route_id')
routes |= self.env["stock.location.route"].search([('supplied_wh_id', 'in', self.ids)])
return routes
diff --git a/addons/stock/report/report_stock_forecast.py b/addons/stock/report/report_stock_forecast.py
index 59e788ac4b6..1bf69ab5719 100644
--- a/addons/stock/report/report_stock_forecast.py
+++ b/addons/stock/report/report_stock_forecast.py
@@ -129,6 +129,8 @@ class ReportStockForecat(models.Model):
WHERE
final.product_qty != 0 OR final.reference = 'Starting Inventory'
%s
+ ORDER BY
+ date
)
""" % (self._select(), self._left_join(), self._groupby())
self.env.cr.execute(query)
@@ -185,7 +187,6 @@ class ReportStockForecat(models.Model):
order to analyze different quantities in different situations.
"""
# Sort result by date_expected and id.
- orderby = 'date, id' if not orderby else orderby
if 'cumulative_quantity' in fields and 'quantity' not in fields:
fields.append('quantity')
res = super(ReportStockForecat, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
diff --git a/addons/stock/wizard/stock_picking_return.py b/addons/stock/wizard/stock_picking_return.py
index c7b0ed32733..f52afdb72a1 100644
--- a/addons/stock/wizard/stock_picking_return.py
+++ b/addons/stock/wizard/stock_picking_return.py
@@ -46,7 +46,7 @@ class ReturnPicking(models.TransientModel):
@api.onchange('picking_id')
def _onchange_picking_id(self):
move_dest_exists = False
- product_return_moves = []
+ product_return_moves = [(5,)]
if self.picking_id and self.picking_id.state != 'done':
raise UserError(_("You may only return Done pickings."))
for move in self.picking_id.move_lines:
diff --git a/addons/stock/wizard/stock_picking_return_views.xml b/addons/stock/wizard/stock_picking_return_views.xml
index 3c901072aa7..e4cb319456c 100644
--- a/addons/stock/wizard/stock_picking_return_views.xml
+++ b/addons/stock/wizard/stock_picking_return_views.xml
@@ -29,7 +29,7 @@
<group>
<field name="parent_location_id" invisible="1"/>
<field name="original_location_id" invisible="1"/>
- <field name="location_id" options="{'no_create': True, 'no_open': True}" groups="stock.group_stock_multi_locations"/>
+ <field name="location_id" options="{'no_create': True, 'no_open': True}" groups="stock.group_stock_multi_locations" required="1"/>
</group>
<footer>
<button name="create_returns" string="Return" type="object" class="btn-primary"/>
diff --git a/addons/stock_account/models/__init__.py b/addons/stock_account/models/__init__.py
index 707d4618bda..a11616c850a 100644
--- a/addons/stock_account/models/__init__.py
+++ b/addons/stock_account/models/__init__.py
@@ -4,6 +4,7 @@
from . import account_chart_template
from . import account_invoice
from . import account_move
+from . import account_reconciliation_widget
from . import product
from . import stock
from . import res_config_settings
diff --git a/addons/stock_account/models/account_reconciliation_widget.py b/addons/stock_account/models/account_reconciliation_widget.py
new file mode 100644
index 00000000000..44110053899
--- /dev/null
+++ b/addons/stock_account/models/account_reconciliation_widget.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, models
+from odoo.osv import expression
+
+
+class AccountReconciliation(models.AbstractModel):
+ _inherit = "account.reconciliation.widget"
+
+ @api.model
+ def _domain_move_lines_for_reconciliation(self, st_line, aml_accounts, partner_id, excluded_ids=None, search_str=False):
+ def to_int(val):
+ try:
+ return int(val)
+ except (ValueError, TypeError):
+ return None
+
+ domain = super()._domain_move_lines_for_reconciliation(
+ st_line, aml_accounts, partner_id, excluded_ids=excluded_ids, search_str=search_str
+ )
+ acc_props = (
+ "property_stock_account_input",
+ "property_stock_account_output",
+ "property_stock_account_input_categ_id",
+ "property_stock_account_output_categ_id",
+ )
+ acc_ids = [
+ (acc["value_reference"] or "").split(",")[-1]
+ for acc in self.env["ir.property"]
+ .sudo()
+ .search([("name", "in", acc_props), ("value_reference", "!=", False)])
+ .read(["value_reference"])
+ if to_int((acc["value_reference"] or "").split(",")[-1])
+ ]
+ if acc_ids:
+ domain = expression.AND([domain, [("account_id.id", "not in", acc_ids)]])
+ return domain
diff --git a/addons/stock_account/tests/test_stockvaluation.py b/addons/stock_account/tests/test_stockvaluation.py
index d3c4a9918e0..e628f11bf48 100644
--- a/addons/stock_account/tests/test_stockvaluation.py
+++ b/addons/stock_account/tests/test_stockvaluation.py
@@ -2590,7 +2590,7 @@ class TestStockValuation(SavepointCase):
self.assertAlmostEqual(self.product1.stock_value, 0.0)
def test_average_perpetual_6(self):
- self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
+ self.product1.categ_id.property_cost_method = 'average'
move1 = self.env['stock.move'].create({
'name': 'Receive 1 unit at 10',
@@ -2627,7 +2627,7 @@ class TestStockValuation(SavepointCase):
""" Test edit in the past. Receive 5@10, receive 10@20, edit the first move to receive
15 instead.
"""
- self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
+ self.product1.categ_id.property_cost_method = 'average'
move1 = self.env['stock.move'].create({
'name': 'IN 5@10',
@@ -2667,7 +2667,7 @@ class TestStockValuation(SavepointCase):
""" Receive 1@10, then dropship 1@20, finally return the dropship. Dropship should not
impact the price.
"""
- self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
+ self.product1.categ_id.property_cost_method = 'average'
move1 = self.env['stock.move'].create({
'name': 'IN 1@10',
diff --git a/addons/survey/views/assets.xml b/addons/survey/views/assets.xml
index 381531bfd41..75543048801 100644
--- a/addons/survey/views/assets.xml
+++ b/addons/survey/views/assets.xml
@@ -21,6 +21,12 @@
</xpath>
</template>
+ <template id="assets_common" name="tour" inherit_id="web.assets_common">
+ <xpath expr="//script[last()]" position="after">
+ <script type="text/javascript" src="/survey/static/src/js/tour_test_survey.js"></script>
+ </xpath>
+ </template>
+
<template id="assets_backend" name="survey assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/survey/static/src/css/survey_result.css"/>
diff --git a/addons/test_mail/tests/test_mail_template.py b/addons/test_mail/tests/test_mail_template.py
index ebff4b24a3a..3a70a575011 100644
--- a/addons/test_mail/tests/test_mail_template.py
+++ b/addons/test_mail/tests/test_mail_template.py
@@ -70,6 +70,46 @@ class TestMailTemplate(BaseFunctionalTest, MockEmails, TestRecipients):
body_content=self.test_record.email_from,
attachments=[('first.txt', b'My first attachment', 'text/plain'), ('second.txt', b'My second attachment', 'text/plain')])
+ def test_composer_template_onchange_attachments(self):
+ """Tests that all attachments are added to the composer,
+ static attachments are not duplicated and while reports are re-generated,
+ and that intermediary attachments are dropped."""
+
+ composer = self.env['mail.compose.message'].with_context(default_attachment_ids=[]).create({})
+ report_template = self.env.ref('web.action_report_externalpreview')
+ template_1 = self.email_template.copy({
+ 'report_template': report_template.id,
+ })
+ template_2 = self.email_template.copy({
+ 'attachment_ids': False,
+ 'report_template': report_template.id,
+ })
+
+ onchange_templates = [template_1, template_2, template_1, False]
+ attachments_onchange = [composer.attachment_ids]
+ # template_1 has two static attachments and one dynamically generated report,
+ # template_2 only has the report, so we should get 3, 1, 3 attachments
+ attachment_numbers = [0, 3, 1, 3, 0]
+
+ with self.env.do_in_onchange():
+ for template in onchange_templates:
+ onchange = composer.onchange_template_id(
+ template.id if template else False, 'comment', self.test_record._name, self.test_record.id
+ )
+ values = composer._convert_to_record(composer._convert_to_cache(onchange['value']))
+ attachments_onchange.append(values['attachment_ids'])
+ composer.update(onchange['value'])
+
+ self.assertEqual(
+ [len(attachments) for attachments in attachments_onchange],
+ attachment_numbers,
+ )
+
+ self.assertTrue(
+ len(attachments_onchange[1] & attachments_onchange[3]) == 2,
+ "The two static attachments on the template should be common to the two onchanges"
+ )
+
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_post_post_w_template(self):
self.test_record.sudo(self.user_employee).message_post_with_template(self.email_template.id, composition_mode='comment')
@@ -235,3 +275,44 @@ class TestMailTemplate(BaseFunctionalTest, MockEmails, TestRecipients):
new_partner = Partner.search([('email', '=', email_new_partner)])
self.assertTrue(new_partner)
self.assertEqual(new_partner.company_id, company1)
+
+ def test_composer_template_onchange_attachments(self):
+ """Tests that all attachments are added to the composer,
+ static attachments are not duplicated and while reports are re-generated,
+ and that intermediary attachments are dropped."""
+
+ composer = self.env['mail.compose.message'].with_context(default_attachment_ids=[]).create({})
+ report_template = self.env.ref('web.action_report_externalpreview')
+ template_1 = self.email_template.copy({
+ 'report_template': report_template.id,
+ })
+ template_2 = self.email_template.copy({
+ 'attachment_ids': False,
+ 'report_template': report_template.id,
+ })
+
+ onchange_templates = [template_1, template_2, template_1, False]
+ attachments_onchange = [composer.attachment_ids]
+ # template_1 has two static attachments and one dynamically generated report,
+ # template_2 only has the report, so we should get 3, 1, 3 attachments
+ # and when there is no template, no attachments
+ attachment_numbers = [0, 3, 1, 3, 0]
+
+ with self.env.do_in_onchange():
+ for template in onchange_templates:
+ onchange = composer.onchange_template_id(
+ template.id if template else False, 'comment', 'mail.test.simple', self.test_record.id
+ )
+ values = composer._convert_to_record(composer._convert_to_cache(onchange['value']))
+ attachments_onchange.append(values['attachment_ids'])
+ composer.update(onchange['value'])
+
+ self.assertEqual(
+ [len(attachments) for attachments in attachments_onchange],
+ attachment_numbers,
+ )
+
+ self.assertTrue(
+ len(attachments_onchange[1] & attachments_onchange[3]) == 2,
+ "The two static attachments on the template should be common to the two onchanges"
+ )
diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js
index f1af6fc7234..6b7df97b767 100644
--- a/addons/web/static/src/js/chrome/abstract_web_client.js
+++ b/addons/web/static/src/js/chrome/abstract_web_client.js
@@ -514,7 +514,11 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi
new RainbowMan(data).appendTo(this.$el);
} else {
// For instance keep title blank, as we don't have title in data
- this.notification_manager.notify('', data.message, true);
+ this.call('notification', 'notify', {
+ title: "",
+ message: data.message,
+ sticky: false
+ });
}
} else {
throw new Error('Unknown effect type: ' + type);
diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js
index f694e2ca1b0..ee3ce15bdd2 100644
--- a/addons/web/static/src/js/chrome/action_manager_act_window.js
+++ b/addons/web/static/src/js/chrome/action_manager_act_window.js
@@ -58,7 +58,11 @@ ActionManager.include({
* @param {string} [state.view_type]
*/
loadState: function (state) {
+ var callersArguments = arguments;
+ var _super = this._super.bind(this);
+ var self = this;
var action;
+ var def;
var options = {
clear_breadcrumbs: true,
pushState: false,
@@ -113,11 +117,20 @@ ActionManager.include({
action = lastAction;
options.viewType = state.view_type;
}
+ } else if (state.sa) {
+ def = this._rpc({
+ route: '/web/session/get_session_action',
+ params: {key: state.sa},
+ }).then(function (sessionAction) {
+ action = sessionAction;
+ });
}
- if (action) {
- return this.doAction(action, options);
- }
- return this._super.apply(this, arguments);
+ return Promise.all([def]).then(function () {
+ if (action) {
+ return self.doAction(action, options);
+ }
+ return _super.apply(self, callersArguments);
+ });
},
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js
index a5ed4065282..af7628cc5ea 100644
--- a/addons/web/static/src/js/core/ajax.js
+++ b/addons/web/static/src/js/core/ajax.js
@@ -27,11 +27,17 @@ function _genericJsonRpc (fct_name, params, settings, fct) {
core.bus.trigger('rpc:result', data, result);
if (result.error !== undefined) {
if (result.error.data.arguments[0] !== "bus.Bus not available in test mode") {
+ var func = console.error;
if (result.error.data.exception_type === "user_error") {
- console.log("Server application error", JSON.stringify(result.error));
- } else {
- console.error("Server application error", JSON.stringify(result.error));
+ func = console.log;
}
+ func(
+ "Server application error\n",
+ "Error code:", result.error.code, "\n",
+ "Error message:", result.error.message, "\n",
+ "Error data message:\n", result.error.data.message, "\n",
+ "Error data debug:\n", result.error.data.debug
+ );
}
return Promise.reject({type: "server", error: result.error});
} else {
diff --git a/addons/web/static/src/js/tools/test_menus.js b/addons/web/static/src/js/tools/test_menus.js
index 4b7ee4b2e02..82a4ed26c0e 100644
--- a/addons/web/static/src/js/tools/test_menus.js
+++ b/addons/web/static/src/js/tools/test_menus.js
@@ -44,8 +44,12 @@
}
}
- // Main function that starts orchestration of tests
function clickEverywhere(){
+ setTimeout(_clickEverywhere, 1000);
+ }
+
+ // Main function that starts orchestration of tests
+ function _clickEverywhere(){
console.log("Starting ClickEverywhere test");
var startTime = performance.now();
createWebClientHooks();
diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js
index 61a966972aa..b3a04ec9a2b 100644
--- a/addons/web/static/src/js/views/calendar/calendar_controller.js
+++ b/addons/web/static/src/js/views/calendar/calendar_controller.js
@@ -160,7 +160,8 @@ var CalendarController = AbstractController.extend({
* @returns {Promise}
*/
_updateRecord: function (record) {
- return this.model.updateRecord(record).then(this.reload.bind(this, {}));
+ var reload = this.reload.bind(this, {});
+ return this.model.updateRecord(record).then(reload, reload);
},
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js
index 0ad1b61e4d7..c3934d930d7 100644
--- a/addons/web/static/src/js/views/calendar/calendar_model.js
+++ b/addons/web/static/src/js/views/calendar/calendar_model.js
@@ -310,7 +310,7 @@ return AbstractModel.extend({
* Toggle the sidebar (containing the mini calendar)
*/
toggleFullWidth: function () {
- var fullWidth = this.call('local_storage', 'getItem', 'calendar_fullWidth') !== 'true';
+ var fullWidth = this.call('local_storage', 'getItem', 'calendar_fullWidth') !== true;
this.call('local_storage', 'setItem', 'calendar_fullWidth', fullWidth);
},
/**
@@ -442,7 +442,7 @@ return AbstractModel.extend({
*/
_loadCalendar: function () {
var self = this;
- this.data.fullWidth = this.call('local_storage', 'getItem', 'calendar_fullWidth') === 'true';
+ this.data.fullWidth = this.call('local_storage', 'getItem', 'calendar_fullWidth') === true;
this.data.fc_options = this._getFullCalendarOptions();
var defs = _.map(this.data.filters, this._loadFilter.bind(this));
diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js
index 3206ef7b47a..5b4480838fe 100644
--- a/addons/web/static/src/js/widgets/date_picker.js
+++ b/addons/web/static/src/js/widgets/date_picker.js
@@ -13,6 +13,7 @@ var DateWidget = Widget.extend({
type_of_date: "date",
events: {
'change.datetimepicker': 'changeDatetime',
+ 'error.datetimepicker': 'errorDatetime',
'change .o_datepicker_input': 'changeDatetime',
'click input': '_onInputClicked',
'input input': '_onInput',
@@ -112,6 +113,12 @@ var DateWidget = Widget.extend({
}
},
/**
+ * Library clears the wrong date format so just ignore error
+ */
+ errorDatetime: function (e) {
+ return false;
+ },
+ /**
* Focuses the datepicker input. This function must be called in order to
* prevent 'input' events triggered by the lib to bubble up, and to cause
* unwanted effects (like triggering 'field_changed' events)
@@ -276,17 +283,6 @@ var DateWidget = Widget.extend({
*/
_onKeydown: function (ev) {
if (ev.which === $.ui.keyCode.ESCAPE) {
- this.__libInput++;
- this.$el.datetimepicker('hide');
- this.__libInput--;
- }
- },
- /**
- * @private
- * @param {KeyEvent} ev
- */
- _onKeydown: function (ev) {
- if (ev.which === $.ui.keyCode.ESCAPE) {
if (this.__isOpen) {
// we don't want any other effects than closing the datepicker,
// like leaving the edition of a row in editable list view
diff --git a/addons/web/static/src/scss/bootstrap_review.scss b/addons/web/static/src/scss/bootstrap_review.scss
index b9e889ce71a..414984dc42d 100644
--- a/addons/web/static/src/scss/bootstrap_review.scss
+++ b/addons/web/static/src/scss/bootstrap_review.scss
@@ -30,6 +30,15 @@
&:last-child {
@include border-bottom-radius($card-inner-border-radius);
}
+
+ &.row {
+ // The 'row' class should not be used on a 'card-body' element but if
+ // it is done, our custom bg color would overflow the card. As a fix
+ // for those cases (normally only one at the time this fix is made),
+ // remove the background color.
+ // TODO remove me in master.
+ background-color: transparent !important;
+ }
}
.accordion {
.collapsing, .collapse.show {
@@ -70,3 +79,8 @@
min-height: 0;
}
}
+
+// Restore pointer cursor which came with the 'btn' class until BS 4.3.1
+.btn:not(:disabled):not(.disabled) {
+ cursor: pointer;
+}
diff --git a/addons/web/static/src/scss/form_view_extra.scss b/addons/web/static/src/scss/form_view_extra.scss
index 277537beab4..0d59483c2d8 100644
--- a/addons/web/static/src/scss/form_view_extra.scss
+++ b/addons/web/static/src/scss/form_view_extra.scss
@@ -140,7 +140,7 @@
border-right: 1px solid #ddd;
}
.o_td_label + td {
- padding: 2px 36px 2px 8px;
+ padding: 0px 36px 0px 8px;
}
.o_field_widget.o_text_overflow {
width: 1px!important; // hack to make the table layout believe it is a small element (so that the table does not grow too much) ...
diff --git a/addons/web/static/src/scss/kanban_view.scss b/addons/web/static/src/scss/kanban_view.scss
index 7ab56e2bbb3..f80d50ba576 100644
--- a/addons/web/static/src/scss/kanban_view.scss
+++ b/addons/web/static/src/scss/kanban_view.scss
@@ -248,6 +248,7 @@
font-weight: 500;
background-color: transparent;
color: inherit;
+ box-shadow: none;
@include o-kanban-tag-color;
span {
diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss
index 3d46aeef890..f8c0afdd499 100644
--- a/addons/web/static/src/scss/list_view.scss
+++ b/addons/web/static/src/scss/list_view.scss
@@ -42,6 +42,10 @@
font-weight: $o-list-footer-font-weight;
border-top: 2px solid #cacaca;
border-bottom: 1px solid #cacaca;
+
+ .o_list_number {
+ text-align: right;
+ }
}
.o_column_sortable {
diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js
index 7ac817af30c..c82755f2987 100644
--- a/addons/web/static/tests/chrome/action_manager_tests.js
+++ b/addons/web/static/tests/chrome/action_manager_tests.js
@@ -1177,6 +1177,30 @@ QUnit.module('ActionManager', {
actionManager.destroy();
});
+ QUnit.test('state.sa should load action from session', async function (assert) {
+ assert.expect(1);
+
+ var actionManager = await createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ mockRPC: function (route, args) {
+ if (route === '/web/session/get_session_action') {
+ return Promise.resolve(1);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ await actionManager.loadState({
+ sa: 1,
+ });
+
+ assert.strictEqual(actionManager.$('.o_kanban_view').length, 1,
+ "should have rendered a kanban view");
+
+ actionManager.destroy();
+ });
+
QUnit.module('Concurrency management');
QUnit.test('drop previous actions if possible', async function (assert) {
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index e768c1b67ea..5c399547458 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -2549,6 +2549,25 @@ QUnit.module('basic_fields', {
form.destroy();
});
+ QUnit.test('date field should remove the date if the date is not valid', async function (assert) {
+ assert.expect(1);
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners"><field name="date"/></form>',
+ res_id: 4,
+ });
+ // switch to edit mode
+ await testUtils.form.clickEdit(form);
+ // set an invalid date
+ var $input = form.$('.o_field_widget[name=date] input');
+ $input.val('mmmh').trigger('change');
+ assert.strictEqual($input.text(), "", "The date field should be empty");
+ form.destroy();
+ });
+
QUnit.test('date field value should not set on first click', async function (assert) {
assert.expect(2);
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index d78424841ff..babb22a5ba1 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -1,11 +1,13 @@
odoo.define('web.calendar_tests', function (require) {
"use strict";
+var AbstractStorageService = require('web.AbstractStorageService');
var CalendarView = require('web.CalendarView');
var CalendarRenderer = require('web.CalendarRenderer');
var Dialog = require('web.Dialog');
var fieldUtils = require('web.field_utils');
var mixins = require('web.mixins');
+var RamStorage = require('web.RamStorage');
var testUtils = require('web.test_utils');
var session = require('web.session');
@@ -2612,6 +2614,45 @@ QUnit.module('Views', {
calendar.destroy();
});
+ QUnit.test('calendar sidebar toggle', async function (assert) {
+ assert.expect(5);
+
+ var calendar = await createCalendarView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch:
+ '<calendar date_start="start_date">'+
+ '<field name="name"/>'+
+ '</calendar>',
+ services: {
+ local_storage: AbstractStorageService.extend({storage: new RamStorage()}),
+ },
+ });
+
+ var fullWidth = calendar.call('local_storage', 'getItem', 'calendar_fullWidth');
+ assert.strictEqual(fullWidth, undefined,
+ "calendar_fullWidth should be undefined in local storage");
+ assert.containsNone(calendar, '.o_calendar_sidebar_container.o_sidebar_hidden',
+ "sidebar should be opened");
+
+ // click on close
+ await testUtils.dom.click(calendar.$('.o_calendar_sidebar_toggler'));
+ fullWidth = calendar.call('local_storage', 'getItem', 'calendar_fullWidth');
+ assert.strictEqual(fullWidth, true,
+ "calendar_fullWidth should be true in local storage");
+ assert.containsOnce(calendar, '.o_calendar_sidebar_container.o_sidebar_hidden',
+ "sidebar should be hidden");
+
+ // reload calendar view and check calendar view has still full width
+ calendar.reload();
+ await testUtils.nextTick();
+ assert.containsOnce(calendar, '.o_calendar_sidebar_container.o_sidebar_hidden',
+ "sidebar should be hidden");
+
+ calendar.destroy();
+ });
+
});
});
diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js
index 4d8b751cc1a..7c6b0c5c335 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -8158,6 +8158,72 @@ QUnit.module('Views', {
await testUtils.nextTick();
form.destroy();
});
+
+ QUnit.test('resequence list lines when discardable lines are present', async function (assert) {
+ assert.expect(8);
+
+ var onchangeNum = 0;
+
+ this.data.partner.onchanges = {
+ p: function (obj) {
+ onchangeNum++;
+ obj.foo = obj.p.length.toString();
+ },
+ };
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="foo"/>' +
+ '<field name="p"/>' +
+ '</form>',
+ archs: {
+ 'partner,false,list':
+ '<tree editable="bottom">' +
+ '<field name="int_field" widget="handle"/>' +
+ '<field name="display_name" required="1"/>' +
+ '</tree>',
+ },
+ });
+
+ assert.strictEqual(onchangeNum, 1, "one onchange happens when form is opened");
+ assert.strictEqual(form.$('[name="foo"]').val(), "0", "onchange worked there is 0 line");
+
+ // Add one line
+ await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
+ form.$('.o_field_one2many input:first').focus();
+ await testUtils.nextTick();
+ form.$('.o_field_one2many input:first').val('first line').trigger('input');
+ await testUtils.nextTick();
+ await testUtils.dom.click(form.$('input[name="foo"]'));
+ assert.strictEqual(onchangeNum, 2, "one onchange happens when a line is added");
+ assert.strictEqual(form.$('[name="foo"]').val(), "1", "onchange worked there is 1 line");
+
+ // Drag and drop second line before first one (with 1 draft and invalid line)
+ await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
+ await testUtils.dom.dragAndDrop(
+ form.$('.ui-sortable-handle').eq(0),
+ form.$('.o_data_row').last(),
+ {position: 'bottom'}
+ );
+ assert.strictEqual(onchangeNum, 3, "one onchange happens when lines are resequenced");
+ assert.strictEqual(form.$('[name="foo"]').val(), "1", "onchange worked there is 1 line");
+
+ // Add a second line
+ await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
+ form.$('.o_field_one2many input:first').focus();
+ await testUtils.nextTick();
+ form.$('.o_field_one2many input:first').val('second line').trigger('input');
+ await testUtils.nextTick();
+ await testUtils.dom.click(form.$('input[name="foo"]'));
+ assert.strictEqual(onchangeNum, 4, "one onchange happens when a line is added");
+ assert.strictEqual(form.$('[name="foo"]').val(), "2", "onchange worked there is 2 lines");
+
+ form.destroy();
+ });
+
QUnit.test('if the focus is on the discard button, hitting ESCAPE should discard', async function (assert) {
assert.expect(1);
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index ddf340f62df..4175a7171aa 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -15,6 +15,7 @@ var widgetRegistry = require('web.widget_registry');
var Widget = require('web.Widget');
var _t = core._t;
+var createActionManager = testUtils.createActionManager;
var createView = testUtils.createView;
@@ -982,6 +983,47 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('editable list view: check that controlpanel buttons are updating when groupby applied', async function (assert) {
+ assert.expect(4);
+
+ this.data.foo.fields.foo = {string: "Foo", type: "char", required:true};
+
+ var actionManager = await createActionManager({
+ actions: [{
+ id: 11,
+ name: 'Partners Action 11',
+ res_model: 'foo',
+ type: 'ir.actions.act_window',
+ views: [[3, 'list']],
+ search_view_id: [9, 'search'],
+ }],
+ archs: {
+ 'foo,3,list': '<tree editable="top"><field name="display_name"/><field name="foo"/></tree>',
+
+ 'foo,9,search': '<search>'+
+ '<filter string="candle" name="itsName" context="{\'group_by\': \'foo\'}"/>' +
+ '</search>',
+ },
+ data: this.data,
+ });
+
+ await actionManager.doAction(11);
+ await testUtils.dom.click(actionManager.$('.o_list_button_add'));
+
+ assert.isNotVisible(actionManager.$('.o_list_button_add'),
+ "create button should be invisible");
+ assert.isVisible(actionManager.$('.o_list_button_save'), "save button should be visible");
+
+ await testUtils.dom.click(actionManager.$('.o_dropdown_toggler_btn:contains("Group By")'));
+ await testUtils.dom.click(actionManager.$('.o_group_by_menu .o_menu_item a:contains("candle")'));
+
+ assert.isNotVisible(actionManager.$('.o_list_button_add'), "create button should be invisible");
+ assert.isNotVisible(actionManager.$('.o_list_button_save'),
+ "save button should be invisible after applying groupby");
+
+ actionManager.destroy();
+ });
+
QUnit.test('selection changes are triggered correctly', async function (assert) {
assert.expect(8);
diff --git a/addons/web_editor/static/lib/summernote/summernote.js b/addons/web_editor/static/lib/summernote/summernote.js
index 0f9dfaef656..5cebec404d9 100644
--- a/addons/web_editor/static/lib/summernote/summernote.js
+++ b/addons/web_editor/static/lib/summernote/summernote.js
@@ -147,7 +147,7 @@ var dialog = renderer.create('<div class="modal" aria-hidden="false" tabindex="-
(options.title
? ' <div class="modal-header">' +
' <h4 class="modal-title">' + options.title + '</h4>' +
- ' <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
+ ' <button type="button" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
' </div>' : ''),
' <div class="modal-body">' + options.body + '</div>',
(options.footer
@@ -229,11 +229,16 @@ var ui = {
onDialogHidden: function ($dialog, handler) {
$dialog.one('hidden.bs.modal', handler);
},
- showDialog: function ($dialog) {
+ showDialog: function ($dialog, onClose) {
+ $dialog.find('.close').on('click', function (ev) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ onClose();
+ });
$dialog.modal('show');
},
hideDialog: function ($dialog) {
- $dialog.modal('hide');
+ $dialog.detach().modal('hide');
},
createLayout: function ($note, options) {
var $editor = (options.airMode ? ui.airEditor([
@@ -5986,7 +5991,7 @@ var LinkDialog = /** @class */ (function () {
deferred.reject();
}
});
- _this.ui.showDialog(_this.$dialog);
+ _this.ui.showDialog(_this.$dialog, _this.destroy.bind(_this));
}).promise();
};
/**
@@ -6174,7 +6179,7 @@ var ImageDialog = /** @class */ (function () {
deferred.reject();
}
});
- _this.ui.showDialog(_this.$dialog);
+ _this.ui.showDialog(_this.$dialog, _this.destroy.bind(_this));
});
};
return ImageDialog;
@@ -6464,7 +6469,7 @@ var VideoDialog = /** @class */ (function () {
deferred.reject();
}
});
- _this.ui.showDialog(_this.$dialog);
+ _this.ui.showDialog(_this.$dialog, _this.destroy.bind(_this));
});
};
return VideoDialog;
@@ -6530,7 +6535,7 @@ var HelpDialog = /** @class */ (function () {
_this.context.triggerEvent('dialog.shown');
deferred.resolve();
});
- _this.ui.showDialog(_this.$dialog);
+ _this.ui.showDialog(_this.$dialog, _this.destroy.bind(_this));
}).promise();
};
HelpDialog.prototype.show = function () {
diff --git a/addons/web_editor/static/src/js/backend/field_html.js b/addons/web_editor/static/src/js/backend/field_html.js
index 14f6d59b05c..40456961aeb 100644
--- a/addons/web_editor/static/src/js/backend/field_html.js
+++ b/addons/web_editor/static/src/js/backend/field_html.js
@@ -296,7 +296,7 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
resolver();
};
- this.$iframe.one('load', function onLoad() {
+ this.$iframe.on('load', function onLoad() {
var _avoidDoubleLoad = ++avoidDoubleLoad;
ajax.loadAsset(self.nodeOptions.cssReadonly).then(function (asset) {
if (_avoidDoubleLoad !== avoidDoubleLoad) {
diff --git a/addons/web_editor/static/src/js/wysiwyg/plugin/bullet.js b/addons/web_editor/static/src/js/wysiwyg/plugin/bullet.js
index dc2a444384b..6a516dd2ad5 100644
--- a/addons/web_editor/static/src/js/wysiwyg/plugin/bullet.js
+++ b/addons/web_editor/static/src/js/wysiwyg/plugin/bullet.js
@@ -165,10 +165,10 @@ var BulletPlugin = AbstractPlugin.extend({
// convert ul <-> ol
var ul;
+ $(ol).removeClass('o_checklist');
if (sorted === 'checklist' && current === "ul") {
ul = ol;
} else if (sorted === 'ul' && current === 'checklist') {
- $(ol).removeClass('o_checklist');
ul = ol;
} else {
ul = this.document.createElement(sorted === "ol" ? "ol" : "ul");
@@ -321,7 +321,7 @@ var BulletPlugin = AbstractPlugin.extend({
var self = this;
var nodes = [];
var isWithinElem;
- var ancestor = range.commonAncestor();
+ var ancestor = range.commonAncestor() || this.editable;
var $dom = $(ancestor);
if (!dom.isList(ancestor)) {
@@ -344,7 +344,8 @@ var BulletPlugin = AbstractPlugin.extend({
if (!$dom.length) {
// if the selection is contained in a single HTML node, we indent
// the first ancestor 'content block' (P, H1, PRE, ...) or TD
- $dom = $(range.sc).closest(this.options.styleTags.join(',') + ',td');
+ var nodes = this.context.invoke('HelperPlugin.getSelectedNodes');
+ $dom = $(this._filterEditableFormatNodes(nodes));
}
}
diff --git a/addons/web_editor/static/src/js/wysiwyg/plugin/media.js b/addons/web_editor/static/src/js/wysiwyg/plugin/media.js
index b11c15b2564..71365e4348f 100644
--- a/addons/web_editor/static/src/js/wysiwyg/plugin/media.js
+++ b/addons/web_editor/static/src/js/wysiwyg/plugin/media.js
@@ -44,6 +44,7 @@ var MediaPlugin = AbstractPlugin.extend({
},
mousePosition: {},
+ _modalOpen: false,
//--------------------------------------------------------------------------
// Public
@@ -53,6 +54,10 @@ var MediaPlugin = AbstractPlugin.extend({
* Open the image dialog and listen to its saved/closed events.
*/
showImageDialog: function () {
+ if (this._modalOpen) {
+ return;
+ }
+ this._modalOpen = true;
this.context.invoke('editor.saveRange');
var media = this.context.invoke('editor.restoreTarget');
@@ -79,6 +84,7 @@ var MediaPlugin = AbstractPlugin.extend({
});
mediaDialog.on('closed', this, function () {
this.context.invoke('editor.restoreRange');
+ this._modalOpen = false;
});
mediaDialog.open();
},
diff --git a/addons/web_editor/static/src/js/wysiwyg/plugin/text.js b/addons/web_editor/static/src/js/wysiwyg/plugin/text.js
index 16e22939cd4..f2834c531f7 100644
--- a/addons/web_editor/static/src/js/wysiwyg/plugin/text.js
+++ b/addons/web_editor/static/src/js/wysiwyg/plugin/text.js
@@ -229,8 +229,11 @@ var TextPlugin = AbstractPlugin.extend({
$(br).before(emptyText).remove();
range = this.context.invoke('editor.setRange', emptyText, 0, emptyText, 1);
} else {
+ var offset = range.so;
this.context.invoke('HelperPlugin.insertTextInline', '\u200B');
- range.eo += 1;
+ range = this.context.invoke('editor.createRange');
+ range.so = offset;
+ range.eo = offset + 1;
}
range.select();
}
diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
index 545f06a38e2..198dbd14244 100644
--- a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
+++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
@@ -10,6 +10,7 @@ var fonts = require('wysiwyg.fonts');
var ImageOptimizeDialog = require('wysiwyg.widgets.image_optimize_dialog').ImageOptimizeDialog;
var utils = require('web.utils');
var Widget = require('web.Widget');
+var session = require('web.session');
var QWeb = core.qweb;
var _t = core._t;
diff --git a/addons/web_editor/static/tests/wysiwyg_tests.js b/addons/web_editor/static/tests/wysiwyg_tests.js
index e03fb3888fb..7e2c35d2dad 100644
--- a/addons/web_editor/static/tests/wysiwyg_tests.js
+++ b/addons/web_editor/static/tests/wysiwyg_tests.js
@@ -110,7 +110,7 @@ QUnit.test('Magic wand', async function (assert) {
});
QUnit.test('Font style', function (assert) {
- assert.expect(56);
+ assert.expect(58);
return weTestUtils.createWysiwyg({
debug: false,
@@ -283,6 +283,19 @@ QUnit.test('Font style', function (assert) {
end: 'p:contents()[3]->2',
},
},
+ {
+ name: "Click BOLD: bold -> normal (at start of dom)",
+ content: '<p><b>abc</b></p>',
+ start: 'b:contents()[0]->0',
+ do: function () {
+ $btnBold.mousedown().click();
+ },
+ test: {
+ content: '<p>\u200B<b>abc</b></p>',
+ start: 'p:contents()[0]->1',
+ end: 'p:contents()[0]->1',
+ },
+ },
/* ITALIC */
{
name: "Click ITALIC: bold -> bold + italic",
@@ -2227,7 +2240,7 @@ QUnit.test('Align', function (assert) {
});
QUnit.test('Indent/outdent', function (assert) {
- assert.expect(18);
+ assert.expect(20);
return weTestUtils.createWysiwyg({
debug: false,
@@ -2269,6 +2282,21 @@ QUnit.test('Indent/outdent', function (assert) {
start: 'p:eq(1):contents()[0]->1',
},
},
+ {
+ name: "Click INDENT: li -> indented li",
+ content: '<p>aaa</p><p>bbb</p><p>ccc</p><p>ddd</p>',
+ start: 'p:eq(1):contents()[0]->0',
+ end: 'p:eq(3):contents()[0]->3',
+ do: function () {
+ $dropdownPara.mousedown().click();
+ $btnIndent.mousedown().click();
+ },
+ test: {
+ content: '<p>aaa</p><p style="margin-left: 1.5em;">bbb</p><p style="margin-left: 1.5em;">ccc</p><p style="margin-left: 1.5em;">ddd</p>',
+ start: 'p:eq(1):contents()[0]->0',
+ end: 'p:eq(3):contents()[0]->3',
+ },
+ },
/* OUTDENT */
{
name: "Click OUTDENT: indented p -> p",
diff --git a/addons/web_tour/static/src/js/tour_manager.js b/addons/web_tour/static/src/js/tour_manager.js
index 09abee2e767..d117c24f919 100644
--- a/addons/web_tour/static/src/js/tour_manager.js
+++ b/addons/web_tour/static/src/js/tour_manager.js
@@ -213,6 +213,9 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
* @returns {boolean} true if a tip was found and activated/updated
*/
_check_for_tooltip: function (tip, tour_name) {
+ if (tip === undefined) {
+ return true;
+ }
if ($('body').hasClass('o_ui_blocked')) {
this._deactivate_tip(tip);
this._log.push("blockUI is preventing the tip to be consumed");
diff --git a/addons/website/models/res_users.py b/addons/website/models/res_users.py
index a84eaaeb3c6..8902e4c34b6 100644
--- a/addons/website/models/res_users.py
+++ b/addons/website/models/res_users.py
@@ -2,8 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
-from odoo import api, fields, models, tools, _
-from odoo.exceptions import ValidationError
+from odoo import api, fields, models, tools
from odoo.http import request
_logger = logging.getLogger(__name__)
@@ -12,8 +11,8 @@ _logger = logging.getLogger(__name__)
class ResUsers(models.Model):
_inherit = 'res.users'
- website_id = fields.Many2one('website', related='partner_id.website_id', store=True, related_sudo=False, readonly=True)
-
+ website_id = fields.Many2one('website', related='partner_id.website_id', store=True, related_sudo=False, readonly=False)
+
_sql_constraints = [
# Partial constraint, complemented by unique index (see below). Still
# useful to keep because it provides a proper error message when a
@@ -51,7 +50,7 @@ class ResUsers(models.Model):
@api.model_cr_context
def _auto_init(self):
result = super(ResUsers, self)._auto_init()
- # Use unique index to implement unique constraint per website, even if website_id is null
+ # Use unique index to implement unique constraint per website, even if website_id is null
# (not possible using a constraint)
tools.create_unique_index(self._cr, 'res_users_login_key_unique_website_index',
self._table, ['login', 'COALESCE(website_id,-1)'])
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index bbc8489cf47..5202798fb97 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -149,14 +149,17 @@ class Website(models.Model):
@api.multi
def write(self, values):
+ public_user_to_change_websites = self.env['website']
self._handle_favicon(values)
self._get_languages.clear_cache(self)
if 'company_id' in values and 'user_id' not in values:
- company = self.env['res.company'].browse(values['company_id'])
- values['user_id'] = company._get_public_user().id
+ public_user_to_change_websites = self.filtered(lambda w: w.user_id.company_id.id != values['company_id'])
+ if public_user_to_change_websites:
+ company = self.env['res.company'].browse(values['company_id'])
+ super(Website, public_user_to_change_websites).write(dict(values, user_id=company._get_public_user().id))
- result = super(Website, self).write(values)
+ result = super(Website, self - public_user_to_change_websites).write(values)
if 'cdn_activated' in values or 'cdn_url' in values or 'cdn_filters' in values:
# invalidate the caches from static node at compile time
self.env['ir.qweb'].clear_caches()
@@ -185,6 +188,8 @@ class Website(models.Model):
self.homepage_id = self.env['website.page'].search([('website_id', '=', self.id),
('key', '=', standard_homepage.key)])
+ # prevent /-1 as homepage URL
+ self.homepage_id.url = '/'
# Bootstrap default menu hierarchy, create a new minimalist one if no default
default_menu = self.env.ref('website.main_menu')
diff --git a/addons/website/static/src/js/content/snippets.animation.js b/addons/website/static/src/js/content/snippets.animation.js
index 7f57eb81f2c..33e1e01ebcc 100644
--- a/addons/website/static/src/js/content/snippets.animation.js
+++ b/addons/website/static/src/js/content/snippets.animation.js
@@ -860,6 +860,10 @@ registry.gallerySlider = publicWidget.Widget.extend({
destroy: function () {
this._super.apply(this, arguments);
+ if (!this.$indicator) {
+ return;
+ }
+
this.$prev.prependTo(this.$indicator);
this.$next.appendTo(this.$indicator);
this.$carousel.off('.gallery_slider');
diff --git a/addons/website/static/src/js/content/website_root.js b/addons/website/static/src/js/content/website_root.js
index 22c71890643..612265124cd 100644
--- a/addons/website/static/src/js/content/website_root.js
+++ b/addons/website/static/src/js/content/website_root.js
@@ -19,6 +19,7 @@ var WebsiteRoot = publicRootData.PublicRoot.extend({
}),
custom_events: _.extend({}, publicRootData.PublicRoot.prototype.custom_events || {}, {
'ready_to_clean_for_save': '_onWidgetsStopRequest',
+ seo_object_request: '_onSeoObjectRequest',
}),
/**
@@ -111,6 +112,36 @@ var WebsiteRoot = publicRootData.PublicRoot.extend({
window.location.href = _.str.sprintf("/website/lang/%(lang)s?r=%(url)s%(hash)s", redirect);
},
/**
+ /**
+ * Checks information about the page SEO object.
+ *
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onSeoObjectRequest: function (ev) {
+ var res = this._unslugHtmlDataObject('seo-object');
+ ev.data.callback(res);
+ },
+ /**
+ * Returns a model/id object constructed from html data attribute.
+ *
+ * @private
+ * @param {string} dataAttr
+ * @returns {Object} an object with 2 keys: model and id, or null
+ * if not found
+ */
+ _unslugHtmlDataObject: function (dataAttr) {
+ var repr = $('html').data(dataAttr);
+ var match = repr && repr.match(/(.+)\((\d+),(.*)\)/);
+ if (!match) {
+ return null;
+ }
+ return {
+ model: match[1],
+ id: match[2] | 0,
+ };
+ },
+ /**
* @todo review
* @private
*/
diff --git a/addons/website/static/src/js/menu/seo.js b/addons/website/static/src/js/menu/seo.js
index 332fde71a4f..b65137ecc8f 100644
--- a/addons/website/static/src/js/menu/seo.js
+++ b/addons/website/static/src/js/menu/seo.js
@@ -129,17 +129,17 @@ var Keyword = Widget.extend({
_updateTitle: function () {
var $title = this.$('.js_seo_keyword_title');
if (this.htmlPage.isInTitle(this.keyword)) {
- $title.css('visibility','visible');
+ $title.css('visibility', 'visible');
} else {
- $title.css('visibility','hidden');
+ $title.css('visibility', 'hidden');
}
},
_updateDescription: function () {
var $description = this.$('.js_seo_keyword_description');
if (this.htmlPage.isInDescription(this.keyword)) {
- $description.css('visibility','visible');
+ $description.css('visibility', 'visible');
} else {
- $description.css('visibility','hidden');
+ $description.css('visibility', 'hidden');
}
},
});
@@ -210,7 +210,7 @@ var Preview = Widget.extend({
this.url = options.url;
this.description = options.description || _t("The description will be generated by search engines based on page content unless you specify one.");
if (this.description.length > 160) {
- this.description = this.description.substring(0,159) + '…';
+ this.description = this.description.substring(0, 159) + '…';
}
this._super(parent);
},
@@ -289,19 +289,19 @@ var HtmlPage = Class.extend(mixins.PropertiesMixin, {
return $('body').children().not('.oe_seo_configuration').find('h2').text();
},
isInBody: function (text) {
- return new RegExp(WORD_SEPARATORS_REGEX+text+WORD_SEPARATORS_REGEX, 'gi').test(this.bodyText());
+ return new RegExp(WORD_SEPARATORS_REGEX + text + WORD_SEPARATORS_REGEX, 'gi').test(this.bodyText());
},
isInTitle: function (text) {
- return new RegExp(WORD_SEPARATORS_REGEX+text+WORD_SEPARATORS_REGEX, 'gi').test(this.title());
+ return new RegExp(WORD_SEPARATORS_REGEX + text + WORD_SEPARATORS_REGEX, 'gi').test(this.title());
},
isInDescription: function (text) {
- return new RegExp(WORD_SEPARATORS_REGEX+text+WORD_SEPARATORS_REGEX, 'gi').test(this.description());
+ return new RegExp(WORD_SEPARATORS_REGEX + text + WORD_SEPARATORS_REGEX, 'gi').test(this.description());
},
isInHeading1: function (text) {
- return new RegExp(WORD_SEPARATORS_REGEX+text+WORD_SEPARATORS_REGEX, 'gi').test(this.heading1());
+ return new RegExp(WORD_SEPARATORS_REGEX + text + WORD_SEPARATORS_REGEX, 'gi').test(this.heading1());
},
isInHeading2: function (text) {
- return new RegExp(WORD_SEPARATORS_REGEX+text+WORD_SEPARATORS_REGEX, 'gi').test(this.heading2());
+ return new RegExp(WORD_SEPARATORS_REGEX + text + WORD_SEPARATORS_REGEX, 'gi').test(this.heading2());
},
});
@@ -415,7 +415,7 @@ var MetaTitleDescription = Widget.extend({
_renderPreview: function () {
var indexed = this.isIndexed;
var preview = "";
- if (indexed){
+ if (indexed) {
preview = new Preview(this, {
title: this.getTitle(),
description: this.getDescription(),
@@ -447,7 +447,7 @@ var MetaKeywords = Widget.extend({
start: function () {
var self = this;
this.$input = this.$('input[name=website_meta_keywords]');
- this.keywordList = new KeywordList(this, { htmlPage: this.htmlPage });
+ this.keywordList = new KeywordList(this, {htmlPage: this.htmlPage});
this.keywordList.on('list-full', this, function () {
self.$input.attr({
readonly: 'readonly',
@@ -494,7 +494,7 @@ var MetaKeywords = Widget.extend({
model: 'website',
method: 'get_languages',
args: [[context.website_id]],
- }).then( function (data) {
+ }).then(function (data) {
self.$('#language-box').html(core.qweb.render('Configurator.language_promote', {
'language': data,
'def_lang': context.lang
@@ -508,7 +508,7 @@ var MetaKeywords = Widget.extend({
* @param {boolean} removed: a keyword is about to be removed,
* we need to exclude it from the count
*/
- _updateTable : function (removed) {
+ _updateTable: function (removed) {
var min = removed ? 1 : 0;
if (this.keywordList.keywords().length > min) {
this.$('table').show();
@@ -562,7 +562,7 @@ var MetaImageSelector = Widget.extend({
_setDescription: function (description) {
this.metaDescription = description || _t("The description will be generated by social media based on page content unless you specify one.");
if (this.metaDescription.length > 160) {
- this.metaDescription = this.metaDescription.substring(0,159) + '…';
+ this.metaDescription = this.metaDescription.substring(0, 159) + '…';
}
},
@@ -731,19 +731,25 @@ var SeoConfigurator = Dialog.extend({
});
},
getMainObject: function () {
- var repr = $('html').data('main-object');
- var m = repr.match(/(.+)\((\d+),(.*)\)/);
- if (!m) {
- return null;
- } else {
- return {
- model: m[1],
- id: m[2]|0
- };
- }
+ var mainObject;
+ this.trigger_up('main_object_request', {
+ callback: function (value) {
+ mainObject = value;
+ },
+ });
+ return mainObject;
+ },
+ getSeoObject: function () {
+ var seoObject;
+ this.trigger_up('seo_object_request', {
+ callback: function (value) {
+ seoObject = value;
+ },
+ });
+ return seoObject;
},
loadMetaData: function () {
- var obj = this.getMainObject();
+ var obj = this.getSeoObject() || this.getMainObject();
return new Promise(function (resolve, reject) {
if (!obj) {
// return Promise.reject(new Error("No main_object was found."));
@@ -751,7 +757,7 @@ var SeoConfigurator = Dialog.extend({
} else {
var fields = ['website_meta_title', 'website_meta_description', 'website_meta_keywords'
,'website_meta_og_img'];
- if (obj.model === 'website.page'){
+ if (obj.model === 'website.page') {
fields.push('website_indexed');
fields.push('website_id');
}
@@ -772,7 +778,7 @@ var SeoConfigurator = Dialog.extend({
});
},
saveMetaData: function (data) {
- var obj = this.getMainObject();
+ var obj = this.getSeoObject() || this.getMainObject();
if (!obj) {
return Promise.reject();
} else {
diff --git a/addons/website/static/tests/tours/rte.js b/addons/website/static/tests/tours/rte.js
index e29f40e45ed..716562389e7 100644
--- a/addons/website/static/tests/tours/rte.js
+++ b/addons/website/static/tests/tours/rte.js
@@ -13,6 +13,7 @@ var ready = Promise.all([domReady, session.is_bound, ajax.loadXML()]);
tour.register('rte_translator', {
test: true,
+ url: '/',
wait_for: ready,
url: '/',
}, [{
diff --git a/addons/website/tests/test_get_current_website.py b/addons/website/tests/test_get_current_website.py
index a71126cb484..57d85887b4d 100644
--- a/addons/website/tests/test_get_current_website.py
+++ b/addons/website/tests/test_get_current_website.py
@@ -112,3 +112,10 @@ class TestGetCurrentWebsite(TransactionCase):
self.assertEqual(Website._get_current_website_id('site-1.com:81', False), website2.id)
self.assertEqual(Website._get_current_website_id('site-1.com:82', False), website1.id)
self.assertEqual(Website._get_current_website_id('site-1.com', False), website1.id)
+
+ def test_02_signup_user_website_id(self):
+ website = self.env['website'].browse(1)
+ website.specific_user_account = True
+
+ user = self.env['res.users'].create({'website_id': website.id, 'login': 'sad@mail.com', 'name': 'Hope Fully'})
+ self.assertTrue(user.website_id == user.partner_id.website_id == website)
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 583e019e20e..4dd19528c0d 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -169,7 +169,8 @@
'data-view-xmlid': xmlid if editable or translatable else None,
'data-viewid': viewid if editable or translatable else None,
'data-main-object': repr(main_object) if editable or translatable else None,
- 'data-oe-company-name': res_company.name
+ 'data-seo-object': repr(seo_object) if seo_object else None,
+ 'data-oe-company-name': res_company.name,
}"/>
</xpath>
<xpath expr="//head" position="before">
@@ -178,8 +179,9 @@
<t t-set="additional_title" t-value="main_object.name"/>
</t>
<t t-set="default_title"> <t t-if="additional_title"><t t-raw="additional_title"/> | </t><t t-raw="(website or res_company).name"/> </t>
- <t t-if="main_object and 'website_meta_title' in main_object and main_object.website_meta_title">
- <t t-set="title" t-value="main_object.website_meta_title"/>
+ <t t-set="seo_object" t-value="seo_object or main_object"/>
+ <t t-if="seo_object and 'website_meta_title' in seo_object and seo_object.website_meta_title">
+ <t t-set="title" t-value="seo_object.website_meta_title"/>
</t>
<t t-else="">
<t t-set="title" t-value="default_title"></t>
@@ -189,17 +191,18 @@
</xpath>
<xpath expr="//head/meta[last()]" position="after">
<meta name="generator" content="Odoo"/>
- <t t-set="website_meta" t-value="main_object and main_object.get_website_meta() or {}"/>
+ <t t-set="website_meta" t-value="seo_object and seo_object.get_website_meta() or {}"/>
<meta name="default_title" t-att-content="default_title" groups="website.group_website_designer"/>
<meta t-if="main_object and 'website_indexed' in main_object
and not main_object.website_indexed" name="robots" content="noindex"/>
- <t t-set="meta_description" t-value="main_object and 'website_meta_description' in main_object
- and main_object.website_meta_description or website_meta_description or website_meta.get('meta_description', '')"/>
- <t t-set="meta_keywords" t-value="main_object and 'website_meta_keywords' in main_object
- and main_object.website_meta_keywords or website_meta_keywords"/>
+ <t t-set="seo_object" t-value="seo_object or main_object"/>
+ <t t-set="meta_description" t-value="seo_object and 'website_meta_description' in seo_object
+ and seo_object.website_meta_description or website_meta_description or website_meta.get('meta_description', '')"/>
+ <t t-set="meta_keywords" t-value="seo_object and 'website_meta_keywords' in seo_object
+ and seo_object.website_meta_keywords or website_meta_keywords"/>
<meta t-if="meta_description or editable" name="description" t-att-content="meta_description"/>
<meta t-if="meta_keywords or editable" name="keywords" t-att-content="meta_keywords"/>
- <t t-if="main_object">
+ <t t-if="seo_object">
<meta name="default_description" t-att-content="website_meta_description or website_meta.get('meta_description')" groups="website.group_website_designer"/>
<!-- OpenGraph tags for Facebook sharing -->
<t t-set="opengraph_meta" t-value="website_meta.get('opengraph_meta')"/>
@@ -399,7 +402,7 @@
</div>
</li>
<li groups="website.group_website_publisher" class="list-inline-item">
- <t t-set="url_return" t-value="url_for('', '[lang]') + '?' + keep_query()"/>
+ <t t-set="url_return" t-value="quote_plus(url_for('', '[lang]') + '?' + keep_query())"/>
<a class="d-none d-sm-block" t-attf-href="/web#action=base.action_view_base_language_install&amp;website_id=#{website.id if website else ''}&amp;url_return=#{url_return}">
<i class="fa fa-plus-circle"/>
Add a language...
diff --git a/addons/website_blog/views/snippets.xml b/addons/website_blog/views/snippets.xml
index f8564f6b254..8702928d12d 100644
--- a/addons/website_blog/views/snippets.xml
+++ b/addons/website_blog/views/snippets.xml
@@ -74,5 +74,8 @@
</div>
</div>
</xpath>
+ <xpath expr="//div[@data-js='layout_column']" position="attributes">
+ <attribute name="data-exclude" add=".s_latest_posts, .s_latest_posts_big_picture" separator=","/>
+ </xpath>
</template>
</odoo>
diff --git a/addons/website_crm_partner_assign/tests/test_partner_assign.py b/addons/website_crm_partner_assign/tests/test_partner_assign.py
index 720c9afb65f..dce67bb4f03 100644
--- a/addons/website_crm_partner_assign/tests/test_partner_assign.py
+++ b/addons/website_crm_partner_assign/tests/test_partner_assign.py
@@ -16,6 +16,19 @@ class TestPartnerAssign(TransactionCase):
def setUp(self):
super(TestPartnerAssign, self).setUp()
+ self.customer_uk = self.env['res.partner'].create({
+ 'name': 'Nigel',
+ 'country_id': self.env.ref('base.uk').id,
+ 'city': 'Birmingham',
+ 'zip': 'B46 3AG',
+ 'street': 'Cannon Hill Park',
+ })
+ self.lead_uk = self.env['crm.lead'].create({
+ 'type': 'opportunity',
+ 'name': 'Office Design and Architecture',
+ 'partner_id': self.customer_uk.id
+ })
+
def geo_find(addr):
return {
'Wavre, Belgium': (50.7158956, 4.6128075),
@@ -46,7 +59,7 @@ class TestPartnerAssign(TransactionCase):
"partner_weight": 10,
})
- lead = self.env.ref('crm.crm_case_21')
+ lead = self.lead_uk
# In order to test find nearest Partner functionality and assign to opportunity,
# I Set Geo Lattitude and Longitude according to partner address.
diff --git a/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml b/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml
index bd7c88b3e43..21b70a11112 100644
--- a/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml
+++ b/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml
@@ -164,13 +164,9 @@
<t t-if="any([p.website_published for p in partner.implemented_partner_ids])">
<h3 id="references">References</h3>
<div t-foreach="partner.implemented_partner_ids" t-if="reference.website_published" t-as="reference" class="media mt-3">
- <a t-attf-href="/customers/#{slug(reference)}">
- <span t-field="reference.image_medium" t-options='{"widget": "image", "class": "d-block mr-3"}'/>
- </a>
+ <span t-field="reference.image_medium" t-options='{"widget": "image", "class": "d-block mr-3"}'/>
<div class="media-body" style="min-height: 64px;">
- <a t-attf-href="/customers/#{slug(reference)}">
- <span t-field="reference.self"/>
- </a>
+ <span t-field="reference.self"/>
<div t-field='reference.website_short_description'/>
</div>
</div>
diff --git a/addons/website_customer/views/website_customer_templates.xml b/addons/website_customer/views/website_customer_templates.xml
index da6e9aa8a33..9cd668d901d 100644
--- a/addons/website_customer/views/website_customer_templates.xml
+++ b/addons/website_customer/views/website_customer_templates.xml
@@ -234,4 +234,13 @@
</t>
</template>
+<template id="references_block_href" inherit_id="website_crm_partner_assign.references_block" name="Partner References Block">
+ <xpath expr="//div/span" position="replace">
+ <a t-attf-href="/customers/#{slug(reference)}">$0</a>
+ </xpath>
+ <xpath expr="//div[hasclass('media-body')]/span" position="replace">
+ <a t-attf-href="/customers/#{slug(reference)}">$0</a>
+ </xpath>
+</template>
+
</odoo>
diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py
index 1174f91d40e..edd01ebbe19 100644
--- a/addons/website_event/controllers/main.py
+++ b/addons/website_event/controllers/main.py
@@ -148,14 +148,15 @@ class WebsiteEventController(http.Controller):
values = {
'event': event,
- 'main_object': event
}
if '.' not in page:
page = 'website_event.%s' % page
try:
- request.website.get_template(page)
+ # Every event page view should have its own SEO.
+ values['seo_object'] = request.website.get_template(page)
+ values['main_object'] = event
except ValueError:
# page not found
values['path'] = re.sub(r"^website_event\.", '', page)
diff --git a/addons/website_event/views/event_templates.xml b/addons/website_event/views/event_templates.xml
index 4231efb2907..36228dfb580 100644
--- a/addons/website_event/views/event_templates.xml
+++ b/addons/website_event/views/event_templates.xml
@@ -529,20 +529,20 @@
</strong>
</h4>
<div class="row d-none d-lg-flex">
- <div class="col-4">
+ <div class="col-md">
<strong>Name</strong>
</div>
- <div class="col-4">
+ <div class="col-md">
<strong>Email</strong>
</div>
- <div class="col-4">
- <strong>Phone <small>(optional)</small></strong>
+ <div class="col-md">
+ <strong>Phone <small class='lbl_optional'>(optional)</small></strong>
</div>
</div>
<t t-foreach="range(1, ticket['quantity'] + 1)" t-as="att_counter" name="attendee_loop">
<t t-set="counter" t-value="counter + 1"/>
<div class="row py-2 o_wevent_registration_list_attendees">
- <div class="col-12 col-lg-4">
+ <div class="col-md">
<div class="mb-1 d-lg-none">
<strong>Name</strong>
</div>
@@ -550,7 +550,7 @@
<input class='form-control' type='text' t-attf-name="#{counter}-name" required="This field is required" t-att-placeholder="attendee_placeholder %counter"/>
</div>
- <div class="col-12 col-lg-4">
+ <div class="col-md">
<div class="mt-1 d-lg-none">
<strong>Email</strong>
</div>
@@ -558,9 +558,9 @@
<input class='form-control' type='email' t-attf-name="#{counter}-email" required="This field is required"/>
</div>
</div>
- <div class="col-12 col-lg-4">
+ <div class="col-md">
<div class="mt-1 d-lg-none">
- <strong>Phone <small>(optional)</small></strong>
+ <strong>Phone <small class='lbl_optional'>(optional)</small></strong>
</div>
<input class='form-control' type='tel' t-attf-name="#{counter}-phone"/>
</div>
diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py
index ed2cb30df2e..bd62e519338 100644
--- a/addons/website_forum/controllers/main.py
+++ b/addons/website_forum/controllers/main.py
@@ -439,7 +439,7 @@ class WebsiteForum(WebsiteProfile):
values = self._prepare_user_values(forum=forum)
values.update({
- 'posts_ids': posts_to_validate_ids,
+ 'posts_ids': posts_to_validate_ids.sudo(),
'queue_type': 'validation',
})
@@ -459,7 +459,7 @@ class WebsiteForum(WebsiteProfile):
values = self._prepare_user_values(forum=forum)
values.update({
- 'posts_ids': flagged_posts_ids,
+ 'posts_ids': flagged_posts_ids.sudo(),
'queue_type': 'flagged',
'flagged_queue_active': 1,
})
@@ -478,7 +478,7 @@ class WebsiteForum(WebsiteProfile):
values = self._prepare_user_values(forum=forum)
values.update({
- 'posts_ids': offensive_posts_ids,
+ 'posts_ids': offensive_posts_ids.sudo(),
'queue_type': 'offensive',
})
diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py
index 75039efff89..bf0effb67c6 100644
--- a/addons/website_forum/models/forum.py
+++ b/addons/website_forum/models/forum.py
@@ -251,10 +251,11 @@ class Post(models.Model):
operator = operator == "=" and '!=' or '='
value = True
- if self._uid == SUPERUSER_ID:
+ user = self.env.user
+ # Won't impact sitemap, search() in converter is forced as public user
+ if user._is_admin():
return [(1, '=', 1)]
- user = self.env['res.users'].browse(self._uid)
req = """
SELECT p.id
FROM forum_post p
diff --git a/addons/website_forum/models/res_users.py b/addons/website_forum/models/res_users.py
index 67326175ac9..35a5fc32545 100644
--- a/addons/website_forum/models/res_users.py
+++ b/addons/website_forum/models/res_users.py
@@ -1,27 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import hashlib
-
-from datetime import datetime
-
-from werkzeug import urls
-
from odoo import api, fields, models
class Users(models.Model):
_inherit = 'res.users'
- def __init__(self, pool, cr):
- init_res = super(Users, self).__init__(pool, cr)
- type(self).SELF_WRITEABLE_FIELDS = list(
- set(
- self.SELF_WRITEABLE_FIELDS +
- ['country_id', 'city', 'website', 'website_description', 'website_published']))
- type(self).SELF_READABLE_FIELDS = type(self).SELF_READABLE_FIELDS + ['karma']
- return init_res
-
create_date = fields.Datetime('Create Date', readonly=True, index=True)
forum_waiting_posts_count = fields.Integer('Waiting post', compute="_get_user_waiting_post")
diff --git a/addons/website_forum/static/src/scss/website_forum.scss b/addons/website_forum/static/src/scss/website_forum.scss
index d0cfce94ec6..018bf679e8a 100644
--- a/addons/website_forum/static/src/scss/website_forum.scss
+++ b/addons/website_forum/static/src/scss/website_forum.scss
@@ -22,6 +22,7 @@ $bronze: #eea91e;
font-style: italic;
font-weight: lighter;
}
+ word-wrap: break-word;
}
.vote {
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml
index 24fdc26feb3..f971c068f8e 100644
--- a/addons/website_forum/views/website_forum.xml
+++ b/addons/website_forum/views/website_forum.xml
@@ -60,7 +60,7 @@
<div id="wrap" t-attf-class="container #{website_forum_action}">
<div class="row">
- <div class="col o_wprofile_email_validation_container flex-grow-1 mb16 mt16">
+ <div class="col o_wprofile_email_validation_container mb16 mt16 col-lg-9">
<t t-call="website_profile.email_validation_banner">
<t t-set="redirect_url" t-value="'/forum/%s' % forum.id"/>
<t t-set="send_validation_email_message" t-value="'Click here to send a verification email allowing you to participate to the forum.'"/>
@@ -68,66 +68,65 @@
</t>
<t t-raw="0"/>
</div>
- <t t-if="not is_profile_page">
- <div class="col-lg-3" id="right-column">
- <a role="button" type="button" class="btn btn-primary btn-lg btn-block mb-3" t-attf-href="/forum/#{slug(forum)}/ask">Ask a Question</a>
- <div class="card bg-secondary" t-if="question">
- <h6 class="card-header"><b>Writer</b></h6>
- <div class="card-body o_card_people">
- <img class="o_wforum_people_photo_container d-block mx-auto mb-3" t-attf-src="/web/image/res.users/#{question.create_uid.id}/image_medium"/>
- <h6 class="text-center">
- <b><i class="fa fa-diamond text-secondary"/> <t t-esc="question.create_uid.sudo().karma"/></b>
- <b><a t-attf-href="/forum/#{slug(forum)}/user/#{question.create_uid.sudo().id}" t-field="question.create_uid" t-options='{"widget": "contact", "fields": ["name", "country_id"]}'/></b>
- </h6>
- </div>
- </div>
- <div class="card my-3 bg-secondary" t-if="question and not len(question.tag_ids)&lt;=0">
- <h6 class="card-header">
- <b>
- <t t-if="len(question.tag_ids)&lt;=1">
- Tag
- </t>
- <t t-else="">
- Tags
- </t>
- </b>
+ <div class="col-lg-3" id="right-column" t-if="not is_profile_page">
+ <a role="button" type="button" class="btn btn-primary btn-lg btn-block mb-3" t-attf-href="/forum/#{slug(forum)}/ask">Ask a Question</a>
+ <div class="card bg-secondary" t-if="question">
+ <h6 class="card-header"><b>Writer</b></h6>
+ <div class="card-body o_card_people">
+ <img class="o_wforum_people_photo_container d-block mx-auto mb-3" t-attf-src="/web/image/res.users/#{question.create_uid.id}/image_medium"/>
+ <h6 class="text-center">
+ <b><i class="fa fa-diamond text-secondary"/> <t t-esc="question.create_uid.sudo().karma"/></b>
+ <b><a t-attf-href="/forum/#{slug(forum)}/user/#{question.create_uid.sudo().id}" t-field="question.create_uid" t-options='{"widget": "contact", "fields": ["name", "country_id"]}'/></b>
</h6>
- <div class="card-body">
- <t t-foreach="question.tag_ids" t-as="tag">
- <a t-attf-href="/forum/#{ slug(forum) }/tag/#{ tag.id }/questions" class="badge badge-primary" t-field="tag.name"/>
- </t>
- </div>
</div>
- <div class="card my-3 bg-secondary" t-if="question">
- <h6 class="card-header"><b>Keep Informed</b></h6>
- <div class="card-body o_subscribe_post">
- <t t-call="website_mail.follow">
- <t t-set="object" t-value="question"/>
- <t t-set="div_class" t-value="'mx-auto'"/>
+ </div>
+ <div class="card my-3 bg-secondary" t-if="question and not len(question.tag_ids)&lt;=0">
+ <h6 class="card-header">
+ <b>
+ <t t-if="len(question.tag_ids)&lt;=1">
+ Tag
</t>
- <div class="text-center mt-3">
- <strong><t t-raw="len(question.message_follower_ids)"/> follower(s)</strong>
- </div>
- </div>
+ <t t-else="">
+ Tags
+ </t>
+ </b>
+ </h6>
+ <div class="card-body">
+ <t t-foreach="question.tag_ids" t-as="tag">
+ <a t-attf-href="/forum/#{ slug(forum) }/tag/#{ tag.id }/questions" class="badge badge-primary" t-field="tag.name"/>
+ </t>
</div>
- <div class="card my-3 bg-secondary" id="about_forum">
- <h6 class="card-header"><b>About This Community</b></h6>
- <div class="card-body">
- <span t-field="forum.description"/>
- <a t-if="not header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}/faq" class="btn btn-primary mt-3">Read Guidelines</a>
- <a t-if="header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}" class="btn btn-primary mt-3"> Back to <span t-field="forum.name"/></a>
+ </div>
+ <div class="card my-3 bg-secondary" t-if="question">
+ <h6 class="card-header"><b>Keep Informed</b></h6>
+ <div class="card-body o_subscribe_post">
+ <t t-call="website_mail.follow">
+ <t t-set="object" t-value="question"/>
+ <t t-set="div_class" t-value="'mx-auto'"/>
+ </t>
+ <div class="text-center mt-3">
+ <strong><t t-raw="len(question.message_follower_ids)"/> follower(s)</strong>
</div>
</div>
- <div class="card my-3 bg-secondary" id="moderation_tools" t-if="user.karma>=forum.karma_moderate">
- <h6 class="card-header"><b>Moderation Tools</b></h6>
- <div class="card-body">
- <a t-attf-href="/forum/#{slug(forum)}/validation_queue" class="text-dark"><i class="fa fa-check-square-o mr-2 text-primary"/><strong><t t-esc="forum.count_posts_waiting_validation"/></strong> waiting for validation</a>
- <hr style="margin-top:4px;margin-bottom:0;padding-bottom:4px;" />
- <a t-attf-href="/forum/#{slug(forum)}/flagged_queue" class="text-dark"><i class="fa fa-flag mr-2 text-primary"/><strong id="count_flagged_posts"><t t-esc="forum.count_flagged_posts"/></strong> post<t t-if="forum.count_flagged_posts&gt;1">s</t> flagged</a>
- </div>
+ </div>
+ <div class="card my-3 bg-secondary" id="about_forum">
+ <h6 class="card-header"><b>About This Community</b></h6>
+ <div class="card-body">
+ <span t-field="forum.description"/>
+ <a t-if="not header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}/faq" class="btn btn-primary mt-3">Read Guidelines</a>
+ <a t-if="header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}" class="btn btn-primary mt-3"> Back to <span t-field="forum.name"/></a>
</div>
</div>
- </t>
+ <div class="card my-3 bg-secondary" id="moderation_tools" t-if="user.karma>=forum.karma_moderate">
+ <h6 class="card-header"><b>Moderation Tools</b></h6>
+ <div class="card-body">
+ <a t-attf-href="/forum/#{slug(forum)}/validation_queue" class="text-dark"><i class="fa fa-check-square-o mr-2 text-primary"/><strong><t t-esc="forum.count_posts_waiting_validation"/></strong> waiting for validation</a>
+ <hr style="margin-top:4px;margin-bottom:0;padding-bottom:4px;" />
+ <a t-attf-href="/forum/#{slug(forum)}/flagged_queue" class="text-dark"><i class="fa fa-flag mr-2 text-primary"/><strong id="count_flagged_posts"><t t-esc="forum.count_flagged_posts"/></strong> post<t t-if="forum.count_flagged_posts&gt;1">s</t> flagged</a>
+ </div>
+ </div>
+ </div>
+
</div>
</div>
<div class="oe_structure" id="oe_structure_website_forum_header_1"/>
@@ -707,7 +706,7 @@
</div>
</t>
</div>
- <div t-attf-class="col question-block mb-5 o_post_content" role="alert">
+ <div t-attf-class="col-11 question-block mb-5 o_post_content" role="alert">
<span t-if="question.has_validated_answer" title="Question has accepted answer" role="img" aria-label="Question has accepted answer"><i class="fa fa-check-circle text-success"/> <b class="text-success">Resolved</b></span>
<h1 t-attf-class="#{not question.active and 'text-danger' or 'text-primary'}">
<t t-esc="question.name"/>
@@ -862,7 +861,7 @@
</div>
</t>
</div>
- <div class="col">
+ <div class="col-11">
<t t-if="answer.can_comment">
<div class="dropdown dropleft float-right">
<a class="btn bg-white" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@@ -1004,14 +1003,14 @@
<t t-set="inDropdown" t-value="True"/>
<t t-set="icon" t-value="'fa-trash-o text-primary'"/>
<t t-set="classes" t-value="'comment_delete border-bottom'"/>
- <t t-set="karma" t-value="not object.can_unlink and answer.karma_unlink or 0"/>
+ <t t-set="karma" t-value="not object.can_unlink and object.karma_unlink or 0"/>
</t>
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(object) + '/comment/' + slug(message) + '/convert_to_answer'"/>
<t t-set="label">Convert as a answer</t>
<t t-set="inDropdown" t-value="True"/>
<t t-set="icon" t-value="'fa-magic text-primary'"/>
- <t t-set="karma" t-value="not object.can_comment_convert and answer.karma_comment_convert or 0"/>
+ <t t-set="karma" t-value="not object.can_comment_convert and object.karma_comment_convert or 0"/>
</t>
</div>
</div>
@@ -1179,7 +1178,7 @@
</div>
</div>
<t t-if="question.parent_id">
- <div t-foreach="question" t-as="answer" class="col">
+ <div t-foreach="question" t-as="answer" class="col-md-10 col-sm-8">
<t t-call="website_forum.moderation_display_post_answer"/>
<small class="text-muted">
<i t-attf-class="fa fa-user" role="img" aria-label="Question" />
diff --git a/addons/website_links/static/src/js/website_links_menu.js b/addons/website_links/static/src/js/website_links_menu.js
index 5ebc7f98c77..6f673223720 100644
--- a/addons/website_links/static/src/js/website_links_menu.js
+++ b/addons/website_links/static/src/js/website_links_menu.js
@@ -8,9 +8,9 @@ odoo.define('website_links.website_links_menu', function (require) {
'use strict';
var publicWidget = require('web.public.widget');
+var websiteNavbarData = require('website.navbar');
-publicWidget.registry.websiteLinksMenu = publicWidget.Widget.extend({
- selector: '#o_website_links_share_page',
+var WebsiteLinksMenu = publicWidget.Widget.extend({
/**
* @override
@@ -20,4 +20,7 @@ publicWidget.registry.websiteLinksMenu = publicWidget.Widget.extend({
return this._super.apply(this, arguments);
},
});
+
+websiteNavbarData.websiteNavbarRegistry.add(WebsiteLinksMenu, '#o_website_links_share_page');
+
});
diff --git a/addons/website_mail/models/mail_message.py b/addons/website_mail/models/mail_message.py
index 34609160f1d..683990e1e67 100644
--- a/addons/website_mail/models/mail_message.py
+++ b/addons/website_mail/models/mail_message.py
@@ -26,6 +26,11 @@ class MailMessage(models.Model):
description = fields.Char(compute="_compute_description", help='Message description: either the subject, or the beginning of the body')
website_published = fields.Boolean(string='Published', help="Visible on the website as a comment", copy=False)
+ @api.model
+ def _non_employee_message_domain(self):
+ domain = super(MailMessage, self)._non_employee_message_domain()
+ return expression.AND([domain, [('website_published', '=', True)]])
+
@api.multi
def _compute_description(self):
for message in self:
diff --git a/addons/website_profile/controllers/main.py b/addons/website_profile/controllers/main.py
index e46e72b2cfb..c3b9ba54941 100644
--- a/addons/website_profile/controllers/main.py
+++ b/addons/website_profile/controllers/main.py
@@ -191,13 +191,13 @@ class WebsiteProfile(http.Controller):
# ---------------------------------------------------
def _prepare_all_users_values(self, user):
return {
- 'position': user.karma_position,
'id': user.id,
'name': user.name,
- 'company_name': user.company_id.name,
+ 'company_name': user.partner_id.company_name,
'rank': user.rank_id.name,
'karma': user.karma,
'badge_count': len(user.badge_ids),
+ 'website_published': user.website_published
}
@http.route(['/profile/users',
@@ -212,20 +212,56 @@ class WebsiteProfile(http.Controller):
dom = expression.AND([['|', ('name', 'ilike', search_term), ('company_id.name', 'ilike', search_term)], dom])
user_count = User.sudo().search_count(dom)
- page_count = math.ceil(user_count / self._users_per_page)
- pager = request.website.pager(url="/profile/users", total=user_count, page=page, step=self._users_per_page,
- scope=page_count if page_count < self._pager_max_pages else self._pager_max_pages)
- users = User.sudo().search(dom, limit=self._users_per_page, offset=pager['offset'], order='karma DESC')
- user_values = [self._prepare_all_users_values(user) for user in users]
+ if user_count:
+ page_count = math.ceil(user_count / self._users_per_page)
+ pager = request.website.pager(url="/profile/users", total=user_count, page=page, step=self._users_per_page,
+ scope=page_count if page_count < self._pager_max_pages else self._pager_max_pages)
+
+ users = User.sudo().search(dom, limit=self._users_per_page, offset=pager['offset'], order='karma DESC')
+ user_values = [self._prepare_all_users_values(user) for user in users]
+
+ # Get karma position for users (only website_published)
+ position_domain = [('karma', '>', 1), ('website_published', '=', True)]
+ position_map = self._get_users_karma_position(position_domain, users.ids)
+ for user in user_values:
+ user['position'] = position_map.get(user['id'], 0)
+
+ values = {
+ 'top3_users': user_values[:3] if not search_term and page == 1 else None,
+ 'users': user_values[3:] if not search_term and page == 1 else user_values,
+ 'pager': pager
+ }
+ else:
+ values = {'top3_users': [], 'users': [], 'pager': dict(page_count=0)}
- values = {
- 'top3_users': user_values[:3] if not search_term and page == 1 else None,
- 'users': user_values[3:] if not search_term and page == 1 else user_values,
- 'pager': pager
- }
return request.render("website_profile.users_page_main", values)
+ def _get_users_karma_position(self, domain, user_ids):
+ if not user_ids:
+ return {}
+
+ Users = request.env['res.users']
+ where_query = Users._where_calc(domain)
+ Users._apply_ir_rules(where_query, 'read')
+ from_clause, where_clause, where_clause_params = where_query.get_sql()
+
+ # we search on every user in the DB to get the real positioning (not the one inside the subset)
+ # then, we filter to get only the subset.
+ query = """
+ SELECT sub.id, sub.karma_position
+ FROM (
+ SELECT "res_users"."id", row_number() OVER (ORDER BY res_users.karma DESC) AS karma_position
+ FROM {from_clause}
+ WHERE {where_clause}
+ ) sub
+ WHERE sub.id IN %s
+ """.format(from_clause=from_clause, where_clause=where_clause)
+
+ request.env.cr.execute(query, where_clause_params + [tuple(user_ids)])
+
+ return {item['id']: item['karma_position'] for item in request.env.cr.dictfetchall()}
+
# User and validation
# --------------------------------------------------
diff --git a/addons/website_profile/models/res_users.py b/addons/website_profile/models/res_users.py
index 5bf49be0a78..fd440665f44 100644
--- a/addons/website_profile/models/res_users.py
+++ b/addons/website_profile/models/res_users.py
@@ -14,6 +14,15 @@ VALIDATION_KARMA_GAIN = 3
class Users(models.Model):
_inherit = 'res.users'
+ def __init__(self, pool, cr):
+ init_res = super(Users, self).__init__(pool, cr)
+ type(self).SELF_WRITEABLE_FIELDS = list(
+ set(
+ self.SELF_WRITEABLE_FIELDS +
+ ['country_id', 'city', 'website', 'website_description', 'website_published']))
+ type(self).SELF_READABLE_FIELDS = type(self).SELF_READABLE_FIELDS + ['karma']
+ return init_res
+
@api.model
def _generate_profile_token(self, user_id, email):
"""Return a token for email validation. This token is valid for the day
diff --git a/addons/website_profile/views/website_profile.xml b/addons/website_profile/views/website_profile.xml
index 1f0d22e4123..d03428d9287 100644
--- a/addons/website_profile/views/website_profile.xml
+++ b/addons/website_profile/views/website_profile.xml
@@ -207,7 +207,7 @@
<div class="col-12 col-md-4 col-lg-3">
<div t-attf-class="d-flex align-items-start h-100 #{'justify-content-between' if (request.env.user == user) else 'justify-content-around' }">
<div class="o_wprofile_pict d-inline-block mb-3 mb-md-0" t-attf-style="background-image:url(/web/image/res.users/#{user.id}/image)"/>
- <a class="btn btn-primary d-inline-block d-md-none" t-if="request.env.user == user and validation_email_done" t-attf-href="/profile/edit?url_param=#{edit_button_url_param}">
+ <a class="btn btn-primary d-inline-block d-md-none" t-if="request.env.user == user and user.karma != 0" t-attf-href="/profile/edit?url_param=#{edit_button_url_param}">
<i class="fa fa-pencil mr-1"/>EDIT
</a>
</div>
@@ -219,14 +219,14 @@
<h1 class="o_card_people_name">
<span t-field="user.name"/><small t-if="user.karma == 0"> (not verified)</small>
</h1>
- <a class="btn btn-primary d-none d-md-inline-block" t-if="request.env.user == user and validation_email_done or request.env.user._is_admin()" t-attf-href="/profile/edit?url_param=#{edit_button_url_param}">
+ <a class="btn btn-primary d-none d-md-inline-block" t-if="request.env.user == user and user.karma != 0 or request.env.user._is_admin()" t-attf-href="/profile/edit?url_param=#{edit_button_url_param}">
<i class="fa fa-pencil mr-2"/>EDIT PROFILE
</a>
</div>
<div class="d-flex flex-column justify-content-center flex-grow-1 mb-0 mb-md-5">
- <div t-if="user.company_id" class="lead mb-2">
- <i class="fa fa-building-o fa-fw mr-1"/><span t-field="user.company_id"/>
+ <div t-if="user.partner_id.company_name" class="lead mb-2">
+ <i class="fa fa-building-o fa-fw mr-1"/><span t-field="user.partner_id.company_name"/>
</div>
<div t-if="user.city or user.country_id" class="lead mb-2">
<i class="fa fa-map-marker fa-fw mr-1"/>
@@ -478,11 +478,14 @@
<t t-call="website_profile.top3_user_card"></t>
</div>
</div>
- <table class="table table-sm">
+ <table class="table table-sm" t-if='users'>
<tr t-foreach="users" t-as="user" t-attf-onclick="location.href='/profile/user/#{user['id']}';" class="o_wprofile_pointer bg-white">
<t t-call="website_profile.all_user_card"/>
</tr>
</table>
+ <t else=''>
+ <div class='alert alert-warning'>No results matched</div>
+ </t>
<div class="form-inline justify-content-center">
<t t-call="website_profile.pager_nobox"/>
</div>
@@ -497,6 +500,7 @@
<img class="position-absolute" t-attf-src="/website_profile/static/src/img/rank_#{user['position']}.svg" alt="User rank" style="bottom: 0; right: -10px"/>
</div>
<h3 class="mt-2 mb-0" t-esc="user['name']"></h3>
+ <span class="badge badge-danger font-weight-normal px-2" t-if="not user['website_published']">Unpublished</span>
<strong class="text-muted" t-esc="user['rank']"/>
</div>
<div class="row mx-0 o_wprofile_top3_card_footer text-nowrap">
@@ -517,6 +521,9 @@
<span class="font-weight-bold" t-esc="user['name']"/><br/>
<span class="text-muted font-weight-bold" t-esc="user['rank']"></span>
</td>
+ <td t-if="not user['website_published']" class="align-middle font-weight-bold text-right text-nowrap">
+ <span class="badge badge-danger font-weight-normal px-2 py-1 m-1">Unpublished</span>
+ </td>
<td class="align-middle font-weight-bold text-right text-nowrap">
<b t-esc="user['karma']"/> <span class="text-muted small font-weight-bold">XP</span>
</td>
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index 8228027fced..95b3e3bfd74 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -310,13 +310,9 @@ class SaleOrder(models.Model):
for order in self:
order._portal_ensure_token()
composer_form_view_id = self.env.ref('mail.email_compose_message_wizard_form').id
- try:
- default_template = self.env.ref('website_sale.mail_template_sale_cart_recovery', raise_if_not_found=False)
- default_template_id = default_template.id if default_template else False
- template_id = (self.filtered('website_id') == self and
- self.mapped('website_id')[-1:1].cart_recovery_mail_template_id.id) or default_template_id
- except:
- template_id = False
+
+ template_id = self._get_cart_recovery_template().id
+
return {
'type': 'ir.actions.act_window',
'view_type': 'form',
@@ -336,6 +332,34 @@ class SaleOrder(models.Model):
}
@api.multi
+ def _get_cart_recovery_template(self):
+ """
+ Return the cart recovery template record for a set of orders.
+ If they all belong to the same website, we return the website-specific template;
+ otherwise we return the default template.
+ If the default is not found, the empty ['mail.template'] is returned.
+ """
+ websites = self.mapped('website_id')
+ template = websites.cart_recovery_mail_template_id if len(websites) == 1 else False
+ template = template or self.env.ref('website_sale.mail_template_sale_cart_recovery', raise_if_not_found=False)
+ return template or self.env['mail.template']
+
+ @api.multi
+ def _cart_recovery_email_send(self):
+ """Send the cart recovery email on the current recordset,
+ making sure that the portal token exists to avoid broken links, and marking the email as sent.
+ Similar method to action_recovery_email_send, made to be called in automated actions.
+ Contrary to the former, it will use the website-specific template for each order."""
+ sent_orders = self.env['sale.order']
+ for order in self:
+ template = order._get_cart_recovery_template()
+ if template:
+ order._portal_ensure_token()
+ template.send_mail(order.id)
+ sent_orders |= order
+ sent_orders.write({'cart_recovery_email_sent': True})
+
+ @api.multi
def get_base_url(self):
"""When using multi-website, we want the user to be redirected to the
most appropriate website if possible."""
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index 78161683b78..f257454e0f8 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -140,7 +140,6 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, {
'change form.js_attributes input, form.js_attributes select': '_onChangeAttribute',
'mouseup form.js_add_cart_json label': '_onMouseupAddCartLabel',
'touchend form.js_add_cart_json label': '_onMouseupAddCartLabel',
- 'change .css_attribute_color input': '_onChangeColorAttribute',
'click .show_coupon': '_onClickShowCoupon',
'submit .o_wsale_products_searchbar_form': '_onSubmitSaleSearch',
'change select[name="country_id"]': '_onChangeCountry',
@@ -148,7 +147,7 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, {
'click .toggle_summary': '_onToggleSummary',
'click #add_to_cart, #buy_now, #products_grid .o_wsale_product_btn .a-submit': 'async _onClickAdd',
'click input.js_product_change': 'onChangeVariant',
- // dirty fix: prevent options modal events to be triggered and bubbled
+ 'change .js_main_product [data-attribute_exclusions]': 'onChangeVariant',
'change oe_optional_products_modal [data-attribute_exclusions]': 'onChangeVariant',
}),
@@ -162,6 +161,9 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, {
this._changeCountry = _.debounce(this._changeCountry.bind(this), 500);
this.isWebsite = true;
+
+ delete this.events['change .main_product:not(.in_cart) input.js_quantity'];
+ delete this.events['change [data-attribute_exclusions]'];
},
/**
* @override
@@ -412,10 +414,14 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, {
* @override
* @private
*/
- _updateProductImage: function ($productContainer, displayImage, productId, productTemplateId, new_carousel) {
+ _updateProductImage: function ($productContainer, displayImage, productId, productTemplateId, new_carousel, isCombinationPossible) {
var $img;
var $carousel = $productContainer.find('#o-carousel-product');
+ if (isCombinationPossible === undefined) {
+ isCombinationPossible = this.isSelectedVariantAllowed;
+ }
+
if (new_carousel) {
// When using the web editor, don't reload this or the images won't
// be able to be edited depending on if this is done loading before
@@ -458,8 +464,7 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, {
}
}
- $carousel.toggleClass('css_not_available',
- $productContainer.find('.js_main_product').hasClass('css_not_available'));
+ $carousel.toggleClass('css_not_available', !isCombinationPossible);
},
/**
* @private
@@ -646,13 +651,6 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, {
* @private
* @param {Event} ev
*/
- _onChangeColorAttribute: function (ev) { // highlight selected color
- this._changeColorAttribute();
- },
- /**
- * @private
- * @param {Event} ev
- */
_onClickShowCoupon: function (ev) {
$(ev.currentTarget).hide();
$('.coupon_form').removeClass('d-none');
@@ -692,20 +690,22 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, {
$('.ship_to_other').toggle(!$(ev.currentTarget).prop('checked'));
},
/**
- * Dirty fix: prevent options modal events to be triggered and bubbled
- * Also write the properties of the form elements in the DOM to prevent the
+ * Toggles the add to cart button depending on the possibility of the
+ * current combination.
+ *
+ * @override
+ */
+ _toggleDisable: function ($parent, isCombinationPossible) {
+ VariantMixin._toggleDisable.apply(this, arguments);
+ $parent.find("#add_to_cart").toggleClass('disabled', !isCombinationPossible);
+ },
+ /**
+ * Write the properties of the form elements in the DOM to prevent the
* current selection from being lost when activating the web editor.
*
* @override
*/
onChangeVariant: function (ev, data) {
- var $originPath = ev.originalEvent && Array.isArray(ev.originalEvent.path) ? $(ev.originalEvent.path) : $();
- var $container = data && data.$container ? data.$container : $();
- if ($originPath.add($container).hasClass('oe_optional_products_modal')) {
- ev.stopPropagation();
- return;
- }
-
var $component = $(ev.currentTarget).closest('.js_product');
$component.find('input').each(function () {
var $el = $(this);
diff --git a/addons/website_sale/tests/test_website_sale_cart_recovery.py b/addons/website_sale/tests/test_website_sale_cart_recovery.py
index 3dd3206506a..215bee56188 100644
--- a/addons/website_sale/tests/test_website_sale_cart_recovery.py
+++ b/addons/website_sale/tests/test_website_sale_cart_recovery.py
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import tagged
-from odoo.tests.common import HttpCase
+from odoo.tests.common import HttpCase, TransactionCase
@tagged('post_install', '-at_install')
@@ -11,3 +11,110 @@ class TestWebsiteSaleCartRecovery(HttpCase):
def test_01_shop_cart_recovery_tour(self):
"""The goal of this test is to make sure cart recovery works."""
self.start_tour("/", 'shop_cart_recovery', login="portal")
+
+
+@tagged('post_install', '-at_install')
+class TestWebsiteSaleCartRecoveryServer(TransactionCase):
+
+ def setUp(self):
+ res = super(TestWebsiteSaleCartRecoveryServer, self).setUp()
+
+ self.customer = self.env['res.partner'].create({
+ 'name': 'a',
+ 'email': 'a@example.com',
+ })
+ self.recovery_template_default = self.env.ref('website_sale.mail_template_sale_cart_recovery')
+ self.recovery_template_custom1 = self.recovery_template_default.copy()
+ self.recovery_template_custom2 = self.recovery_template_default.copy()
+
+ self.website0 = self.env['website'].create({
+ 'name': 'web0',
+ 'cart_recovery_mail_template_id': self.recovery_template_default.id,
+ })
+ self.website1 = self.env['website'].create({
+ 'name': 'web1',
+ 'cart_recovery_mail_template_id': self.recovery_template_custom1.id,
+ })
+ self.website2 = self.env['website'].create({
+ 'name': 'web2',
+ 'cart_recovery_mail_template_id': self.recovery_template_custom2.id,
+ })
+ self.so0 = self.env['sale.order'].create({
+ 'partner_id': self.customer.id,
+ 'website_id': self.website0.id,
+ 'is_abandoned_cart': True,
+ 'cart_recovery_email_sent': False,
+ })
+ self.so1 = self.env['sale.order'].create({
+ 'partner_id': self.customer.id,
+ 'website_id': self.website1.id,
+ 'is_abandoned_cart': True,
+ 'cart_recovery_email_sent': False,
+ })
+ self.so2 = self.env['sale.order'].create({
+ 'partner_id': self.customer.id,
+ 'website_id': self.website2.id,
+ 'is_abandoned_cart': True,
+ 'cart_recovery_email_sent': False,
+ })
+
+ return res
+
+ def test_cart_recovery_mail_template(self):
+ """Make sure that we get the correct cart recovery templates to send."""
+ self.assertEqual(
+ self.so1._get_cart_recovery_template(),
+ self.recovery_template_custom1,
+ "We do not return the correct mail template"
+ )
+ self.assertEqual(
+ self.so2._get_cart_recovery_template(),
+ self.recovery_template_custom2,
+ "We do not return the correct mail template"
+ )
+ # Orders that belong to different websites; we should get the default template
+ self.assertEqual(
+ (self.so1 + self.so2)._get_cart_recovery_template(),
+ self.recovery_template_default,
+ "We do not return the correct mail template"
+ )
+
+ def test_cart_recovery_mail_template_send(self):
+ """The goal of this test is to make sure cart recovery works."""
+ orders = self.so0 + self.so1 + self.so2
+
+ self.assertFalse(
+ any(orders.mapped('cart_recovery_email_sent')),
+ "The recovery mail should not have been sent yet."
+ )
+ self.assertFalse(
+ any(orders.mapped('access_token')),
+ "There should not be an access token yet."
+ )
+
+ orders._cart_recovery_email_send()
+
+ self.assertTrue(
+ all(orders.mapped('cart_recovery_email_sent')),
+ "The recovery mail should have been sent."
+ )
+ self.assertTrue(
+ all(orders.mapped('access_token')),
+ "All tokens should have been generated."
+ )
+
+ sent_mail = {}
+ for order in orders:
+ mail = self.env["mail.mail"].search([
+ ('record_name', '=', order['name'])
+ ])
+ sent_mail.update({order: mail})
+
+ self.assertTrue(
+ all(len(sent_mail[order]) == 1 for order in orders),
+ "Each cart recovery mail has been sent exactly once."
+ )
+ self.assertTrue(
+ all(order.access_token in sent_mail[order].body for order in orders),
+ "Each mail should contain the access token of the corresponding SO."
+ )
diff --git a/addons/website_sale_coupon/tests/test_shop_sale_coupon.py b/addons/website_sale_coupon/tests/test_shop_sale_coupon.py
index e34b6904638..76df76157a1 100644
--- a/addons/website_sale_coupon/tests/test_shop_sale_coupon.py
+++ b/addons/website_sale_coupon/tests/test_shop_sale_coupon.py
@@ -1,14 +1,23 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import HttpCase, tagged
+from odoo.addons.sale.tests.test_sale_product_attribute_value_config import TestSaleProductAttributeValueSetup
@tagged('post_install', '-at_install')
-class TestUi(HttpCase):
+class TestUi(TestSaleProductAttributeValueSetup, HttpCase):
post_install = True
at_install = False
+ def setUp(self):
+ super(TestUi, self).setUp()
+
+ # set currency to not rely on demo data and avoid possible race condition
+ self.currency_ratio = 1.0
+ pricelist = self.env.ref('product.list0')
+ pricelist.currency_id = self._setup_currency(self.currency_ratio)
+
def test_01_admin_shop_sale_coupon_tour(self):
# pre enable "Show # found" option to avoid race condition...
self.env.ref("website_sale.search_count_box").write({"active": True})
diff --git a/addons/website_sale_delivery/controllers/main.py b/addons/website_sale_delivery/controllers/main.py
index 73c89e4943f..c623ebab63c 100644
--- a/addons/website_sale_delivery/controllers/main.py
+++ b/addons/website_sale_delivery/controllers/main.py
@@ -87,14 +87,13 @@ class WebsiteSaleDelivery(WebsiteSale):
Monetary = request.env['ir.qweb.field.monetary']
carrier_id = int(post['carrier_id'])
currency = order.currency_id
- delivery_price = order.order_line.filtered(lambda line: line.is_delivery).price_unit
if order:
return {
'status': order.delivery_rating_success,
'error_message': order.delivery_message,
'carrier_id': carrier_id,
- 'is_free_delivery': not bool(delivery_price),
- 'new_amount_delivery': Monetary.value_to_html(delivery_price, {'display_currency': currency}),
+ 'is_free_delivery': not bool(order.amount_delivery),
+ 'new_amount_delivery': Monetary.value_to_html(order.amount_delivery, {'display_currency': currency}),
'new_amount_untaxed': Monetary.value_to_html(order.amount_untaxed, {'display_currency': currency}),
'new_amount_tax': Monetary.value_to_html(order.amount_tax, {'display_currency': currency}),
'new_amount_total': Monetary.value_to_html(order.amount_total, {'display_currency': currency}),
diff --git a/addons/website_sale_product_configurator/static/src/js/product_configurator_modal.js b/addons/website_sale_product_configurator/static/src/js/product_configurator_modal.js
index a6ebf95d32c..940d7e04a63 100644
--- a/addons/website_sale_product_configurator/static/src/js/product_configurator_modal.js
+++ b/addons/website_sale_product_configurator/static/src/js/product_configurator_modal.js
@@ -24,10 +24,6 @@ OptionalProductsModal.include({
this._super.apply(this, arguments);
this.isWebsite = params.isWebsite;
- if (this.isWebsite) {
- delete this.events['click button.js_add_cart_json'];
- }
-
this.dialogClass = 'oe_optional_products_modal' + (params.isWebsite ? ' oe_website_sale' : '');
},
/**
diff --git a/addons/website_sale_product_configurator/static/src/js/website_sale_options.js b/addons/website_sale_product_configurator/static/src/js/website_sale_options.js
index 26e8efaabd3..18558954c0c 100644
--- a/addons/website_sale_product_configurator/static/src/js/website_sale_options.js
+++ b/addons/website_sale_product_configurator/static/src/js/website_sale_options.js
@@ -44,7 +44,7 @@ publicWidget.registry.WebsiteSale.include({
*/
_onOptionsUpdateQuantity: function (quantity) {
this.$form
- .find('input[name="add_qty"]')
+ .find('.js_main_product input[name="add_qty"]')
.first()
.val(quantity)
.trigger('change');
diff --git a/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js b/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js
index 49ab188b294..5a899feb7ed 100644
--- a/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js
+++ b/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js
@@ -162,9 +162,9 @@ publicWidget.registry.ProductWishlist = publicWidget.Widget.extend(VariantMixin,
wSaleUtils.animateClone($('#my_cart'), tr, 25, 40);
if ($('#b2b_wish').is(':checked')) {
- return this._addToCart(product, tr.find('qty').val() || 1);
+ return this._addToCart(product, tr.find('add_qty').val() || 1);
} else {
- var adding_deffered = this._addToCart(product, tr.find('qty').val() || 1);
+ var adding_deffered = this._addToCart(product, tr.find('add_qty').val() || 1);
this._removeWish(e, adding_deffered);
return adding_deffered;
}
diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py
index 5bff0ef6adb..c7e36539ce7 100644
--- a/addons/website_slides/controllers/main.py
+++ b/addons/website_slides/controllers/main.py
@@ -294,7 +294,7 @@ class WebsiteSlides(WebsiteProfile):
domain = request.website.website_domain()
domain = self._build_channel_domain(domain, slide_type=slide_type, my=my, **post)
- order = self._channel_order_by_criterion.get(post.get('sorting', 'date'), 'create_date desc')
+ order = self._channel_order_by_criterion.get(post.get('sorting'))
channels = request.env['slide.channel'].search(domain, order=order)
# channels_layouted = list(itertools.zip_longest(*[iter(channels)] * 4, fillvalue=None))
@@ -460,7 +460,7 @@ class WebsiteSlides(WebsiteProfile):
@http.route(['/slides/channel/join'], type='json', auth='public', website=True)
def slide_channel_join(self, channel_id):
if request.website.is_public_user():
- return {'error': 'public_user'}
+ return {'error': 'public_user', 'error_signup_allowed': request.env['res.users'].sudo()._get_signup_invitation_scope() == 'b2c'}
success = request.env['slide.channel'].browse(channel_id).action_add_member()
if not success:
return {'error': 'join_done'}
@@ -587,7 +587,7 @@ class WebsiteSlides(WebsiteProfile):
@http.route('/slides/slide/like', type='json', auth="public", website=True)
def slide_like(self, slide_id, upvote):
if request.website.is_public_user():
- return {'error': 'public_user'}
+ return {'error': 'public_user', 'error_signup_allowed': request.env['res.users'].sudo()._get_signup_invitation_scope() == 'b2c'}
slide_partners = request.env['slide.slide.partner'].sudo().search([
('slide_id', '=', slide_id),
('partner_id', '=', request.env.user.partner_id.id)
diff --git a/addons/website_slides/static/src/js/slides_course_join.js b/addons/website_slides/static/src/js/slides_course_join.js
index 985d8f446c3..002ea049e5f 100644
--- a/addons/website_slides/static/src/js/slides_course_join.js
+++ b/addons/website_slides/static/src/js/slides_course_join.js
@@ -53,7 +53,12 @@ var CourseJoinWidget = publicWidget.Widget.extend({
location.reload();
} else {
if (data.error === 'public_user') {
- self._popoverAlert(self.$el, _.str.sprintf(_t('Please <a href="/web/login?redirect=%s">login</a> to join this course.'), (document.URL)));
+ var message = _t('Please <a href="/web/login?redirect=%s">login</a> to join this course');
+ var signupAllowed = data.error_signup_allowed || false;
+ if (signupAllowed) {
+ message = _t('Please <a href="/web/signup?redirect=%s">create an account</a> to join this course');
+ }
+ self._popoverAlert(self.$el, _.str.sprintf(message, (document.URL)));
} else if (data.error === 'join_done') {
self._popoverAlert(self.$el, _t('You have already joined this channel'));
} else {
diff --git a/addons/website_slides/static/src/js/slides_slide_like.js b/addons/website_slides/static/src/js/slides_slide_like.js
index e85ca928a7f..88bc3db1b77 100644
--- a/addons/website_slides/static/src/js/slides_slide_like.js
+++ b/addons/website_slides/static/src/js/slides_slide_like.js
@@ -55,7 +55,12 @@ var SlideLikeWidget = publicWidget.Widget.extend({
self.$el.find('span.o_wslides_js_slide_like_down span').text(data.dislikes);
} else {
if (data.error === 'public_user') {
- self._popoverAlert(self.$el, _.str.sprintf(_t('Please <a href="/web/login?redirect=%s">login</a> to vote this lesson'), (document.URL)));
+ var message = _t('Please <a href="/web/login?redirect=%s">login</a> to vote this lesson');
+ var signupAllowed = data.error_signup_allowed || false;
+ if (signupAllowed) {
+ message = _t('Please <a href="/web/signup?redirect=%s">create an account</a> to vote this lesson');
+ }
+ self._popoverAlert(self.$el, _.str.sprintf(message, (document.URL)));
} else if (data.error === 'vote_done') {
self._popoverAlert(self.$el, _t('You have already voted for this lesson'));
} else if (data.error === 'slide_access') {
diff --git a/addons/website_slides/views/website_slides_templates_course.xml b/addons/website_slides/views/website_slides_templates_course.xml
index d612215cc0a..e5412a34bd3 100644
--- a/addons/website_slides/views/website_slides_templates_course.xml
+++ b/addons/website_slides/views/website_slides_templates_course.xml
@@ -167,7 +167,7 @@
<i class="fa fa-home"/> Course
</a>
</li>
- <li class="nav-item">
+ <li t-if="channel.allow_comment" class="nav-item">
<a class="nav-link" id="review-tab" data-toggle="pill" href="#review" role="tab" aria-controls="review" aria-selected="false">Review</a>
</li>
</ul>
@@ -176,7 +176,7 @@
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
<t t-if="channel.channel_type == 'training'" t-call="website_slides.course_slides_list"/>
</div>
- <div class="tab-pane fade" id="review" role="tabpanel" aria-labelledby="review-tab">
+ <div t-if="channel.allow_comment" class="tab-pane fade" id="review" role="tabpanel" aria-labelledby="review-tab">
<t t-call="portal.message_thread">
<t t-set="object" t-value="channel"/>
<t t-set="hash" t-value="message_post_hash"/>
@@ -335,9 +335,8 @@
<div t-if="channel.can_publish and category_id" class="o_wslides_slides_list_drag py-2 pr-3">
<i class="fa fa-bars"/>
</div>
- <span t-if="category_id" t-att-class="'text-muted' if not channel.can_upload else ''" t-esc="category['name']"/>
- <em t-elif="channel.can_publish" class="text-muted mb-1">Uncategorized</em>
- <small t-if="not category['total_slides']" class="ml-1 text-muted"><b>(empty)</b></small>
+ <span t-if="category_id" t-att-class="'text-muted' if not channel.can_upload else ''" t-field="category['category'].name"/>
+ <small t-if="not category['total_slides'] and category_id" class="ml-1 text-muted"><b>(empty)</b></small>
</div>
<div t-if="category_id" class="o_text_link d-flex border-left">
<a t-if="channel.can_upload"
diff --git a/addons/website_slides/views/website_slides_templates_homepage.xml b/addons/website_slides/views/website_slides_templates_homepage.xml
index 9df61fe9c44..59db5c942b0 100644
--- a/addons/website_slides/views/website_slides_templates_homepage.xml
+++ b/addons/website_slides/views/website_slides_templates_homepage.xml
@@ -321,7 +321,6 @@
<div class="o_wslides_home_aside">
<div class="row o_wslides_home_aside_title">
<div class="col">
- <a href="/slides" class="float-right">View all</a>
<h5 class="m-0">Latest achievements</h5>
<hr class="mt-2"/>
</div>
@@ -345,7 +344,7 @@
<t t-set="user" t-value="achievement.user_id"/>
</t>
<div style="line-height: 1.3">
- <span class="font-weight-bold" t-esc="achievement.user_id.name"/> achieved <a href="/slides"><span t-esc="achievement.badge_id.name"/></a>
+ <span class="font-weight-bold" t-esc="achievement.user_id.name"/> achieved <span class="font-weight-bold" t-esc="achievement.badge_id.name"/>
</div>
</div>
</template>
diff --git a/doc/_extensions/autojsdoc/parser/parser.py b/doc/_extensions/autojsdoc/parser/parser.py
index a5eb45a8e85..62660514598 100644
--- a/doc/_extensions/autojsdoc/parser/parser.py
+++ b/doc/_extensions/autojsdoc/parser/parser.py
@@ -36,7 +36,7 @@ class ModuleMatcher(Visitor):
'property': {'name': 'define'},
},
}}):
- [module, func] = node['expression']['arguments']
+ [module, *_, func] = node['expression']['arguments']
mod = jsdoc.parse_comments(node.get('comments'), jsdoc.ModuleDoc)
# set module name
mod.set_name(module['value'])
diff --git a/doc/cla/corporate/acsone.md b/doc/cla/corporate/acsone.md
index d4ee1947927..25b0e1b1c8a 100644
--- a/doc/cla/corporate/acsone.md
+++ b/doc/cla/corporate/acsone.md
@@ -27,3 +27,4 @@ Thomas Binsfeld thomas.binsfeld@acsone.eu https://github.com/ThomasBinsfeld
Benjamin Willig benjamin.willig@acsone.eu https://github.com/benwillig
François Honoré francois.honore@acsone.eu https://github.com/acsonefho
Denis Robinet denis.robinet@acsone.eu https://github.com/RobinetDenisAcsone
+Benoit Aimont benoit.aimont@acsone.eu https://github.com/baimont
diff --git a/doc/cla/corporate/ecoservice.md b/doc/cla/corporate/ecoservice.md
new file mode 100644
index 00000000000..b1b1cbe8ff0
--- /dev/null
+++ b/doc/cla/corporate/ecoservice.md
@@ -0,0 +1,21 @@
+Germany, 2019-05-14
+
+ecoservice GbR agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Falk Neubert neubert@ecoservice.de https://github.com/FNeu75
+
+List of contributors:
+
+* Christian Engelhardt c.engelhardt@ecoservice.de https://github.com/c-engelhardt-ecoservice
+* Christian Schöttke c.schoettke@ecoservice.de https://github.com/cschoettke
+* Falk Neubert neubert@ecoservice.de https://github.com/FNeu75
+* Gülhan Celik g.celik@ecoservice.de https://github.com/guelhancelik
+* Jan Brodersen 4rmitxes@gmail.com https://github.com/Armitxes
+* Kamal Prajapati k.prajapati@ecoservice.de https://github.com/kamalprajapati
+* Marcel Much m.much@ecoservice.de https://github.com/mmuch
diff --git a/doc/cla/corporate/savoirfairelinux.md b/doc/cla/corporate/savoirfairelinux.md
index 913e317cb07..04f4563d4b7 100644
--- a/doc/cla/corporate/savoirfairelinux.md
+++ b/doc/cla/corporate/savoirfairelinux.md
@@ -12,17 +12,24 @@ João Alfredo Gama Batista joao.gama@savoirfairelinux.com https://github.com/joa
List of contributors:
-Agathe Mollé agathe.molle@savoirfairelinux.com https://github.com/Ehtaga
-Bruno Joliveau bruno.joliveau@savoirfairelinux.com https://github.com/bjoliveau
-David Dufresne david.dufresne@savoirfairelinux.com https://github.com/dufresnedavid
-Istvan SZALAÏ istvan.szalai@savoirfairelinux.com https://github.com/ventilooo
-João Alfredo Gama Batista joao.gama@savoirfairelinux.com https://github.com/joaoalf
-Julien Jezequel-Breard julien.jezequel-breard@savoirfairelinux.com https://github.com/jjbreard
-Quentin Lavallée-Bourdeau quentin.lavallee@savoirfairelinux.com https://github.com/qtiplb
+
Rim Ben Dhaou rim.bendhaou@savoirfairelinux.com https://github.com/rimbendhaou
-Yasmine El Mrini yasmine.elmrini@savoirfairelinux.com https://github.com/yasmineelmrini
Luis Garcia Ruiz luis.garcia@savoirfairelinux.com https://github.com/eilst
Dimitar Apostolov dimitar.apostolov@savoirfairelinux.com https://github.com/dapostolov-sfl
+Pierre Gault pierre.gault@savoirfairelinux.com https://github.com/gaultp
+Maroua Romdhane maroua.romdhane@savoirfairelinux.com https://github.com/mromdhane
+Jean-François Bolduc jean-francois.bolduc@savoirfairelinux.com https://github.com/fjdrake
+Blaise Frison blaise.frison@savoirfairelinux.com https://github.com/bfrison-sfl
+Larbi Gharib larbi.gharib@savoirfairelinux.com https://github.com/lgharib
+Jananjoy Rajkumar jananjoy.rajkumar@savoirfairelinux.com https://github.com/jananjoy
+Yasmine El Mrini yasmine.elmrini@savoirfairelinux.com https://github.com/yasmineelmrini (up to 2019-05-13)
+Agathe Mollé agathe.molle@savoirfairelinux.com https://github.com/Ehtaga (up to 2019-05-13)
+Bruno Joliveau bruno.joliveau@savoirfairelinux.com https://github.com/bjoliveau (up to 2019-05-13)
+David Dufresne david.dufresne@savoirfairelinux.com https://github.com/dufresnedavid (up to 2019-05-13)
+Istvan SZALAÏ istvan.szalai@savoirfairelinux.com https://github.com/ventilooo (up to 2019-05-13)
+João Alfredo Gama Batista joao.gama@savoirfairelinux.com https://github.com/joaoalf (up to 2019-05-13)
+Julien Jezequel-Breard julien.jezequel-breard@savoirfairelinux.com https://github.com/jjbreard (up to 2019-05-13)
+Quentin Lavallée-Bourdeau quentin.lavallee@savoirfairelinux.com https://github.com/qtiplb (up to 2019-05-13)
Jérome Boisvert-Chouinard jerome.boisvertchouinard@savoirfairelinux.com https://github.com/jbchouinard (up to 2017-10-17)
Julie Moussu julie.mousse@savoirfairelinux.com https://github.com/JulieSFL (up to 2017-10-17)
Pierre Lamarche pierre.lamarche@savoirfairelinux.com https://github.com/plamarche (up to 2017-10-17)
@@ -34,6 +41,5 @@ Jordi Riera jordi.riera@savoirfairelinux.com https://github.com/foutoucour (up t
Julien Roux julien.roux@savoirfairelinux.com https://github.com/jrouxsfl (up to 2017-05-12)
Loïc Faure-Lacroix loic.lacroix@savoirfairelinux.com https://github.com/llacroix (up to 2017-05-12)
Maxime Chambreuil maxime.chambreuil@savoirfairelinux.com https://github.com/max3903 (up to 2017-05-12)
-Pierre Gault pierre.gault@savoirfairelinux.com https://github.com/gaultp (up to 2017-05-12)
Sandy Carter sandy.carter@savoirfairelinux.com https://github.com/bwrsandman (up to 2017-05-12)
Vincent Vinet vincent.vinet@savoirfairelinux.com https://github.com/veloutin (up to 2017-05-12)
diff --git a/doc/cla/corporate/vauxoo.md b/doc/cla/corporate/vauxoo.md
index 235931654a6..2cce9509360 100644
--- a/doc/cla/corporate/vauxoo.md
+++ b/doc/cla/corporate/vauxoo.md
@@ -44,4 +44,5 @@ Alexander Olivares alexander@vauxoo.com https://github.com/alxolivares
Jose Manuel Robles josemanuel@vauxoo.com https://github.com/keylor2906
Erick Birbe erick@vauxoo.com https://github.com/ebirbe
Tomas Alvarez tomas@vauxoo.com https://github.com/tomeyro
+Carmen Liliana Miranda González carmen@vauxoo.com https://github.com/CarmenMiranda
Arturo Flores arturo@vauxoo.com https://github.com/umiphos
diff --git a/doc/cla/individual/0yik.md b/doc/cla/individual/0yik.md
new file mode 100644
index 00000000000..a70e2510a71
--- /dev/null
+++ b/doc/cla/individual/0yik.md
@@ -0,0 +1,11 @@
+Indonesia, 2019-04-24
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Anang Aji Rahmawan aji.abuismail@gmail.com https://github.com/0yik
diff --git a/doc/cla/individual/AfroMonkey.md b/doc/cla/individual/AfroMonkey.md
new file mode 100644
index 00000000000..6a5e4a0d561
--- /dev/null
+++ b/doc/cla/individual/AfroMonkey.md
@@ -0,0 +1,11 @@
+México, 2019-04-03
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Navarro Presas Moisés Alejandro moisalejandro@gmail.com https://github.com/AfroMonkey
diff --git a/doc/cla/individual/gjanssens.md b/doc/cla/individual/gjanssens.md
new file mode 100644
index 00000000000..d3cda29d978
--- /dev/null
+++ b/doc/cla/individual/gjanssens.md
@@ -0,0 +1,11 @@
+Belgium, 2019-05-09
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Geert Janssens geert@kobaltwit.be https://github.com/gjanssens
diff --git a/doc/howtos/backend/exercise-basic-action b/doc/howtos/backend/exercise-basic-action
index 8fa44f743c6..277e88bed1e 100644
--- a/doc/howtos/backend/exercise-basic-action
+++ b/doc/howtos/backend/exercise-basic-action
@@ -31,7 +31,7 @@ Index: addons/openacademy/views/openacademy.xml
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="help" type="html">
-+ <p class="oe_view_nocontent_smiling_face">Create the first course
++ <p class="o_view_nocontent_smiling_face">Create the first course
+ </p>
+ </field>
+ </record>
diff --git a/doc/howtos/backend/exercise-searchview b/doc/howtos/backend/exercise-searchview
index ed6fc681a99..e11c96627e7 100644
--- a/doc/howtos/backend/exercise-searchview
+++ b/doc/howtos/backend/exercise-searchview
@@ -24,5 +24,5 @@ Index: addons/openacademy/views/openacademy.xml
<field name="view_mode">tree,form</field>
+ <field name="context" eval="{'search_default_my_courses': 1}"/>
<field name="help" type="html">
- <p class="oe_view_nocontent_smiling_face">Create the first course
+ <p class="o_view_nocontent_smiling_face">Create the first course
</p>
diff --git a/odoo/addons/base/models/ir_mail_server.py b/odoo/addons/base/models/ir_mail_server.py
index 6773e1f61cc..2422b9cf4a9 100644
--- a/odoo/addons/base/models/ir_mail_server.py
+++ b/odoo/addons/base/models/ir_mail_server.py
@@ -119,10 +119,18 @@ def encode_rfc2822_address_header(header_text):
# Header as a string, using an unlimited line length.", the old one
# was "A synonym for Header.encode()." so call encode() directly?
name = Header(pycompat.to_text(name)).encode()
- return formataddr((name, email))
+ # if the from does not follow the (name <addr>),* convention, we might
+ # try to encode meaningless strings as address, as getaddresses is naive
+ # note it would also fail on real addresses with non-ascii characters
+ try:
+ return formataddr((name, email))
+ except UnicodeEncodeError:
+ _logger.warning(_('Failed to encode the address %s\n'
+ 'from mail header:\n%s') % addr, header_text)
+ return ""
addresses = getaddresses([pycompat.to_text(ustr(header_text))])
- return COMMASPACE.join(encode_addr(a) for a in addresses)
+ return COMMASPACE.join(a for a in (encode_addr(addr) for addr in addresses) if a)
class IrMailServer(models.Model):
diff --git a/odoo/addons/base/models/ir_translation.py b/odoo/addons/base/models/ir_translation.py
index 9c3b5a18fcc..000cb6a3a44 100644
--- a/odoo/addons/base/models/ir_translation.py
+++ b/odoo/addons/base/models/ir_translation.py
@@ -650,8 +650,8 @@ class IrTranslation(models.Model):
rows_by_type = defaultdict(list)
for vals in vals_list:
rows_by_type[vals['type']].append((
- vals['name'], vals['lang'], vals['res_id'], vals['src'], vals['type'],
- vals.get('module'), vals['value'], vals.get('state'), vals.get('comments'),
+ vals['name'], vals['lang'], vals['res_id'], vals['src'] or '', vals['type'],
+ vals.get('module'), vals['value'] or '', vals.get('state'), vals.get('comments'),
))
if rows_by_type['model']:
diff --git a/odoo/addons/base/models/res_company.py b/odoo/addons/base/models/res_company.py
index d9f35d76276..2b69228c655 100644
--- a/odoo/addons/base/models/res_company.py
+++ b/odoo/addons/base/models/res_company.py
@@ -263,19 +263,15 @@ class Company(models.Model):
return self.env['res.config.settings'].open_company()
@api.multi
- def write_company_and_print_report(self, values):
- res = self.write(values)
-
- report_name = values.get('default_report_name')
- active_ids = values.get('active_ids')
- active_model = values.get('active_model')
+ def write_company_and_print_report(self):
+ context = self.env.context
+ report_name = context.get('default_report_name')
+ active_ids = context.get('active_ids')
+ active_model = context.get('active_model')
if report_name and active_ids and active_model:
docids = self.env[active_model].browse(active_ids)
return (self.env['ir.actions.report'].search([('report_name', '=', report_name)], limit=1)
- .with_context(values)
.report_action(docids))
- else:
- return res
@api.model
def action_open_base_onboarding_company(self):
diff --git a/odoo/addons/base/models/res_config.py b/odoo/addons/base/models/res_config.py
index 9d94f636164..4572d2a2ba1 100644
--- a/odoo/addons/base/models/res_config.py
+++ b/odoo/addons/base/models/res_config.py
@@ -543,7 +543,7 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin)
_logger.warning(WARNING_MESSAGE, value, field, icp)
value = 0.0
elif field.type == 'boolean':
- value = value.lower() == 'true'
+ value = bool(value)
res[name] = value
# other fields: call the method 'get_values'
diff --git a/odoo/addons/base/models/res_partner.py b/odoo/addons/base/models/res_partner.py
index e9d607f42a8..9f3fcd91c17 100644
--- a/odoo/addons/base/models/res_partner.py
+++ b/odoo/addons/base/models/res_partner.py
@@ -248,7 +248,7 @@ class Partner(models.Model):
@api.depends('is_company', 'name', 'parent_id.name', 'type', 'company_name')
def _compute_display_name(self):
- diff = dict(show_address=None, show_address_only=None, show_email=None)
+ diff = dict(show_address=None, show_address_only=None, show_email=None, html_format=None, show_vat=False)
names = dict(self.with_context(**diff).name_get())
for partner in self:
partner.display_name = names.get(partner.id)
diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py
index a999b14bf5e..6037c59d7d6 100644
--- a/odoo/addons/base/models/res_users.py
+++ b/odoo/addons/base/models/res_users.py
@@ -1044,10 +1044,9 @@ class GroupsView(models.Model):
""" Modify the view with xmlid ``base.user_groups_view``, which inherits
the user form view, and introduces the reified group fields.
"""
- if self._context.get('install_mode'):
- # use installation/admin language for translatable names in the view
- user_context = self.env['res.users'].context_get()
- self = self.with_context(**user_context)
+
+ # remove the language to avoid translations, it will be handled at the view level
+ self = self.with_context(lang=None)
# We have to try-catch this, because at first init the view does not
# exist but we are already creating some basic groups.
@@ -1057,7 +1056,7 @@ class GroupsView(models.Model):
group_employee = view.env.ref('base.group_user')
xml1, xml2, xml3 = [], [], []
xml_by_category = {}
- xml1.append(E.separator(string=_('User Type'), colspan="2", groups='base.group_no_one'))
+ xml1.append(E.separator(string='User Type', colspan="2", groups='base.group_no_one'))
user_type_field_name = ''
for app, kind, gs, category_name in self.get_groups_by_application():
@@ -1088,7 +1087,7 @@ class GroupsView(models.Model):
else:
# application separator with boolean fields
- app_name = app.name or _('Other')
+ app_name = app.name or 'Other'
xml3.append(E.separator(string=app_name, colspan="4", **attrs))
for g in gs:
field_name = name_boolean_group(g.id)
@@ -1178,9 +1177,9 @@ class UsersView(models.Model):
group_multi_company = self.env.ref('base.group_multi_company', False)
if group_multi_company and 'company_ids' in values:
if len(user.company_ids) <= 1 and user.id in group_multi_company.users.ids:
- group_multi_company.write({'users': [(3, user.id)]})
+ user.write({'groups_id': [(3, group_multi_company.id)]})
elif len(user.company_ids) > 1 and user.id not in group_multi_company.users.ids:
- group_multi_company.write({'users': [(4, user.id)]})
+ user.write({'groups_id': [(4, group_multi_company.id)]})
return user
@api.multi
@@ -1191,9 +1190,9 @@ class UsersView(models.Model):
if group_multi_company and 'company_ids' in values:
for user in self:
if len(user.company_ids) <= 1 and user.id in group_multi_company.users.ids:
- group_multi_company.write({'users': [(3, user.id)]})
+ user.write({'groups_id': [(3, group_multi_company.id)]})
elif len(user.company_ids) > 1 and user.id not in group_multi_company.users.ids:
- group_multi_company.write({'users': [(4, user.id)]})
+ user.write({'groups_id': [(4, group_multi_company.id)]})
return res
def _remove_reified_groups(self, values):
diff --git a/odoo/addons/base/static/src/js/res_config_settings.js b/odoo/addons/base/static/src/js/res_config_settings.js
index 404ad4d3d68..9ad0a2c9196 100644
--- a/odoo/addons/base/static/src/js/res_config_settings.js
+++ b/odoo/addons/base/static/src/js/res_config_settings.js
@@ -309,7 +309,7 @@ var BaseSettingRenderer = FormRenderer.extend({
module.settingView.find('h2').addClass('o_hidden');
module.settingView.find('.settingSearchHeader').addClass('o_hidden');
module.settingView.find('.o_settings_container').removeClass('mt16');
- var resultSetting = module.settingView.find("label:containsTextLike('" + self.searchText + "')");
+ var resultSetting = module.settingView.find(".o_form_label:containsTextLike('" + self.searchText + "')");
if (resultSetting.length > 0) {
resultSetting.each(function () {
var settingBox = $(this).closest('.o_setting_box');
@@ -347,8 +347,8 @@ var BaseSettingRenderer = FormRenderer.extend({
}
var match = text.search(new RegExp(word, "i"));
word = text.substring(match, match + word.length);
- var hilitedWord = "<span class='highlighter'>" + word + '</span>';
- return text.replace(word, hilitedWord);
+ var highlightedWord = "<span class='highlighter'>" + word + '</span>';
+ return text.replace(word, highlightedWord);
},
});
diff --git a/odoo/addons/base/static/tests/base_settings_tests.js b/odoo/addons/base/static/tests/base_settings_tests.js
index 319e7bf93b5..98d68b1281e 100644
--- a/odoo/addons/base/static/tests/base_settings_tests.js
+++ b/odoo/addons/base/static/tests/base_settings_tests.js
@@ -25,7 +25,7 @@ QUnit.module('base_settings_tests', {
QUnit.module('BaseSetting');
QUnit.test('change setting on nav bar click in base settings', async function (assert) {
- assert.expect(4);
+ assert.expect(5);
var form = await createView({
View: BaseSettingsView,
@@ -63,7 +63,7 @@ QUnit.module('base_settings_tests', {
'<field name="foo"/>'+
'</div>'+
'<div class="o_setting_right_pane">'+
- '<label for="foo"/>'+
+ '<span class="o_form_label">Foo</span>'+
'<div class="text-muted">'+
'this is foo'+
'</div>'+
@@ -79,9 +79,11 @@ QUnit.module('base_settings_tests', {
assert.hasAttrValue(form.$('.selected'), 'data-key',"crm","crm setting selected");
assert.isVisible(form.$(".settings .app_settings_block"), "project settings show");
await testUtils.fields.editAndTrigger(form.$('.searchInput'), 'b', 'keyup');
- assert.strictEqual($('.highlighter').html(),"B","b word hilited");
+ assert.strictEqual(form.$('.highlighter').html(), "B", "b word highlighted");
await testUtils.fields.editAndTrigger(form.$('.searchInput'), 'bx', 'keyup');
assert.isVisible(form.$('.notFound'), "record not found message shown");
+ form.$('.searchInput').val('f').trigger('keyup');
+ assert.strictEqual(form.$('span.o_form_label .highlighter').html(), "F", "F word highlighted");
form.destroy();
});
diff --git a/odoo/addons/test_convert/__init__.py b/odoo/addons/test_convert/__init__.py
index dc5e6b693d1..a92fd9a2904 100644
--- a/odoo/addons/test_convert/__init__.py
+++ b/odoo/addons/test_convert/__init__.py
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import models
+from . import models # noqa
diff --git a/odoo/addons/test_convert/ir.model.access.csv b/odoo/addons/test_convert/ir.model.access.csv
index 0282a269c41..0ba260e485e 100644
--- a/odoo/addons/test_convert/ir.model.access.csv
+++ b/odoo/addons/test_convert/ir.model.access.csv
@@ -1,2 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_test_convert_usered,access_test_convert_usered,model_test_convert_usered,base.group_user,1,1,1,1
+access_test_convert_test_model,access_test_convert_test_model,model_test_convert_test_model,,1,1,1,1 \ No newline at end of file
diff --git a/odoo/addons/test_convert/models.py b/odoo/addons/test_convert/models.py
index e48ac2d1e7f..c98eafe624a 100644
--- a/odoo/addons/test_convert/models.py
+++ b/odoo/addons/test_convert/models.py
@@ -1,8 +1,23 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import models, fields, api
+from odoo import api, fields, models
+class TestModel(models.Model):
+ _name = 'test_convert.test_model'
+ _description = "Test Convert Model"
+
+ @api.model
+ def action_test_date(self, today_date):
+ return True
+
+ @api.model
+ def action_test_time(self, cur_time):
+ return True
+
+ @api.model
+ def action_test_timezone(self, timezone):
+ return True
class Usered(models.Model):
_name = 'test_convert.usered'
diff --git a/odoo/addons/test_convert/tests/test_convert.py b/odoo/addons/test_convert/tests/test_convert.py
index 709a5eeab10..a2615ac488b 100644
--- a/odoo/addons/test_convert/tests/test_convert.py
+++ b/odoo/addons/test_convert/tests/test_convert.py
@@ -4,8 +4,10 @@
import collections
import unittest
+from lxml import etree as ET
from lxml.builder import E
+import odoo
from odoo.tests import common
from odoo.tools.convert import xml_import, _eval_xml
@@ -16,6 +18,25 @@ class TestEvalXML(common.TransactionCase):
def eval_xml(self, node, obj=None):
return _eval_xml(obj, node, self.env)
+ def test_function_eval(self):
+ def id_get(): pass
+ Obj = collections.namedtuple('Obj', ['module', 'idref', 'id_get'])
+ obj = Obj('test_convert', {}, id_get)
+
+ try:
+ test_datetime = ET.XML("<function name='action_test_date' model='test_convert.test_model' eval='[datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")]'/>")
+ self.eval_xml(node=test_datetime, obj=obj)
+ test_time = ET.XML("<function name='action_test_time' model='test_convert.test_model' eval='[time.strftime(\"%Y-%m-%d %H:%M:%S\")]'/>")
+ self.eval_xml(node=test_time, obj=obj)
+ test_timedelta = ET.XML("<function name='action_test_date' model='test_convert.test_model' eval='[(datetime.today()-timedelta(days=365)).strftime(\"%Y-%m-%d %H:%M:%S\")]'/>")
+ self.eval_xml(node=test_timedelta, obj=obj)
+ test_relativedelta = ET.XML("<function name='action_test_date' model='test_convert.test_model' eval='[(datetime.today()+relativedelta(months=3)).strftime(\"%Y-%m-%d %H:%M:%S\")]'/>")
+ self.eval_xml(node=test_relativedelta, obj=obj)
+ test_timezone = ET.XML("<function name='action_test_timezone' model='test_convert.test_model' eval='[pytz.timezone(\"Asia/Calcutta\")]'/>")
+ self.eval_xml(node=test_timezone, obj=obj)
+ except ValueError as e:
+ self.fail(e.message)
+
def test_char(self):
self.assertEqual(
self.eval_xml(Field("foo")),
diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py
index bb9f10c7792..e7deced446f 100644
--- a/odoo/addons/test_new_api/tests/test_new_fields.py
+++ b/odoo/addons/test_new_api/tests/test_new_fields.py
@@ -255,11 +255,12 @@ class TestFields(common.TransactionCase):
self.assertEqual(c.display_name, 'B / C')
self.assertEqual(d.display_name, 'B / C / D')
- b.name = 'X'
+ # rename several records to trigger several recomputations at once
+ (d + c + b).write({'name': 'X'})
self.assertEqual(a.display_name, 'A')
self.assertEqual(b.display_name, 'X')
- self.assertEqual(c.display_name, 'X / C')
- self.assertEqual(d.display_name, 'X / C / D')
+ self.assertEqual(c.display_name, 'X / X')
+ self.assertEqual(d.display_name, 'X / X / X')
# delete b; both c and d are deleted in cascade; c should also be marked
# to recompute, but recomputation should not fail...
diff --git a/odoo/fields.py b/odoo/fields.py
index 857a942bdb2..cde78d4b180 100644
--- a/odoo/fields.py
+++ b/odoo/fields.py
@@ -116,8 +116,8 @@ class Field(MetaField('DummyField', (object,), {})):
:param required: whether the value of the field is required (boolean, by
default ``False``)
- :param index: whether the field is indexed in database (boolean, by
- default ``False``)
+ :param index: whether the field is indexed in database. Note: no effect
+ on non-stored and virtual fields. (boolean, by default ``False``)
:param default: the default value for the field; this is either a static
value, or a function taking a recordset and returning a value; use
@@ -640,6 +640,7 @@ class Field(MetaField('DummyField', (object,), {})):
_related_help = property(attrgetter('help'))
_related_groups = property(attrgetter('groups'))
_related_group_operator = property(attrgetter('group_operator'))
+ _related_context_dependent = property(attrgetter('context_dependent'))
@property
def base_field(self):
@@ -1090,6 +1091,8 @@ class Field(MetaField('DummyField', (object,), {})):
recs = record._recompute_check(self)
if recs:
# recompute the value (only in cache)
+ if self.recursive:
+ recs = record
self.compute_value(recs)
# HACK: if result is in the wrong cache, copy values
if recs.env != env:
diff --git a/odoo/models.py b/odoo/models.py
index 77701cf676a..ddbae89f9a4 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -4035,8 +4035,7 @@ Fields:
# The quotes surrounding `ir_translation` are important as well.
unique_translation_subselect = """
(SELECT res_id, value FROM "ir_translation"
- WHERE name=%s AND lang=%s AND value!=''
- ORDER BY res_id, id DESC)
+ WHERE type='model' AND name=%s AND lang=%s AND value!='')
"""
alias, alias_statement = query.add_join(
(table_alias, unique_translation_subselect, 'id', 'res_id', field),
@@ -5478,6 +5477,19 @@ Fields:
if field.type in ('one2many', 'many2many') else record[name]
)
+ def has_changed(self, name):
+ """ Return whether a field on record has changed. """
+ record = self['<record>']
+ subnames = self['<tree>'][name]
+ if not subnames:
+ return self[name] != record[name]
+ else:
+ return len(self[name]) != len(record[name]) or any(
+ line_snapshot.has_changed(subname)
+ for line_snapshot in self[name]
+ for subname in subnames
+ )
+
def diff(self, other):
""" Return the values in ``self`` that differ from ``other``.
Requires record cache invalidation for correct output!
@@ -5553,24 +5565,23 @@ Fields:
result = {'warnings': OrderedSet()}
- # process names in order (or the keys of values if no name given)
+ # process names in order
while todo:
- name = todo.pop(0)
- if name in done:
- continue
- done.add(name)
-
# apply field-specific onchange methods
- if field_onchange.get(name):
- record._onchange_eval(name, field_onchange[name], result)
-
- # make a snapshot (this forces evaluation of computed fields)
- snapshot1 = Snapshot(record, nametree)
+ for name in todo:
+ if field_onchange.get(name):
+ record._onchange_eval(name, field_onchange[name], result)
+ done.add(name)
+
+ # determine which fields to process for the next pass
+ todo = [
+ name
+ for name in nametree
+ if name not in done and snapshot0.has_changed(name)
+ ]
- # determine which fields have been modified
- for name in nametree:
- if snapshot1[name] != snapshot0[name]:
- todo.append(name)
+ # make the snapshot with the final values of record
+ snapshot1 = Snapshot(record, nametree)
# determine values that have changed by comparing snapshots
self.invalidate_cache()
diff --git a/odoo/service/server.py b/odoo/service/server.py
index 977cc0a3630..207d590e724 100644
--- a/odoo/service/server.py
+++ b/odoo/service/server.py
@@ -329,7 +329,7 @@ class ThreadedServer(CommonServer):
os._exit(0)
# interrupt run() to start shutdown
raise KeyboardInterrupt()
- elif sig == signal.SIGXCPU:
+ elif hasattr(signal, 'SIGXCPU') and sig == signal.SIGXCPU:
sys.stderr.write("CPU time limit exceeded! Shutting down immediately\n")
sys.stderr.flush()
os._exit(0)
diff --git a/odoo/tests/common.py b/odoo/tests/common.py
index 4cdb964d1e7..c121c6389a4 100644
--- a/odoo/tests/common.py
+++ b/odoo/tests/common.py
@@ -1426,7 +1426,8 @@ class Form(object):
if not any(spec[f] for f in fields):
return
- result = self._model.onchange(self._onchange_values(), fields, spec)
+ record = self._model.browse(self._values.get('id'))
+ result = record.onchange(self._onchange_values(), fields, spec)
if result.get('warning'):
_logger.getChild('onchange').warn("%(title)s %(message)s" % result.get('warning'))
values = result.get('value', {})
diff --git a/odoo/tools/convert.py b/odoo/tools/convert.py
index b2ea452192b..ac4098d4f84 100644
--- a/odoo/tools/convert.py
+++ b/odoo/tools/convert.py
@@ -182,15 +182,18 @@ def _eval_xml(self, node, env):
return tuple(res)
return res
elif node.tag == "function":
- model = env[node.get('model')]
+ model_str = node.get('model')
+ model = env[model_str]
method_name = node.get('name')
# determine arguments
args = []
kwargs = {}
a_eval = node.get('eval')
+
if a_eval:
- self.idref['ref'] = self.id_get
- args = list(safe_eval(a_eval, self.idref))
+ idref2 = _get_idref(self, env, model_str, self.idref)
+ args = safe_eval(a_eval, idref2)
+ args = list(safe_eval(a_eval, idref2))
for child in node:
if child.tag == 'value' and child.get('name'):
kwargs[child.get('name')] = _eval_xml(self, child, env)
diff --git a/odoo/tools/image.py b/odoo/tools/image.py
index 6f02f3c3d65..498a4cfda5b 100644
--- a/odoo/tools/image.py
+++ b/odoo/tools/image.py
@@ -118,14 +118,9 @@ class ImageProcess():
if output_format == 'PNG':
opt['optimize'] = True
if quality:
- alpha = False
- if output_image.mode in ('RGBA', 'LA') or (output_image.mode == 'P' and 'transparency' in output_image.info):
- alpha = output_image.convert('RGBA').split()[-1]
if output_image.mode != 'P':
# Floyd Steinberg dithering by default
output_image = output_image.convert('RGBA').convert('P', palette=Image.WEB, colors=256)
- if alpha:
- output_image.putalpha(alpha)
if output_format == 'JPEG':
opt['optimize'] = True
opt['quality'] = quality or 95
diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py
index 160cce4f6e4..9dfd9ec09e0 100644
--- a/odoo/tools/translate.py
+++ b/odoo/tools/translate.py
@@ -984,8 +984,11 @@ def trans_generate(lang, modules, cr):
extra_comments = extra_comments or []
if not module: return
src_file = open(fabsolutepath, 'rb')
+ options = {}
+ if extract_method == 'python':
+ options['encoding'] = 'UTF-8'
try:
- for extracted in extract.extract(extract_method, src_file, keywords=extract_keywords):
+ for extracted in extract.extract(extract_method, src_file, keywords=extract_keywords, options=options):
# Babel 0.9.6 yields lineno, message, comments
# Babel 1.3 yields lineno, message, comments, context
lineno, message, comments = extracted[:3]